Code Spatter » MySQL http://codespatter.com Fri, 04 Sep 2009 14:59:15 +0000 http://wordpress.org/?v=2.8.4 en hourly 1 Careful with Numeric Data Types in MySQL http://codespatter.com/2008/07/24/careful-with-numeric-data-types-in-mysql/ http://codespatter.com/2008/07/24/careful-with-numeric-data-types-in-mysql/#comments Thu, 24 Jul 2008 13:55:04 +0000 Greg Allard http://codespatter.com/?p=57 // = 0) { links[i].innerHTML = 'View Comments'; query += 'wpid' + i + '=' + encodeURIComponent(links[i].getAttribute('wpid')) + '&'; } } document.write(' TINYINT can be set to length of 4, but that doesn’t mean the max value is 9999. A tiny int will always have the same range no matter what size you specify since that isn’t what it is for with integers. It is the display width for the command line output. This is what I was [...] Related posts:
  1. How to Break a MySQL Left Join Care must be taken when placing conditions on the results...
  2. Tips for MySQL to PostgreSQL Switch If you’ve decided to move a few tables from MySQL...
  3. How To Use Triggers to Track Changes in MySQL Setting constraints and rules in the database is better than...
]]>
TINYINT can be set to length of 4, but that doesn’t mean the max value is 9999. A tiny int will always have the same range no matter what size you specify since that isn’t what it is for with integers. It is the display width for the command line output.

This is what I was able to find in the MySQL manual:

The display width does not constrain the range of values that can be stored in the column, nor the number of digits that are displayed for values having a width exceeding that specified for the column.

Reading the manual for things like this isn’t something people normally do. I had no idea about this until recently and I’ve been using MySQL for a long time.

Reference Chart for Max Signed Integers

TINYINT 127
SMALLINT 32767
MEDIUMINT 8388607
INT 2147483647
BIGINT 9223372036854775807

However, CHAR and VARCHAR work as expected with these limits.

