Karl Rixon

Update Multiple Rows With Different Values and a Single SQL Query

with 36 comments

Sometimes you may need to update multiple rows of data in a table. This is no problem a lot of the time, as the fields to update may all need to be changed to the same value, in which case you might run a query like the following.

1
UPDATE mytable SET myfield = 'value' WHERE other_field = 'other_value';

But what about if you want to update multiple rows but you need the field set to a different value for each row? For example, maybe you have a table of categories with a field to store their display order. How would you update the display order field for each category when the order changes? Most often you will see people just run multiple queries. For example, if you are using PHP you might think to do something like this:

1
2
3
4
foreach ($display_order as $id => $ordinal) {
    $sql = "UPDATE categories SET display_order = $ordinal WHERE id = $id";
    mysql_query($sql);
}

Of course this will work fine, but queries in loops make me start to sweat nervously. It’s all to easy to start hammering the database with a crazy number of queries. Fortunately there is a better way! The SQL is a little more complex, but it is possible to achieve the same result using only one query. The syntax is as follows.

1
2
3
4
5
6
7
UPDATE mytable
    SET myfield = CASE other_field
        WHEN 1 THEN 'value'
        WHEN 2 THEN 'value'
        WHEN 3 THEN 'value'
    END
WHERE id IN (1,2,3)

Mapping this to the categories example, we get the following query.

1
2
3
4
5
6
7
UPDATE categories
    SET display_order = CASE id
        WHEN 1 THEN 3
        WHEN 2 THEN 4
        WHEN 3 THEN 5
    END
WHERE id IN (1,2,3)

This is fairly simple to understand. Rather than setting a field to a particular value, a CASE operator is used to determine which of a set of values is used based on a given condition, in this case if the value of the id field matches the specified id.

The WHERE clause is not needed as such in that it doesn’t affect the logic of the query, but it does improve performance by ensuring that the logic is only applied to the smallest number of rows possible. In this example only 3 rows are being updated, and the WHERE clause ensures that only those 3 rows are tested. Without it the every row in the table would be tested (unnecessarily, since they will never match).

What about when you need to update multiple fields? This is easily done just by adding another CASE block.

1
2
3
4
5
6
7
8
9
10
11
12
UPDATE categories
    SET display_order = CASE id
        WHEN 1 THEN 3
        WHEN 2 THEN 4
        WHEN 3 THEN 5
    END,
    title = CASE id
        WHEN 1 THEN 'New Title 1'
        WHEN 2 THEN 'New Title 2'
        WHEN 3 THEN 'New Title 3'
    END
WHERE id IN (1,2,3)

Now this is all well and good, but the real beauty of this comes when the technique is combined with a scripting language such as PHP in order to build these queries dynamically. Let’s examine one technique for doing this, using the category ordering example.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// An array containing the category ids as keys and the new positions as values
$display_order = array(
    1 => 4,
    2 => 1,
    3 => 2,
    4 => 3,
    5 => 9,
    6 => 5,
    7 => 8,
    8 => 9
);

$ids = implode(',', array_keys($display_order));
$sql = "UPDATE categories SET display_order = CASE id ";
foreach ($display_order as $id => $ordinal) {
    $sql .= sprintf("WHEN %d THEN %d ", $id, $ordinal);
}
$sql .= "END WHERE id IN ($ids)";
echo $sql;

Now in this particular example, only 8 rows are being updated, but 7 queries have been trimed, which is not insignificant. Apply this technique to a situation where hundreds or thousands of rows have to be updated and you can imagine the benefits it will have.

Written by Karl

April 30th, 2009 at 12:22 pm

Posted in SQL

36 Responses to 'Update Multiple Rows With Different Values and a Single SQL Query'

