Blog

  • P.E.B.K.A.C.

    Tech support, this is Eden.

    Yeah, uh, hi. It’s me again.

    Oh. You. Yes, how can I help you?

    Uh, I’m having trouble with my computer.

    What seems to be the problem this time?

    The text is too small to read.

    Try moving a little closer, like we talked about last time.

    Ok, well, now it’s all black.

    The screen is black?

    No, everything. I can’t see a thing.

    Is the power out?

    No, of course not. I can maybe see a little bit if I look way down.

    What about if you tilt your head sideways?

    Now I can see off to the left.

    Ma’am, I think you have your hat pulled down over your eyes. I suggest you take it off and put it back on.

    Oh, hey, that works great! How do you know all this stuff?

    It’s just common sense.

    Hm. Where can I get some of that?

    Let me check if we have any in stock.

    Looks like all we have left is some that someone sent in for a refurb. Let me get the lid screwed back on and I’ll overnight it to you.

  • Octets from INET_ATON

    So you’ve follow Arjen’s suggestion and are storing all your IP addresses in a nice integer format, having converted them with INET_ATON and selecting them with INET_NTOA.

    But now you want to use one of the octets, maybe to find out how many connections you get from 192.x.x.x.

    One way is to turn the integer back into a string, and use one of the substring functions to grab the parts you need. Let’s try that out for speed (I’ll use 192.168.1.1 in my examples, which translates to 3232235777):

    mysql> SELECT BENCHMARK(10000000, SUBSTRING_INDEX(INET_NTOA(3232235777), '.', 1) = '192');
    +-----------------------------------------------------------------------------+
    | BENCHMARK(10000000, SUBSTRING_INDEX(INET_NTOA(3232235777), '.', 1) = '192') |
    +-----------------------------------------------------------------------------+
    |                                                                           0 |
    +-----------------------------------------------------------------------------+
    1 row in set (6.28 sec)

    Is there a better way? Of course; I’d hardly be writing this blog entry if there weren’t. According to the manual, the calculation for INET_ATON is
    192x256^3 + 168x256^2 + 1x256^1 + 1x256^0
    Notice all those 256s. 256 is 2^8, so all we have to do is shift the bits to the right by the appropriate multiple of 8, and then throw away anything higher than the 8th bit. We’ll check real quick that gives us the right number:

    mysql> SELECT
        ->   (3232235777 >> 24) & 255 AS oct1,
        ->   (3232235777 >> 16) & 255 AS oct2,
        ->   (3232235777 >> 8) & 255 AS oct3,
        ->   (3232235777 >> 0) & 255 AS oct4;
    +------+------+------+------+
    | oct1 | oct2 | oct3 | oct4 |
    +------+------+------+------+
    |  192 |  168 |    1 |    1 |
    +------+------+------+------+

    Looks like the right answer. Let’s see how it compares.

    mysql> SELECT BENCHMARK(10000000, (3232235777 >> 24) & 255 = 192);
    +-----------------------------------------------------+
    | BENCHMARK(10000000, (3232235777 >> 24) & 255 = 192) |
    +-----------------------------------------------------+
    |                                                   0 |
    +-----------------------------------------------------+
    1 row in set (1.61 sec)

    Noticeably faster, and you get to use those bitwise operators that you’ve always wondered about but never had a reason to use.

  • Dawn of a New Era

    Feb. 26, 2007

    Dear diary,

    Today was my 3rd birthday. Daddy says I’m now old enough to have a ghost write my biography. I don’t really know what a ghost is, but if it’s anything like that guy in the scary mask last October, I want nothing to do with it.

    We spent a long time getting ready for the party. I found out that sometimes you get your face painted, so I just had to try it out. Daddy laughed and said I didn’t quite get the concept. I think I’m quite the artist, and cleverly circumvented the ‘no nose ring’ rule.


    Yesterday was Sunday. Daddy doesn’t go to work; we go to church to see Satya and Hannah. Mommy dressed me in a pretty green dress with flowers, and I got to wear a tiara in my hair. Everybody at church talked about the beautiful princess.

    We got pizza after church, and I made sure to drop some marinara on my dress. Eden sniffed and called me an amateur.

    Eden and I had to take a nap when we got home, but Mommy and Daddy must have been hard at work, because when I got up, Hannah and Satya and Madeline and Emma and Bethany and their mommies and daddies were all here, and there were balloons all over the place and pink flowers from our yard on the table. The ‘puter was playing all my favorite Veggies Tales songs and some loud songs that are fun to dance to.

    All the daddies went outside and helped us nail together firetrucks with holes in the back to hold crayons. Hannah’s daddy said the very first thing to do with any project is to take the instructions and very carefully throw them on the floor. Maybe that’s why Daddy and I were the only ones that didn’t have to take it apart to finish putting it together, or borrow some extra pieces so we didn’t snap the axles while trying to put the roof on. I painted flowers on mine. We raced them down the driveway, and all the daddies told stories about these Pinewood Derby things they had way back in prehistoric times.

    We took turns playing on the swing while we waited for Daddy and Hannah’s daddy to hang a rope from the tree for Winnie-the-Pooh to hang from. We poked him about a bit with half of the old shower curtain rod. Then we pulled on the ribbons, and Mommy got the one that opened the bottom and candy fell out. We each took one and left the rest, and all the mommies and daddies laughed at us and tried to get us to take more. Mommy had used her crinkly scissors to cut out some hearts and glued them to paper bags for us to put our candy in. Daddy got out his balloons and made a green giraffe and a blue elephant and a pink mouse and a red swan and a white rabbit. We liberated a couple of our balloons. Now the airplanes will have some to play with too.


    We went inside and I got to paint my fingernails bright red, and put stickers on my fingers and ears, and sparkle gloss on my lips. We had bracelets and rings filled with bubbles and diamond-shaped suckers. I don’t know what happened to all the daddies during this part of the party, I’m sure they were there a second ago…


    I opened a whole pile of presents. Then we had hamburgers and corn on the cob and asparagus. Then Abba’s “Super Trooper” came on and we all ran around in the living room and screeched and chased balloons (except for Emma, who wanted to sit just quietly in the middle of the floor and smell the flowers … I mean, read a book).


    Mommy and Daddy made a white butterfly cake and a German Chocolate heart cake. I blew out all my candles in two tries. After all the cake was gone, everybody who is anybody lined up to get their diapers changed. Then the party was over and everybody went home with their haul of firetrucks and crayons and balloons and paddle balls and pretty little purses.


    Tonight I helped Daddy unload the dishwasher. I handed him all the plates and glasses so he could put them in the cupboard, and I put away all the silverware all by myself. Daddy and I sang “Deck the Halls” as loud as we could while we worked, although Daddy doesn’t seem to know the tune very well — I’m sure that’s either Handel’s “Messiah” or else Verdi’s “Anvil Chorus”. I put all my books on my shelves in the kitchen, although I really need to get Daddy to build me a proper book case, since they keep ending up on the floor and scattered throughout the house, an artifact which I attribute to a phenomenon known as the tunneling effect.


    Grammy Cheryl and Mr. G and Aunt Mara came over for dinner. We had hamburgers and broccoli and squash and more cake. Mr. G said he likes Daddy’s home-brewed root beer, which I’m not allowed to have because it has more beer than root in it. Mara gave me a laptop computer, so I’ll soon be frequenting Internet cafes and blogging about my Web 2.0 experiences and writing beatnik poetry in hexadecimal.

  • Comparing version output

    Features and bugs come and go with each release of MySQL, making it tricky for us hobbyists to help each other out when we don’t have the same version installed and observed behavior is a little different for each.

    So I installed a dozen versions of MySQL, ranging from 3.23.58 to the current beta, and wrote a script (download below) to send one stream of SQL to each and compare output. Now I can show how identical SQL produces different results.

    There’s no reason they have to be different versions; I might run this script against a bunch of identical slaves to check replication status.

    I found some unexpected things along the way (who’d have thought the ON clause was so late to join the syntax?) and some silly trivia that just might win me a book someday (table formatted output differs in justification and whitespace between a few of the versions – can you find where?)

    MySQL Demux

    Requires the PEAR package Console_Getopt.

  • Drop all tables

    Since there exists neither DROP TABLE * nor DROP TABLE WHERE name LIKE ‘something’, here are two ways to achieve the same:

    For version 5.0+:

    mysql -N -e "SELECT CONCAT('DROP TABLE ', GROUP_CONCAT(table_name), ';') FROM information_schema.tables WHERE table_schema = 'dbName'" | mysql dbName

    (more…)

  • Audit Trail

    More an exercise in stored procedures, triggers, and building queries culled from the information_schema than anything else, here’s an audit trail setup.

    Audit trails let you see how data has changed over the course of time, so you know who to blame when something goes wrong.

    This particular setup works by adding an AFTER UPDATE trigger to each table to be audited. That trigger looks at the OLD and NEW values of each field, and if there is a difference, inserts a record into the auditLog.

    • auditTrail.sql – Creates the table to hold the audit data and the stored procedure which is called to add a trigger to each table you wish to audit.
    • setupTest.sql – Creates an example data table such as you might want to audit, and calls the stored procedure to build the trigger for it. The output from that stored procedure should be piped back in to MySQL to actually create the trigger. Why doesn’t it do this automatically? Because prepared statements don’t support CREATE TRIGGER statements as of 5.0.27.
    • runTest.sql – Inserts a few rows of data into the example data table, updates them, and displays the changes recorded in the audit table.

    Here’s the Windows command line to run the test in the `test` database:
    mysql test < auditTrail.sql && mysql -N test < setupTest.sql | mysql --delimiter=$ test && mysql -t test < runTest.sql

    For my purposes, watching updates was sufficient. You could extend this to cover inserts and deletes too.

    There are a number of drawbacks to this particular method:

    • It's a bit unwieldy, since the SQL output has to be piped back in to create the triggers. But hey, if the Wizard can do it, it's ok for us mere mortals too. This could be wrapped in a script. Or maybe it's a good thing, because then you can go edit the output and exclude fields you don't want audited.
    • The tigger has to be created for each table separately. Again, handy if there are only a couple tables you want to audit, but a real pain if you wanted to audit all 297 tables in your database.
    • If the table structure changes, you'll need to drop and recreate the trigger.
    • The stored procedure assumes the current database. Again, something trivial to change.
    • Hope you didn't want to use your AFTER UPDATE trigger for something else.

    My next trick will be to add something so that we know which user made the change. Not which MySQL user, mind you, since these changes are issued from PHP scripts which all run as the same MySQL user. Somehow I need to pass in my application user id. Any suggestions?

  • Here Be Dragons

    I have a table of maps:

    CREATE TABLE map (
        mapName varchar(255),
        mapType ENUM('continent', 'region', 'country'),
        continent varchar(255) NULL,
        region varchar(255) NULL,
        country varchar(255) NULL
    );
    INSERT INTO map VALUES 
        ('Europe', 'continent', 'Europe', NULL, NULL),
        ('North America', 'continent', 'North America', NULL, NULL),
        ('Northern Hemisphere', 'region', NULL, 'Northern Hemisphere', NULL),
        ('Sweden', 'country', NULL, NULL, 'Sweden'),
        ('Mexico', 'country', NULL, NULL, 'Mexico');

    And a table of locations:

    CREATE TABLE locations (
        locationId int auto_increment primary key,
        continent varchar(255),
        region varchar(255),
        country varchar(255)
    );
    
    INSERT INTO locations VALUES 
        (NULL, 'Europe', 'Northern Hemisphere', 'Sweden'),
        (NULL, 'North America',  'Northern Hemisphere', 'Canada'),
        (NULL, 'North America',  'Pacific Northwest', 'USA');

    Obviously, this example is denormalized – in the real world, I’d use a surrogate key and store continentId, regionId, and countryId. Even then, it’s not the ideal layout for this application. But that’s just part of the challenge.

    Here’s the goal: find the most specific map for each location. By most specific, I mean use a country map if available. If not, use a region map. Use a continent map as a last resort.

    So, the result set should be:

    +------------+---------------------+
    | locationId | mapName             |
    +------------+---------------------+
    |          1 | Sweden              |
    |          2 | Northern Hemisphere |
    |          3 | North America       |
    +------------+---------------------+

    Bonus points if it works pre-4.1


    Note the definition of map.mapType. In particular, the order of the values.

    Then take a gander at section 3.6 of the manual.

    SELECT 
        locationId,
        m.mapName
    FROM 
        locations l
        JOIN map m ON (
            l.continent = m.continent
            OR l.region = m.region
            OR l.country = m.country
        )
        LEFT JOIN map m2 ON (
            (l.continent = m2.continent
            OR l.region = m2.region
            OR l.country = m2.country)
            AND CAST(m.mapType AS unsigned) < CAST(m2.mapType AS unsigned)
        )
    WHERE
        m2.mapType IS NULL
    GROUP BY 
        locationId;

  • Empty Set Equality

    SELECT 1 LIMIT 0;
    Empty set (0.00 sec)
    
    SELECT 2 LIMIT 0;
    Empty set (0.00 sec)
    
    SELECT NULL LIMIT 0;
    Empty set (0.00 sec)

    Exactly what I’d expect. So here’s the question:

    SELECT '1 = 1' AS equality, (SELECT 1 LIMIT 0) = (SELECT 1 LIMIT 0) AS result
    UNION SELECT '1 = 2', (SELECT 1 LIMIT 0) = (SELECT 2 LIMIT 0)
    UNION SELECT '1 = NULL', (SELECT 1 LIMIT 0) = (SELECT NULL LIMIT 0);

    What do you think? Are all empty sets created identical?
    MySQL says no.

    +----------+--------+
    | equality | result |
    +----------+--------+
    | 1 = 1    |      1 |
    | 1 = 2    |      0 |
    | 1 = NULL |   NULL |
    +----------+--------+

  • Single-query UNION with LIMIT and ORDER

    I have a query that behaves differently in every version of MySQL I’ve tried. Could you do me a favor? Run the following four lines and post a comment with your results. And if you know of a particular bug fix that addresses this issue, let me know about that too.

    CREATE TABLE `test` (`id` tinyint);
    INSERT INTO test VALUES (1), (2), (3), (4), (5);
    SHOW VARIABLES LIKE 'version%';
    (SELECT * from test ORDER BY id DESC LIMIT 3) ORDER BY id ASC;

    (more…)

  • Auto_increment Indexing

    It’s so common to set the auto_increment column to primary key, it has become common belief that it’s required. Not true; an auto_increment column can use any index type. It doesn’t even have to be unique:

    CREATE TABLE testTable (
      id INT AUTO_INCREMENT,
      INDEX(id)
    ) ENGINE=MyISAM; -- Works for InnoDB too.
    
    INSERT INTO testTable VALUES (1), (1), (NULL);
    
    SELECT * FROM testTable;
    
    +----+
    | id |
    +----+
    |  1 |
    |  1 |
    |  2 |
    +----+