SQL met à jour les champs d'une table à partir des champs d'une autre

124

J'ai deux tables:

A [ID, column1, column2, column3]
B [ID, column1, column2, column3, column4]

Asera toujours un sous-ensemble de B(ce qui signifie que toutes les colonnes de Asont également incluses B).

Je souhaite mettre à jour un enregistrement avec un IDin spécifique Bavec leurs données de Apour toutes les colonnes de A. Cela IDexiste à la fois dans Aet B.

Existe-t-il une UPDATEsyntaxe ou tout autre moyen de le faire sans spécifier les noms de colonne, en disant simplement "définir toutes les colonnes de A" ?

J'utilise PostgreSQL, donc une commande non standard spécifique est également acceptée (mais pas préférée).

Nir
la source
Je pense que c'est ce que vous voulez faire, dba.stackexchange.com/a/58383
-0

Réponses:

234

Vous pouvez utiliser la clause FROM non standard .

UPDATE b
SET column1 = a.column1,
  column2 = a.column2,
  column3 = a.column3
FROM a
WHERE a.id = b.id
AND b.id = 1
Scott Bailey
la source
9
La question est de savoir comment le faire sans spécifier tous les noms de colonnes. (Et je le suis aussi.)
indice
2
Je suis d'accord avec @cluesque, mais cette réponse est un excellent moyen d'utiliser les valeurs d'une colonne d'une table comme table de recherche pour remplacer les valeurs d'une colonne d'une autre table (voir SO 21657475 ), donc +1 ...
Victoria Stuart
1
Pourquoi b.id = 1 est-il nécessaire?
YasirAzgar
1
@YasirAzgar le b.id = 1 est de limiter les lignes de b mises à jour. Sinon, nous mettrions à jour chaque ligne du tableau. Parfois, c'est peut-être ce que vous voulez. Mais la question initiale était de mettre à jour une ligne spécifique en b.
Scott Bailey
C'est ce dont j'avais besoin pour mon problème particulier: mettre à jour la colonne d'une table avec des valeurs d'une autre colonne nommée différemment.
muad-dweeb
49

La question est ancienne mais je sentais que la meilleure réponse n'avait pas encore été donnée.

Existe-t-il une UPDATEsyntaxe ... sans spécifier les noms des colonnes ?

Solution générale avec SQL dynamique

Vous n'avez pas besoin de connaître les noms de colonnes, à l'exception de certaines colonnes uniques à rejoindre ( iddans l'exemple). Fonctionne de manière fiable pour tous les cas d'angle possibles auxquels je peux penser.

Ceci est spécifique à PostgreSQL. Je construis du code dynamique basé sur le schéma_information , en particulier la table information_schema.columns, qui est définie dans la norme SQL et la plupart des grands SGBDR (sauf Oracle) l'ont. Mais une DOinstruction avec du code PL / pgSQL exécutant du SQL dynamique est une syntaxe PostgreSQL totalement non standard.

DO
$do$
BEGIN

EXECUTE (
SELECT
  'UPDATE b
   SET   (' || string_agg(        quote_ident(column_name), ',') || ')
       = (' || string_agg('a.' || quote_ident(column_name), ',') || ')
   FROM   a
   WHERE  b.id = 123
   AND    a.id = b.id'
FROM   information_schema.columns
WHERE  table_name   = 'a'       -- table name, case sensitive
AND    table_schema = 'public'  -- schema name, case sensitive
AND    column_name <> 'id'      -- all columns except id
);

END
$do$;

En supposant une colonne correspondante bpour chaque colonne de a, mais pas l'inverse. bpeut avoir des colonnes supplémentaires.

WHERE b.id = 123 est facultatif, pour mettre à jour une ligne sélectionnée.

SQL Fiddle.

Réponses connexes avec plus d'explications:

Solutions partielles avec SQL brut

Avec liste des colonnes partagées

Vous devez toujours connaître la liste des noms de colonnes que les deux tables partagent. Avec un raccourci de syntaxe pour mettre à jour plusieurs colonnes - plus court que ce que les autres réponses suggèrent jusqu'à présent dans tous les cas.

UPDATE b
SET   (  column1,   column2,   column3)
    = (a.column1, a.column2, a.column3)
FROM   a
WHERE  b.id = 123    -- optional, to update only selected row
AND    a.id = b.id;

SQL Fiddle.

Cette syntaxe a été introduite avec Postgres 8.2 en 2006, bien avant que la question ne soit posée. Détails dans le manuel.

En relation:

Avec la liste des colonnes dans B

Si toutes les colonnes de Asont définies NOT NULL(mais pas nécessairement B),
et que vous connaissez les noms de colonnes de B(mais pas nécessairement A).

UPDATE b
SET   (column1, column2, column3, column4)
    = (COALESCE(ab.column1, b.column1)
     , COALESCE(ab.column2, b.column2)
     , COALESCE(ab.column3, b.column3)
     , COALESCE(ab.column4, b.column4)
      )
FROM (
   SELECT *
   FROM   a
   NATURAL LEFT JOIN  b -- append missing columns
   WHERE  b.id IS NULL  -- only if anything actually changes
   AND    a.id = 123    -- optional, to update only selected row
   ) ab
WHERE b.id = ab.id;

Le NATURAL LEFT JOINjoint une ligne à partir de blaquelle toutes les colonnes du même nom contiennent les mêmes valeurs. Nous n'avons pas besoin d'une mise à jour dans ce cas (rien ne change) et pouvons éliminer ces lignes au début du processus ( WHERE b.id IS NULL).
Nous devons encore trouver une ligne correspondante, donc b.id = ab.iddans la requête externe.

db <> violon ici
Ancien sqlfiddle.

Il s'agit du SQL standard à l' exception de la FROMclause .
Cela fonctionne quelle que soit la colonne réellement présente dans A, mais la requête ne peut pas faire la distinction entre les valeurs NULL réelles et les colonnes manquantes dans A, elle n'est donc fiable que si toutes les colonnes de Asont définies NOT NULL.

Il existe plusieurs variantes possibles, en fonction de ce que vous savez sur les deux tables.

Erwin Brandstetter
la source
La puissance de SQL! Juste remarqué quand vous ajoutez des parenthèses dans la clause set ( SET (column1) = (a.column)) Postgres le traitera comme un autre type de mise à jour et donnera une erreur comme ceci:source for a multiple-column UPDATE item must be a sub-SELECT or ROW() expression
Edgar Ortega
26

Je travaille avec la base de données IBM DB2 depuis plus de dix ans et j'essaie maintenant d'apprendre PostgreSQL.

Il fonctionne sur PostgreSQL 9.3.4, mais ne fonctionne pas sur DB2 10.5:

UPDATE B SET
     COLUMN1 = A.COLUMN1,
     COLUMN2 = A.COLUMN2,
     COLUMN3 = A.COLUMN3
FROM A
WHERE A.ID = B.ID

Remarque: Le problème principal est une cause FROM qui n'est pas prise en charge dans DB2 et pas non plus dans ANSI SQL.

Cela fonctionne sur DB2 10.5, mais ne fonctionne PAS sur PostgreSQL 9.3.4:

UPDATE B SET
    (COLUMN1, COLUMN2, COLUMN3) =
               (SELECT COLUMN1, COLUMN2, COLUMN3 FROM A WHERE ID = B.ID)

ENFIN! Il fonctionne à la fois sur PostgreSQL 9.3.4 et DB2 10.5:

UPDATE B SET
     COLUMN1 = (SELECT COLUMN1 FROM A WHERE ID = B.ID),
     COLUMN2 = (SELECT COLUMN2 FROM A WHERE ID = B.ID),
     COLUMN3 = (SELECT COLUMN3 FROM A WHERE ID = B.ID)
Jochan
la source
3
Notez que les deuxième et troisième requêtes ne sont pas complètement équivalentes à la première. Si aucune ligne correspondante n'est trouvée dans B, la première instruction ne fait rien (la ligne d'origine reste inchangée), tandis que les deux autres écrasent les colonnes avec des valeurs NULL.
Erwin Brandstetter
7