Related posts:

  1. How to Break a MySQL Left Join Care must be taken when placing conditions on the results...
  2. Tips for MySQL to PostgreSQL Switch If you’ve decided to move a few tables from MySQL...
  3. How To Use Triggers to Track Changes in MySQL Setting constraints and rules in the database is better than...
]]>
http://codespatter.com/2008/07/24/careful-with-numeric-data-types-in-mysql/feed/ 2
Conditions on Count or Sum in MySQL http://codespatter.com/2008/07/22/conditions-on-count-or-sum-in-mysql/ http://codespatter.com/2008/07/22/conditions-on-count-or-sum-in-mysql/#comments Tue, 22 Jul 2008 13:55:57 +0000 Greg Allard http://codespatter.com/?p=42
  • How to Break a MySQL Left Join Care must be taken when placing conditions on the results...
  • How To Use Triggers to Track Changes in MySQL Setting constraints and rules in the database is better than...
  • MySQL Fun Trying to find a way to update a bunch of...
  • ]]>
    When selecting count, sum, or some other aggregate function, the value isn’t determined until after the WHERE clause so a condition can’t be placed there. There is still syntax for specifying a condition for this, it is to use HAVING.

    The following example is to count all of the logs an active user has made only if they have more than two logs.

    SELECT users.user_id, count(logs.log_id) AS count
    FROM users
    JOIN logs
    ON users.user_id = logs.user_id
    WHERE user.deleted = 0
    GROUP BY user.user_id
    HAVING count > 2

    It works the same for sum. Example shows users who have logged less than an hour of time.

    SELECT users.user_id, sum(logs.log_time) AS sum
    FROM users
    JOIN logs
    ON users.user_id = logs.user_id
    WHERE user.deleted = 0
    GROUP BY user.user_id
    HAVING sum < 60

    Common aggregate functions this works with:

    • AVG
    • COUNT
    • MAX
    • MIN
    • STD
    • SUM

    Related posts:

    1. How to Break a MySQL Left Join Care must be taken when placing conditions on the results...
    2. How To Use Triggers to Track Changes in MySQL Setting constraints and rules in the database is better than...
    3. MySQL Fun Trying to find a way to update a bunch of...
    ]]>
    http://codespatter.com/2008/07/22/conditions-on-count-or-sum-in-mysql/feed/ 12
    Tips for MySQL to PostgreSQL Switch http://codespatter.com/2008/07/02/tips-for-mysql-to-postgresql-switch/ http://codespatter.com/2008/07/02/tips-for-mysql-to-postgresql-switch/#comments Wed, 02 Jul 2008 13:38:21 +0000 Greg Allard http://codespatter.com/?p=38
  • How to Break a MySQL Left Join Care must be taken when placing conditions on the results...
  • How To Use Triggers to Track Changes in MySQL Setting constraints and rules in the database is better than...
  • Careful with Numeric Data Types in MySQL TINYINT can be set to length of 4, but that...
  • ]]>
    If you’ve decided to move a few tables from MySQL to PostgreSQL, these few tips might help. I won’t get into any reasons why to move to PostgreSQL or not. There are already many discussions on the topic.

    Create Syntax

    The first five listed need to be done in order; the rest can be in any.

    • Replace mediumint with int
    • Replace tinyint with smallint
    • Replace int\([0-9]+\) with int. (This is a regular expression that will find any instance of int() where there is at least one number between the parenthesis)
    • Remove all instances of NOT NULL I don’t know what I was thinking here.
    • Replace int\s+auto_increment with SERIAL (Another regular expression that will catch a case with multiple spaces between int and auto_increment).
      1. UPDATE: try int(\s+|\s+NOT NULL\s+)auto_increment instead to catch when not null is in between int and auto.
      2. UPDATE: try (smallint|int)(\s+|\s+NOT NULL\s+)auto_increment so it will catch the case when it is smallint as well.
    • Remove ENGINE=MyISAM (Change if you use a different engine, i.e. innodb)
    • Remove DEFAULT CHARSET=latin1 (Change if you use a different character set)
    • Remove AUTO_INCREMENT=[0-9]+ (Another regular expression to find all cases of an auto increment number)
    • Remove IF NOT EXISTS
    • Remove all `

    phpMyAdmin

    phpMyAdmin has options when exporting that may make some of these steps unnecessary. If you keep the create sql in a separate file from the insert sql, you save yourself from changing any data that may look like some of the above when it isn’t actual sql syntax.

    Auto Increment and Serial

    After running the create table SQL, a sequence table will be created for the serial column. This sequence will need to be altered if the auto increment won’t be starting at one. A query like the one below will need to be done for each table.

    ALTER SEQUENCE tab_name_col_name_seq RESTART WITH #;

    Indexes

    To define a column to be indexed, it is done in a separate query. Remove anything that looks like KEY username (username). The query to run after the create table query should look like

    CREATE INDEX index_name ON table_name (column_name);

    Insert Syntax

    The syntax here is pretty close, just one thing needs to be changed.

    • Replace “” with \”. (MySQL and PostgreSQL use different methods of escaping these quotes.)

    Make sure the complete inserts option is enabled and extended inserts isn’t. PosgreSQL doesn’t support multiple inserts with one query the same way. (As far as I know at the moment)

    Your Mileage May Vary

    This was tested using phpMyAdmin export and phpPgAdmin SQL executing. The regular expressions and other replace commands were done in jEdit. With my few test cases I probably didn’t catch everything. Let me know how it works out for you.

    Related posts:

    1. How to Break a MySQL Left Join Care must be taken when placing conditions on the results...
    2. How To Use Triggers to Track Changes in MySQL Setting constraints and rules in the database is better than...
    3. Careful with Numeric Data Types in MySQL TINYINT can be set to length of 4, but that...
    ]]>
    http://codespatter.com/2008/07/02/tips-for-mysql-to-postgresql-switch/feed/ 23
    How To Use Triggers to Track Changes in MySQL http://codespatter.com/2008/05/06/how-to-use-triggers-to-track-changes-in-mysql/ http://codespatter.com/2008/05/06/how-to-use-triggers-to-track-changes-in-mysql/#comments Tue, 06 May 2008 13:34:50 +0000 Greg Allard http://codespatter.com/?p=36
  • How to Break a MySQL Left Join Care must be taken when placing conditions on the results...
  • Careful with Numeric Data Types in MySQL TINYINT can be set to length of 4, but that...
  • Tips for MySQL to PostgreSQL Switch If you’ve decided to move a few tables from MySQL...
  • ]]>
    Setting constraints and rules in the database is better than writing special code to handle the same task since it will prevent another developer from writing a different query that bypasses all of the special code and could leave your database with poor data integrity.

    For a long time I was copying info to another table using a script since MySQL didn’t support triggers at the time. I have now found this trigger to be more effective at keeping track of everything.

    This trigger will copy an old value to a history table if it is changed when someone edits a row. Editor ID and last mod are stored in the original table every time someone edits that row; the time corresponds to when it was changed to its current form.

    DROP TRIGGER IF EXISTS history_trigger $$
     
    CREATE TRIGGER history_trigger
    BEFORE UPDATE ON clients
        FOR EACH ROW
        BEGIN
            IF OLD.first_name != NEW.first_name
            THEN
                    INSERT INTO history_clients
                        (
                            client_id    ,
                            col          ,
                            value        ,
                            user_id      ,
                            edit_time
                        )
                        VALUES
                        (
                            NEW.client_id,
                            'first_name',
                            NEW.first_name,
                            NEW.editor_id,
                            NEW.last_mod
                        );
            END IF;
     
            IF OLD.last_name != NEW.last_name
            THEN
                    INSERT INTO history_clients
                        (
                            client_id    ,
                            col          ,
                            value        ,
                            user_id      ,
                            edit_time
                        )
                        VALUES
                        (
                            NEW.client_id,
                            'last_name',
                            NEW.last_name,
                            NEW.editor_id,
                            NEW.last_mod
                        );
            END IF;
     
        END;
    $$

    Query Breakdown

    Notice where all of the semi-colons are. If you leave some out, it may take hours to debug your trigger syntax since the error messages don’t always help.

    The OLD and NEW keywords correspond to the value in the row before an update and what is being inserted. Compare these values for each column that you want to track in the table.

    An AFTER INSERT trigger should also be used to copy the initial data, and a BEFORE DELETE trigger would be good if you actually remove rows from the database instead of doing a soft delete like I normally do.

    Delimiter is Tricky

    $$ is being used to delimit the end of the two queries since the semi colons are needed for the interior statements.

    Command Line Delimiter

    To use this create-trigger query at the command line, you can add

    DELIMITER $$

    before the trigger statement and

    DELIMITER ;

    after it.

    phpMyAdmin Delimiter

    If you are using phpMyAdmin, there should be a small box below the main SQL box where you can enter $$.

    Delimiter in php Scripts

    If you would like to have php scripts create triggers you will need to use the mysqli_multi_query function. Below is an example.

    $con = new MySQLi('host', 'user', 'pass', 'db');
     
    $sql = '
    DROP TRIGGER ... ;
    CREATE TRIGGER ... ;
    ';
     
    $con-&gt;multi_query($sql);

    Notice that there isn’t a need for any special delimiter since the multi_query function applies it for you.

    Privileges

    Make sure the database user has the Super privilege since that is needed to create triggers.

    Related posts:

    1. How to Break a MySQL Left Join Care must be taken when placing conditions on the results...
    2. Careful with Numeric Data Types in MySQL TINYINT can be set to length of 4, but that...
    3. Tips for MySQL to PostgreSQL Switch If you’ve decided to move a few tables from MySQL...
    ]]>
    http://codespatter.com/2008/05/06/how-to-use-triggers-to-track-changes-in-mysql/feed/ 36
    Swap Insert for Update When Key Exists http://codespatter.com/2008/04/30/swap-insert-for-update-when-key-exists/ http://codespatter.com/2008/04/30/swap-insert-for-update-when-key-exists/#comments Wed, 30 Apr 2008 14:39:19 +0000 Greg Allard http://codespatter.com/?p=35
  • Multiple Inserts with a Subquery Inserting multiple rows into a table is simple. INSERT INTO...
  • How To Use Triggers to Track Changes in MySQL Setting constraints and rules in the database is better than...
  • How to Break a MySQL Left Join Care must be taken when placing conditions on the results...
  • ]]>
    When synchronizing tables or databases, sometimes it is useful to have a way to update a row if there is already an entry for a key. Using the MySQL syntax, ON DUPLICATE KEY UPDATE, it is possible to insert a row if it doesn’t exist or update it if there is already a row with that key.

    INSERT INTO table1 (
    	id,
    	text,
    	sync_time
    )
    VALUES  (
    	1,
    	'new',
    	UNIX_TIMESTAMP()
    )
    ON DUPLICATE KEY UPDATE
    	text		= VALUES(text),
    	sync_time	= VALUES(sync_time)

    To update table1 with all of table2’s data, a single query can be run without the need of any programming logic.

    INSERT INTO table1 (id, text, sync_time)
    (SELECT id, text, sync_time FROM table2)
    ON DUPLICATE KEY UPDATE
    	text		= VALUES(text),
    	sync_time	= VALUES(sync_time)

    Related posts:

    1. Multiple Inserts with a Subquery Inserting multiple rows into a table is simple. INSERT INTO...
    2. How To Use Triggers to Track Changes in MySQL Setting constraints and rules in the database is better than...
    3. How to Break a MySQL Left Join Care must be taken when placing conditions on the results...
    ]]>
    http://codespatter.com/2008/04/30/swap-insert-for-update-when-key-exists/feed/ 5
    Multiple Inserts with a Subquery http://codespatter.com/2008/03/20/multiple-inserts-with-a-subquery/ http://codespatter.com/2008/03/20/multiple-inserts-with-a-subquery/#comments Thu, 20 Mar 2008 18:36:31 +0000 Greg Allard http://codespatter.com/2008/03/20/multiple-inserts-with-a-subquery/
  • Swap Insert for Update When Key Exists When synchronizing tables or databases, sometimes it is useful to...
  • Tips for MySQL to PostgreSQL Switch If you’ve decided to move a few tables from MySQL...
  • How to Break a MySQL Left Join Care must be taken when placing conditions on the results...
  • ]]>
    Inserting multiple rows into a table is simple.

    INSERT INTO table1 (id) VALUES (1), (2)

    But what if you want to use a subquery to replace the explicit query with something more dynamic. This should be possible, however, searching the internet didn’t get me (or my colleagues) any closer to figuring this out. The obvious attempt of replacing (1), (2) with (subquery) resulted in the error your subquery returns multiple records

    In the end we discovered that removing VALUES from the query gave the desired effect.

    INSERT INTO table1 (id) (SELECT id FROM table2)

    Executing the above query will insert a row into table1 for every result returned by the subquery.

    Related posts:

    1. Swap Insert for Update When Key Exists When synchronizing tables or databases, sometimes it is useful to...
    2. Tips for MySQL to PostgreSQL Switch If you’ve decided to move a few tables from MySQL...
    3. How to Break a MySQL Left Join Care must be taken when placing conditions on the results...
    ]]>
    http://codespatter.com/2008/03/20/multiple-inserts-with-a-subquery/feed/ 7
    How to Break a MySQL Left Join http://codespatter.com/2008/02/19/how-to-break-a-mysql-left-join/ http://codespatter.com/2008/02/19/how-to-break-a-mysql-left-join/#comments Tue, 19 Feb 2008 14:45:15 +0000 Greg Allard http://codespatter.com/2008/02/19/how-to-break-a-mysql-left-join/
  • Conditions on Count or Sum in MySQL When selecting count, sum, or some other aggregate function, the...
  • Tips for MySQL to PostgreSQL Switch If you’ve decided to move a few tables from MySQL...
  • Careful with Numeric Data Types in MySQL TINYINT can be set to length of 4, but that...
  • ]]>
    Care must be taken when placing conditions on the results of the right-hand table of a LEFT JOIN because it could easily become a normal JOIN since MySQL is using a NULL row for every column in the right-hand table when no records exist.

    Since I like to use a soft delete on all tables (hidden but not deleted), I need to put a condition to alter the results from the right-hand table. The proper way to place a condition on this table is to use:

    ...
    WHERE (
    	right_table.foo = 'bar'  -- the condition you want
    		OR
    	right_table.foo IS NULL  -- it's cool if it's empty
    )
    ...

    Here is an Example

    Here are some example SQL statements to see exactly what is happening. First we create two tables with a one-to-many relationship.

    CREATE TABLE tasks (  -- left
     	task_id MEDIUMINT( 5 ) NOT NULL
     		AUTO_INCREMENT PRIMARY KEY ,
    	 task_text TEXT NOT NULL ,
    	 task_deleted TINYINT( 1 ) NOT NULL ,
    	 task_last_mod INT( 11 ) NOT NULL ,
    	 task_created INT( 11 ) NOT NULL
    	) ENGINE = MYISAM;
     
    CREATE TABLE logs (  -- right
    	 log_id MEDIUMINT( 6 ) NOT NULL
    	 	AUTO_INCREMENT PRIMARY KEY ,
    	 task_id MEDIUMINT( 5 ) NOT NULL ,
    	 log_text TEXT NOT NULL ,
    	 log_length INT( 11 ) NOT NULL ,
    	 log_deleted TINYINT( 1 ) NOT NULL ,
    	 log_last_mod INT( 11 ) NOT NULL ,
    	 log_created INT( 11 ) NOT NULL
    ) ENGINE = MYISAM;

    After creating the tables we are ready to add some testing data.

    INSERT INTO tasks (
     	task_id ,
     	task_text ,
    	task_deleted ,
    	task_last_mod ,
    	task_created
    )
    VALUES (
    	 1 , 'Get me a beer.', 0,
    	 1203372686 , 1203372686
    ), (
    	 2 , 'Something ridiculous.', 0,
    	 1203372686 , 1203372686
    ), (
    	 3 , 'a logless task', 0,
    	 1203372686, 1203372686
    );
     
    INSERT INTO logs (
    	 log_id ,
    	 task_id ,
    	 log_text ,
    	 log_length ,
    	 log_deleted ,
    	 log_last_mod ,
    	 log_created
    )
    VALUES (
    	 1, 1, 'got a beer', 120,
    	 0, 1203372686, 1203372686
    ), (
    	 2, 2, 'this took forever', 900,
    	 0, 1203372686, 1203372686
    ), (
    	 3, 1, 'a deleted one', 900,
    	 1, 1203372686, 1203372686
    ), (
    	 4, 1, 'moar beer', 120,
    	 0, 1203372686, 1203372686
    );

    Simple SELECT

    This will get all tasks, count the logs, and sum the logged time whether there is a log or not for the task.

    SELECT task_text, count(tasks.task_id) AS log_count,
    	 sum(log_length) AS total_time
    FROM tasks LEFT JOIN logs
    ON tasks.task_id = logs.task_id
    WHERE task_deleted = 0
    GROUP BY logs.task_id;

    This query produces expected results as seen below.

    task_text log_count total_time
    a logless task 1 NULL
    Get me a beer. 3 1140
    Something ridiculous. 1 900

    Improper Conditional SELECT

    If we were to place a condition on the right-hand table to limit the results to only count and sum the logs that are active, at first we might try:

    SELECT task_text, count(tasks.task_id) AS log_count,
    	 sum(log_length) AS total_time
    FROM tasks LEFT JOIN logs ON tasks.task_id = logs.task_id
    WHERE task_deleted = 0
    AND log_deleted = 0  -- this breaks the left join
    GROUP BY logs.task_id;

    This produces the results in the following table. Notice that the logless task has disappeared and that the log count and sum for the Get me a beer task has changed to the desired values. This is the same as a normal JOIN statement.

    task_text log_count total_time
    Get me a beer. 2 240
    Something ridiculous. 1 900

    The Proper Way

    The proper way to achieve the desired results is to use the following query:

    SELECT task_text, count(tasks.task_id) AS log_count,
    	 sum(log_length) AS total_time
    FROM tasks LEFT JOIN logs ON tasks.task_id = logs.task_id
    WHERE task_deleted = 0
    AND (
    	log_deleted = 0
    	OR
    	log_deleted IS NULL
    )
    GROUP BY logs.task_id;

    Producing:

    task_text log_count total_time
    a logless task 1 NULL
    Get me a beer. 2 240
    Something ridiculous. 1 900

    As you can see the logless task is back and the values for Get me a beer are accurate.

    Related posts:

    1. Conditions on Count or Sum in MySQL When selecting count, sum, or some other aggregate function, the...
    2. Tips for MySQL to PostgreSQL Switch If you’ve decided to move a few tables from MySQL...
    3. Careful with Numeric Data Types in MySQL TINYINT can be set to length of 4, but that...
    ]]>
    http://codespatter.com/2008/02/19/how-to-break-a-mysql-left-join/feed/ 23
    MySQL Fun http://codespatter.com/2007/05/04/mysql-fun/ http://codespatter.com/2007/05/04/mysql-fun/#comments Fri, 04 May 2007 06:27:16 +0000 Greg Allard http://codespatter.com/?p=4
  • Tips for MySQL to PostgreSQL Switch If you’ve decided to move a few tables from MySQL...
  • How to Break a MySQL Left Join Care must be taken when placing conditions on the results...
  • How To Use Triggers to Track Changes in MySQL Setting constraints and rules in the database is better than...
  • ]]>
    Trying to find a way to update a bunch of records in the request table by changing request_taken_time to a value in another table. The problem is, there are multiple records in the history table so there will be duplicate queries and it may not get the most recent time which is what is wanted. I can’t use group by on an update and if I can’t use group by then I can’t use the MAX function. So it will basically update the records multiple times and won’t get the highest value like I need it to.

    Related posts:

    1. Tips for MySQL to PostgreSQL Switch If you’ve decided to move a few tables from MySQL...
    2. How to Break a MySQL Left Join Care must be taken when placing conditions on the results...
    3. How To Use Triggers to Track Changes in MySQL Setting constraints and rules in the database is better than...
    ]]>
    http://codespatter.com/2007/05/04/mysql-fun/feed/ 8