web-dev-qa-db-fra.com

Mettre à jour plusieurs lignes avec des valeurs différentes dans une seule requête SQL

J'ai une base de données SQLite avec la table myTable et les colonnes id, posX, posY. Le nombre de lignes change constamment (peut augmenter ou diminuer). Si je connais la valeur de id pour chaque ligne et le nombre de lignes, puis-je effectuer une seule requête SQL pour mettre à jour tous les champs posX et posY avec différents valeurs selon l'id?

Par exemple:

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

id   posX    posY

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

Pseudocode de requête SQL:

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

@arrayX et @arrayY sont des tableaux qui stockent de nouvelles valeurs pour les champs posX et posY.

Si, par exemple, arrayX et arrayY contiennent les valeurs suivantes:

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

... alors la base de données après la requête devrait ressembler à ceci:

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

id   posX    posY

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

Est-ce possible? Je mets à jour une ligne par requête en ce moment, mais cela va prendre des centaines de requêtes à mesure que le nombre de lignes augmente. Je fais tout ça dans AIR d'ailleurs.

26
astralmaster

Il y a deux façons d'accomplir cela décemment efficacement.

Première -
Si possible, vous pouvez effectuer une sorte d'insertion groupée dans une table temporaire. Cela dépend quelque peu de votre langage RDBMS/Host, mais au pire, cela peut être accompli avec un SQL dynamique simple (en utilisant une clause VALUES()), puis une mise à jour standard à partir d'une autre table. La plupart des systèmes fournissent des utilitaires pour le chargement en masse, bien que

Seconde -
Et cela dépend aussi du SGBDR, vous pouvez construire une instruction de mise à jour dynamique. Dans ce cas, où la clause VALUES(...) à l'intérieur du CTE a été créée à la volée:

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)

(Selon la documentation, cette devrait être une syntaxe SQLite valide, mais je ne peux pas la faire fonctionner dans un violon)

25
Clockwork-Muse

Oui, vous pouvez le faire, mais je doute que cela améliorerait les performances, à moins que votre requête n'ait une très grande latence.

Vous pourriez faire:

 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]...);

Le coût total est donné plus ou moins par: NUM_QUERIES * (COST_QUERY_SETUP + COST_QUERY_PERFORMANCE). De cette façon, vous abaissez un peu sur NUM_QUERIES, mais COST_QUERY_PERFORMANCE augmente considérablement. Si COST_QUERY_SETUP est vraiment énorme (par exemple, vous appelez un service réseau qui est vraiment lent), alors, oui, vous pourriez toujours vous retrouver en tête.

Sinon, j'essaierais d'indexer sur id ou de modifier l'architecture.

Dans MySQL, je pense que vous pourriez le faire plus facilement avec plusieurs INSERT ON DUPLICATE KEY UPDATE (mais je ne suis pas sûr, jamais essayé).

17
LSerni

Quelque chose comme ça pourrait fonctionner pour vous:

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

Si l'une des valeurs posX ou posY sont identiques, elles peuvent être combinées en une seule requête

UPDATE myTable SET posX='39' WHERE id IN('2','3','40');
5
Brian