Update multiple rows with different values in a single SQL query
Asked Answered
H

8

45

I have a SQLite database with table myTable and columns id, posX, posY. The number of rows changes constantly (might increase or decrease). If I know the value of id for each row, and the number of rows, can I perform a single SQL query to update all of the posX and posY fields with different values according to the id?

For example:

---------------------
myTable:

id   posX    posY

1      35     565
3      89     224
6      11     456
14     87     475
---------------------

SQL query pseudocode:

UPDATE myTable SET posX[id] = @arrayX[id], posY[id] = @arrayY[id] "

@arrayX and @arrayY are arrays which store new values for the posX and posY fields.

If, for example, arrayX and arrayY contain the following values:

arrayX = { 20, 30, 40, 50 }
arrayY = { 100, 200, 300, 400 }

... then the database after the query should look like this:

---------------------
myTable:

id   posX    posY

1      20     100
3      30     200
6      40     300
14     50     400
---------------------

Is this possible? I'm updating one row per query right now, but it's going to take hundreds of queries as the row count increases. I'm doing all this in AIR by the way.

Hardened answered 19/7, 2012 at 15:12 Comment(2)
Maybe this can help dba.stackexchange.com/questions/17590/…Trometer
Related for MySQL: dba.stackexchange.com/questions/69269/…Election
K
44

There's a couple of ways to accomplish this decently efficiently.

First -
If possible, you can do some sort of bulk insert to a temporary table. This depends somewhat on your RDBMS/host language, but at worst this can be accomplished with a simple dynamic SQL (using a VALUES() clause), and then a standard update-from-another-table. Most systems provide utilities for bulk load, though

Second -
And this is somewhat RDBMS dependent as well, you could construct a dynamic update statement. In this case, where the VALUES(...) clause inside the CTE has been created on-the-fly:

WITH Tmp(id, px, py) AS (VALUES(id1, newsPosX1, newPosY1), 
                               (id2, newsPosX2, newPosY2),
                               ......................... ,
                               (idN, newsPosXN, newPosYN))

UPDATE TableToUpdate SET posX = (SELECT px
                                 FROM Tmp
                                 WHERE TableToUpdate.id = Tmp.id),
                         posY = (SELECT py
                                 FROM Tmp
                                 WHERE TableToUpdate.id = Tmp.id)


WHERE id IN (SELECT id
             FROM Tmp)