Subscribe to comments with RSS or TrackBack to 'Update Multiple Rows With Different Values and a Single SQL Query'.

  1. This led me to believe I might be able to use a single update statement to increment a value in a table. Monique Database

  2. Greetings!

    Great post. However, my query is showing errors when I try to run it. Can you post what the final query is supposed to look like? Here is what mine is looking like and the error mysql is giving me.

    UPDATE gallery SET `order` = CASE id WHEN 11 THEN 1 WHEN 10 THEN 2 WHEN 12 THEN 3 WHEN 13 THEN 4 WHEN
    7 THEN 5 WHEN 14 THEN 6 WHEN 15 THEN 7 WHEN 16 THEN 8 WHEN 17 THEN 9 WHEN 18 THEN 10 WHEN 19 THEN 11
    WHEN 20 THEN 12 WHERE id IN (11,10,12,13,7,14,15,16,17,18,19,20)

    #1064 – You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ‘WHERE id IN (11,10,12,13,7,14,15,16,17,18,19,20)’ at line 3

    Thank you kind sir.

    Josh

    13 May 09 at 1:32 am

  3. Hi Josh

    Sorry, I missed out the ‘END’ which closes the CASE in the php example. It’s now been updated. Thanks for letting me know!

    BTW the query should look like this:

    UPDATE gallery SET `order` = CASE id WHEN 11 THEN 1 WHEN 10 THEN 2 WHEN 12 THEN 3 WHEN 13 THEN 4 WHEN
    7 THEN 5 WHEN 14 THEN 6 WHEN 15 THEN 7 WHEN 16 THEN 8 WHEN 17 THEN 9 WHEN 18 THEN 10 WHEN 19 THEN 11
    WHEN 20 THEN 12 END WHERE id IN (11,10,12,13,7,14,15,16,17,18,19,20)

    Karl

    13 May 09 at 11:32 am

  4. Karl beautiful sql big tnx.
    Save a lots of time.

    darxx

    20 Jun 09 at 9:28 pm

  5. Thanks for a very useful tutorial. Will help to hugely cut down my bandwidth and db queries :)

    One thing though, your fifth code box should read:

    UPDATE categories
    SET display_order = CASE id
    WHEN 1 THEN 3
    WHEN 2 THEN 4
    WHEN 3 THEN 5
    END,
    title = CASE id
    WHEN 1 THEN ‘New Title 1′
    WHEN 2 THEN ‘New Title 2′
    WHEN 3 THEN ‘New Title 3′
    END
    WHERE id IN (1,2,3)

    Note the lack of the second “SET”. This caused me loads of head scratching but found the solution in the end. Hope this helps, and keep up the good work.

    Cheers

    Stephen McIntyre

    15 Jul 09 at 9:03 pm

  6. Hi Stephen,

    Thanks for catching that, I’ve updated the post.

    Karl

    16 Jul 09 at 11:57 am

  7. Thanks dude, much appreciated. Great post ;-)

    oneafrikan

    26 Jul 09 at 5:30 pm

  8. (Thnak’s Thnak’s Thnak’s Thnak’s…….) exp infinite.I am very happy to have this..

    asif

    27 Jul 09 at 11:28 am

  9. Wow, excellent article. It’s things like this that keep my interest in web development – I really like doing things more efficiently. I personally would have stopped at the for loop :)

    Many Thanks!

    Rob

    25 Aug 09 at 6:21 pm

  10. Thank you so much dude.,, your post solved my problem.!!!

    Jet Asor

    2 Sep 09 at 6:04 pm

  11. Hi everybody,
    thanx karl for this smart hint.very helpful

  12. Is there any other option to do the same without using CASE? if so please mail me the technique…. Tx in adv

    Yogan

    13 Jan 10 at 10:12 am

  13. I used to follow the ‘foreach’ in similar situatioins. Now got a useful hint. Thanks a lot..

    Jomon

    17 Jan 10 at 2:18 pm

  14. When working with large tables of data where there is heavy system load, this update with one query approach can cause very serious performance issues and will generally lock a table far longer than is reasonable for other users. I have had many a query like this killed by my sysadmin because it has caused system load to go through the roof on a table with only about 500,000 rows.

    Mikey

    20 Jan 10 at 2:27 am

  15. [...] display_order = CASE id WHEN 1 THEN 3 WHEN 2 THEN 4 WHEN 3 THEN 5 END WHERE id IN (1,2,3)Source: http://www.karlrixon.co.uk/articles/sql/update-multiple-rows-with-different-values-and-a-single-sql-…Use join instead of subqueriesAs a programmer, subqueries are something that you can be tempted to [...]

  16. Hi, Mikey you says:
    “When working with large tables of data where there is heavy system load, this update with one query approach can cause very serious performance issues and will generally lock a table far longer than is reasonable for other users. I have had many a query like this killed by my sysadmin because it has caused system load to go through the roof on a table with only about 500,000 rows.”

    What is solutions….

    Thanks’s (sorry for my english)

    Eduardo

    17 Mar 10 at 10:52 am

  17. Hi Eduardo

    One option would be to run updates in batches via cron. This means that you will not lock the table for an excessive amount of time.

    Karl

    17 Mar 10 at 1:57 pm

  18. Excellent post and thanks a lot

  19. can you let me know whether i can update based on values in 2 or 3 columns in the same table for this scenario?

  20. Thank you a lot. I really needed this.

    Jere

    28 Apr 10 at 7:46 pm

  21. That’s very cool indeed.
    I’ve used before a trick to do something similar with selecting only once a group of values assigned to different return variables but could not figure out this could work with updates too. :)
    Here’s the select thing:

    1
    2
    3
    4
    5
    6
    7
    SELECT
    SUM(CASE WHEN status = 'originated' THEN 1 ELSE 0 END) AS originated,
    SUM(CASE WHEN status IN ('calling','connected') THEN 1 ELSE 0 END) AS online,
    SUM(CASE WHEN status = 'connected' THEN 1 ELSE 0 END) AS connected,
    SUM(CASE WHEN status = 'new' THEN 1 ELSE 0 END) AS `new`,
    SUM(CASE WHEN status = 'calling' THEN 1 ELSE 0 END) AS calling
    FROM callrequest"

    The result will be the sum of actual statuses returned in the “columns” specified with AS.

    mogyiman

    29 Apr 10 at 9:17 pm

  22. Hi !

    Great tips for the update !

    I tried to use this with a form send by post and i com with that :

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    $res = mysql_query("SELECT COUNT(*) FROM rules WHERE subtype = 'upload' AND type = '".$_GET['tab']."' ") or die(mysql_error());
       $arr = mysql_fetch_row($res);
       $count = $arr[0];
       
       $rulesarray = array();
       
       for ($i=0; $i < $count; $i++) {
          $rulearray[$_POST['ruleid'][$i]] = $_POST['rule'][$i];
       }
           
       $ids = implode(',', array_keys($rulearray));
       $sql = "UPDATE rules SET text = CASE id";
       foreach ($rulearray as $id => $text) {
          $sql .= sprintf("WHEN %d THEN '%s'", $id, sqlesc($text));
       }
       $sql .= "END WHERE id IN ($ids)";
       //echo $sql;
           
       mysql_query($sql) or die(mysql_error());;

    And it works perfectly ! ^^

    Thanks a lot, before i updated all the rows and i start change my update statement like this one ^^

    Kite

    3 May 10 at 11:47 am

  23. Thanks. I am doing the jQuery sortable so my IDs of the records I want to update are the values in my array. The array index is the ordering (keys). So I swapped the values in WHEN THEN

    1
    2
    3
    4
    5
    6
    7
    $sql = "UPDATE `listitems` SET `position` = CASE `id`";
    foreach ($_GET['listItem'] as $position => $id) {
        $sql .= " WHEN ".$id." THEN ".$position;
        $ids[] = $id;
    }
    $ids = implode(",",$ids);
    $sql .= " END WHERE `id` IN ($ids)";

    dralezero

    16 May 10 at 5:12 am

  24. Excellent post!!!

    Perfect Solution for SQL Query…

    Great post

    Feroz

    20 Jun 10 at 7:51 pm

  25. I’m also doing the jQuery sortable.

    I was going to implement the method described here:
    http://le-gall.net/pierrick/blog/index.php/2007/11/29/108-mysql-bulk-update-with-talend-open-studio

    but fortunately I came across this article. The solution described here is much simpler to implement and as I’ve tested myself, the query is very efficient, at least for me. Thank you very much for the post.

    Tacaza

    26 Jun 10 at 5:44 pm

  26. Anyone know how to program this exact same thing using C# for ASP.NET and maybe ACCESS DB for now? :)

    Colin

    28 Jun 10 at 4:25 pm

  27. great post, thank you.

    bruno negrao

    29 Jun 10 at 6:44 pm

  28. Not Working.
    Using MS Access

    UPDATE navItems
    SET navItems.pos = CASE navItems.pos WHEN 10 THEN 2 WHEN 25 THEN 10 WHEN 24 THEN 13 ELSE 0 END
    WHERE (pos IN (10, 25, 24))

    Receiving a syntax error.

    Any ideas?

    Kirk

    Kirk

    30 Jul 10 at 12:47 am

  29. Kirk: your query doesn’t work because your CASE statement is evaluating the same field you are trying to update.

    zflag

    13 Aug 10 at 7:39 pm

  30. The use of the CASE statement to update multiple records only works, if you’re updating less than 255 rows – there is a limit on the number of CASE statements you can use.
    So, if you have more than 255 rows to update, then just write it as multiple queries, using the very first syntax described above.

    Jay

    2 Sep 10 at 5:01 pm

  31. @Jay

    The limit in the number of case statements does not mean the number of rows which can be updated is limited to 255 – the two factors are not necessarily related. You might have 10 case statements which could match 100000 rows each for example. If you are trying to target a single row per case statement then you may have an issue however. If this is the case you would probably be better off moving the conditions into their own table and joining to achieve the same thing:

    1
    2
    3
    4
    5
    category_order_helper
    category_id  | display_order
    -------------+----------------
    1            | 6
    2            | 4
    1
    2
    3
    4
    UPDATE categories
    SET display_order = h.display_order
    INNER JOIN category_order_helper AS h
    ON categories.id = h.category_id

    It requires a couple of extra queries (a TRUNCATE and an INSERT), but it avoids the case statement limit.

    Karl

    2 Sep 10 at 6:31 pm

Leave a Reply