web-dev-qa-db-fra.com

Est-il possible d'effectuer plusieurs mises à jour avec une seule instruction SQL UPDATE?

Disons que j'ai une table tbl avec des colonnes id et title. Je dois changer toutes les valeurs de la colonne de titre:

  1. de "a-1" à "a1",
  2. de "a.1" à "a1",
  3. de "b-1" à "b1",
  4. de "b.1" à "b1".

En ce moment, j'effectue deux instructions UPDATE:

UPDATE tbl SET title='a1' WHERE title IN ('a-1', 'a.1')
UPDATE tbl SET title='b1' WHERE title IN ('b-1', 'b.1')

Ce n'est pas du tout un problème, si la table est petite et que l'instruction unique se termine en moins d'une seconde et que vous n'avez besoin que de quelques instructions pour exécuter.

Vous l'avez probablement invité - j'ai une énorme table à traiter (une déclaration se termine en environ 90 secondes), et j'ai un grand nombre de mises à jour à effectuer.

Alors, est-il possible de fusionner les mises à jour afin de ne scanner la table qu'une seule fois? Ou peut-être qu'il y a une meilleure façon de faire face à une situation comme celle-ci.

EDIT: Notez que les vraies données avec lesquelles je travaille et les modifications apportées aux données que je dois effectuer ne sont pas vraiment aussi simples - les chaînes sont plus longues et ne suivent aucun modèle (ce sont des données utilisateur, donc pas d'hypothèses peut être fait - cela peut être n'importe quoi).

28
Paulius

Dans un cas plus général, où il peut y avoir plusieurs centaines de mappages à chacune des nouvelles valeurs, vous créez un tableau séparé des anciennes et des nouvelles valeurs, puis vous l'utilisez dans l'instruction UPDATE. Dans un dialecte de SQL:

CREATE TEMP TABLE mapper (old_val CHAR(5) NOT NULL, new_val CHAR(5) NOT NULL);
...multiple inserts into mapper...
INSERT INTO mapper(old_val, new_val) VALUES('a.1', 'a1');
INSERT INTO mapper(old_val, new_val) VALUES('a-1', 'a1');
INSERT INTO mapper(old_val, new_val) VALUES('b.1', 'b1');
INSERT INTO mapper(old_val, new_val) VALUES('b-1', 'b1');
...etcetera...

UPDATE tbl
   SET title = (SELECT new_val FROM mapper WHERE old_val = tbl.title)
   WHERE title IN (SELECT old_val FROM mapper);

Les deux déclarations choisies sont cruciales. La première est une sous-requête corrélée (pas nécessairement rapide, mais plus rapide que la plupart des alternatives si la table de mappage a des milliers de lignes) qui extrait la nouvelle valeur de la table de mappage qui correspond à l'ancienne valeur. La seconde garantit que seules les lignes qui ont une valeur dans la table de mappage sont modifiées; ceci est crucial car sinon, le titre sera défini sur null pour ces lignes sans entrée de mappage (et ce sont les enregistrements qui étaient OK avant de commencer).

Pour quelques alternatives, les opérations CASE sont OK. Mais si vous avez des centaines, des milliers ou des millions de mappages à effectuer, vous risquez de dépasser les limites de la longueur de l'instruction SQL dans votre SGBD.

22
Jonathan Leffler

Vous pouvez utiliser une instruction et un certain nombre d'instructions de cas

update tbl
  set title = 
    case
      when title in ('a-1', 'a.1') then 'a1'
      when title in ('b-1', 'b.1') then 'b1'
      else title
    end

Bien sûr, cela entraînera une écriture sur chaque enregistrement, et avec les index, cela peut être un problème, vous pouvez donc filtrer uniquement les lignes que vous souhaitez modifier:

update tbl
  set title = 
    case
      when title in ('a-1', 'a.1') then 'a1'
      when title in ('b-1', 'b.1') then 'b1'
      else title
    end
where
  title in ('a.1', 'b.1', 'a-1', 'b-1')

Cela réduira le nombre d'écritures sur la table.

24
casperOne

En travaillant sur la réponse de Jonathan.

UPDATE tbl
   SET title = new_val
FROM mapper
WHERE title IN (SELECT old_val FROM mapper)
     AND mapper.old_val = tbl.title;

Sa version initiale nécessiterait un grand nombre de lectures de la table de mappage.

9
mrdenny

Si les transformations sont aussi simples que vos exemples, vous pouvez faire la mise à jour avec un peu de manipulation de chaîne:

UPDATE tbl 
SET title = left(title, 1) + right(title, 1) 
WHERE title IN ('a-1', 'a.1', 'b-1', 'b.1')

Est-ce que quelque chose comme ça fonctionnerait pour vous?

3
Matt Hamilton

Ou

   Update Table set 
     title = Replace(Replace(title, '.', ''), '-', '')
   Where title Like '[ab][.-]1'
0
Charles Bretana