(According to the documentation, this should be valid SQLite syntax, but I can't get it to work in a fiddle)

Kitty answered 19/7, 2012 at 15:47 Comment(7)
Could you put your code into the answer itself? Right now if that link dies, your answer becomes next to useless.Acquiescent
@GeorgeStocker - ah, thanks. Somehow I missed the original tag, and my example statement wasn't valid in the target RDBMS. Hopefully it would work now...Kitty
Looks good! One small correction though, since in the question the name of the column is "id" it should also appear in the answer. Replace "i" with "id" everywhere.Baseburner
I would also use JOIN effectively here UPDATE TableToUpdate, Tmp SET posX = Tmp.px posY = Tmp.py LEFT JOIN ON (TableToUpdate.id = Tmp.id)Trier
@Kitty I'd like to use this with iOS but I'm getting the following error ... library routine called out of sequence :( any ideas?Phebephedra
@Phebephedra - that sounds like something on the iOS side. Normally I'd expect a problem with the statement to generate a syntax error somewhere.Kitty
Thanks. I don't the iOS sql library supports cte 🙁Phebephedra
M
26

One way: SET x=CASE..END (any SQL)

Yes, you can do this, but I doubt that it would improve performances, unless your query has a real large latency.

If the query is indexed on the search value (e.g. if id is the primary key), then locating the desired tuple is very, very fast and after the first query the table will be held in memory.

So, multiple UPDATEs in this case aren't all that bad.

If, on the other hand, the condition requires a full table scan, and even worse, the table's memory impact is significant, then having a single complex query will be better, even if evaluating the UPDATE is more expensive than a simple UPDATE (which gets internally optimized).

In this latter case, you could do:

 UPDATE table SET posX=CASE
      WHEN id=id[1] THEN posX[1]
      WHEN id=id[2] THEN posX[2]
      ...
      ELSE posX END [, posY = CASE ... END]
 WHERE id IN (id[1], id[2], id[3]...);

The total cost is given more or less by: NUM_QUERIES * ( COST_QUERY_SETUP + COST_QUERY_PERFORMANCE ). This way, you knock down on NUM_QUERIES (from N separate id's to 1), but COST_QUERY_PERFORMANCE goes up (about 3x in MySQL 5.28; haven't yet tested in MySQL 8).

Otherwise, I'd try with indexing on id, or modifying the architecture.

This is an example with PHP, where I suppose we have a condition that already requires a full table scan, and which I can use as a key:

// Multiple update rules 
$updates = [
   "fldA='01' AND fldB='X'" => [ 'fldC' => 12, 'fldD' => 15 ],
   "fldA='02' AND fldB='X'" => [ 'fldC' => 60, 'fldD' => 15 ],
   ...
];

The fields updated in the right hand expressions can be one or many, must always be the same (always fldC and fldD in this case). This restriction can be removed, but it would require a modified algorithm.

I can then build the single query through a loop:

$where = [ ];
$set   = [ ];
foreach ($updates as $when => $then) {
    $where[] = "({$when})";
    foreach ($then as $fld => $value) {
       if (!array_key_exists($fld, $set)) {
           $set[$fld] = [ ];
       }
       $set[$fld][] = $value;
    }
}

$set1 = [ ];
foreach ($set as $fld => $values) {
    $set2 = "{$fld} = CASE";
    foreach ($values as $i => $value) {
        $set2 .= " WHEN {$where[$i]} THEN {$value}";
    }
    $set2 .= ' END';
    $set1[] = $set2;
}

// Single query
$sql  = 'UPDATE table SET '
      . implode(', ', $set1)
      . ' WHERE '
      . implode(' OR ', $where);

Another way: ON DUPLICATE KEY UPDATE (MySQL)

In MySQL I think you could do this more easily with a multiple INSERT ON DUPLICATE KEY UPDATE, assuming that id is a primary key keeping in mind that nonexistent conditions ("id = 777" with no 777) will get inserted in the table and maybe cause an error if, for example, other required columns (declared NOT NULL) aren't specified in the query:

INSERT INTO tbl (id, posx, posy, bazinga)
     VALUES (id1, posY1, posY1, 'DELETE'),
     ...
ON DUPLICATE KEY SET posx=VALUES(posx), posy=VALUES(posy);

DELETE FROM tbl WHERE bazinga='DELETE';

The 'bazinga' trick above allows to delete any rows that might have been unwittingly inserted because their id was not present (in other scenarios you might want the inserted rows to stay, though).

For example, a periodic update from a set of gathered sensors, but some sensors might not have been transmitted:

INSERT INTO monitor (id, value)
VALUES (sensor1, value1), (sensor2, 'N/A'), ...
ON DUPLICATE KEY UPDATE value=VALUES(value), reading=NOW();

(This is a contrived case, it would probably be more reasonable to LOCK the table, UPDATE all sensors to N/A and NOW(), then proceed with INSERTing only those values we do have).

A third way: CTE (Any SQL)

This is conceptually almost the same as the INSERT MySQL trick. As written, it works in PostgreSQL 9.6:

WITH updated(id, posX, posY) AS (VALUES
    (id1, posX1, posY1), 
    (id2, posX2, posY2),
    ...
)
UPDATE myTable
    SET 
    posX = updated.posY,
    posY = updated.posY
FROM updated
WHERE (myTable.id = updated.id);
Meissen answered 19/7, 2012 at 15:33 Comment(3)
nice example! unfortunately, as I said, the number of rows is going to change, therefore the number of "WHEN" operators would need to be altered constantly.Hardened
If you're really keen on doing this, you could build the query dynamically. The query broken in five or six pieces that are reassembled, three of them (first and second CASE, and the IN clause) built inside a single WHILE loop. You ought to do something like this anyway, if the values change...Meissen
Can you expand on how having a case statement in the SET causes COST_QUERY_PERFORMANCE to go up?Chrysoberyl
M
7

Something like this might work for you:

"UPDATE myTable SET ... ;
 UPDATE myTable SET ... ;
 UPDATE myTable SET ... ;
 UPDATE myTable SET ... ;"

If any of the posX or posY values are the same, then they could be combined into one query

UPDATE myTable SET posX='39' WHERE id IN('2','3','40');
Misty answered 19/7, 2012 at 15:25 Comment(1)
you can prepare statements like this in a text editor and paste them directly into command line. works well if you don't have too many updates to make.Heist
I
2

In recent versions of SQLite (beginning from 3.24.0 from 2018) you can use the UPSERT clause. Assuming only existing datasets are updated having a unique id column, you can use this approach, which is similar to @LSerni's ON DUPLICATE suggestion:

INSERT INTO myTable (id, posX, posY) VALUES
  ( 1, 35, 565),
  ( 3, 89, 224),
  ( 6, 11, 456),
  (14, 87, 475)
ON CONFLICT (id) DO UPDATE SET
  posX = excluded.posX, posY = excluded.posY
Immoderacy answered 21/2, 2022 at 15:49 Comment(1)
One issue with ON CONFLICT is that it doesn't work if you want to omit a NOT NULL column that doesn't need updating: #48817129Election
B
1

I could not make @Clockwork-Muse work actually. But I could make this variation work:

WITH Tmp AS (SELECT * FROM (VALUES (id1, newsPosX1, newPosY1), 
                                   (id2, newsPosX2, newPosY2),
                                   ......................... ,
                                   (idN, newsPosXN, newPosYN)) d(id, px, py))

UPDATE t

SET posX = (SELECT px FROM Tmp WHERE t.id = Tmp.id),
    posY = (SELECT py FROM Tmp WHERE t.id = Tmp.id)

FROM TableToUpdate t

I hope this works for you too!

Braggart answered 16/12, 2019 at 22:47 Comment(0)
E
0

Use a comma ","

eg: 
UPDATE my_table SET rowOneValue = rowOneValue + 1, rowTwoValue  = rowTwoValue + ( (rowTwoValue / (rowTwoValue) ) + ?) * (v + 1) WHERE value = ?
Egotism answered 4/11, 2020 at 23:5 Comment(1)
Doesn´t make sense to me as you would need multiple where conditions to match each row. Could you please elaborate?Sadirah
W
0

To update a table with different values for a column1, given values on column2, one can do as follows for SQLite:

"UPDATE table SET column1=CASE WHEN column2<>'something' THEN 'val1' ELSE 'val2' END"
Whirly answered 6/4, 2021 at 20:44 Comment(0)
G
-12

Try with "update tablet set (row='value' where id=0001'), (row='value2' where id=0002'), ...

Grayling answered 4/11, 2013 at 10:5 Comment(2)
Did you actually try this?Illa
This would be great, and kinda what i am looking for..Adrenaline

© 2022 - 2024 — McMap. All rights reserved.