C'est une aide précieuse. Le code

UPDATE tbl_b b
SET   (  column1,   column2,   column3)
    = (a.column1, a.column2, a.column3)
FROM   tbl_a a
WHERE  b.id = 1
AND    a.id = b.id;

fonctionne parfaitement.

a noté que vous avez besoin d'un crochet "" dans

From "tbl_a" a

pour le faire fonctionner.

user2493970
la source
5

Pas nécessairement ce que vous avez demandé, mais peut-être que l'utilisation de l'héritage postgres pourrait vous aider?

CREATE TABLE A (
    ID            int,
    column1       text,
    column2       text,
    column3       text
);

CREATE TABLE B (
    column4       text
) INHERITS (A);

Cela évite d'avoir à mettre à jour B.

Mais assurez-vous de lire tous les détails .

Sinon, ce que vous demandez n'est pas considéré comme une bonne pratique - les éléments dynamiques tels que les vues avec SELECT * ...sont déconseillés (car une telle commodité peut casser plus de choses que les choses d'aide), et ce que vous demandez serait équivalent pour la UPDATE ... SETcommande.

Déraison
la source
Je ne sais pas comment l'héritage résoudra cela. Voulez-vous dire ajouter un déclencheur de mise à jour pour A qui met également à jour B? Je ne veux pas synchroniser A avec B tout le temps, uniquement sur demande. Et dans ce cas, je ne peux pas utiliser les déclencheurs.
Nir
2
Oui, si ce n'est que dans certains cas, l'héritage ne fonctionnera pas et dans ce cas, je déconseille l'approche de requête dynamique. (il existe toujours des moyens d'y parvenir en utilisant des langages procéduraux postgres. également si vous souhaitez utiliser des déclencheurs, vous pouvez également les utiliser - en ajoutant un champ de synchronisation, par exemple en déclenchant le déclencheur uniquement lorsqu'il est défini).
Unreason
0

vous pouvez créer et exécuter SQL dynamique pour ce faire, mais ce n'est vraiment pas idéal

Daniel Brink
la source
J'y ai pensé. Je pensais pouvoir rendre ma requête compatible avec les modifications ultérieures des deux tables, mais SQL dynamique semble trop compliqué que de simplement spécifier tous les champs et d'oublier la compatibilité ascendante.
Nir
oui, ce sera compliqué, mais devrait être compatible avec les colonnes ultérieures ajoutées ou supprimées. Vous devrez d'abord effectuer une requête pour obtenir les noms de colonne des deux tables, puis faire correspondre les noms de colonne, puis écrire le SQL dynamique pour effectuer la mise à jour en fonction des noms de colonne correspondants. un projet amusant en fait :)
Daniel Brink
-4

Essayez de suivre

Update A a, B b, SET a.column1=b.column1 where b.id=1

EDITED: - Mettre à jour plus d'une colonne

Update A a, B b, SET a.column1=b.column1, a.column2=b.column2 where b.id=1
Salil
la source
Je ne comprends pas comment il copie colonne1, colonne2 et colonne3. Et j'ai besoin de mentionner explicitement column1.
Nir
Ça ne marche pas pour moi.
J'obtiens
1
Cette syntaxe non standard fonctionnerait pour UPDATEMySQL , mais elle n'est pas valide pour PostgreSQL.
Erwin Brandstetter