Comment faire une mise à jour + rejoindre dans PostgreSQL?


508

Fondamentalement, je veux faire ceci:

update vehicles_vehicle v 
    join shipments_shipment s on v.shipment_id=s.id 
set v.price=s.price_per_vehicle;

Je suis à peu près sûr que cela fonctionnerait dans MySQL (mes antécédents), mais cela ne semble pas fonctionner dans PostgreSQL. L'erreur que j'obtiens est:

ERROR:  syntax error at or near "join"
LINE 1: update vehicles_vehicle v join shipments_shipment s on v.shi...
                                  ^

Il y a sûrement un moyen facile de le faire, mais je ne trouve pas la syntaxe appropriée. Alors, comment pourrais-je écrire ceci dans PostgreSQL?


5
La syntaxe de Postgres est différente: postgresql.org/docs/8.1/static/sql-update.html
Marc B

4
véhicules_véhicule, expéditions_expédition? C'est une convention de dénomination de table intéressante
CodeAndCats

3
@CodeAndCats Haha ... ça a l'air drôle non? Je pense que j'utilisais Django à l'époque, et les tables sont regroupées par fonctionnalité. Il y aurait donc eu une vue des vehicles_*tables, et quelques shipments_*tables.
mpen

Réponses:


777

La syntaxe UPDATE est:

[AVEC [RECURSIF] avec_query [, ...]]
UPDATE [ONLY] table [[AS] alias]
    SET {colonne = {expression | DEFAULT} |
          (colonne [, ...]) = ({expression | DEFAULT} [, ...])} [, ...]
    [FROM from_list]
    [O condition condition | OERE LE COURANT DE nom_curseur]
    [RETOUR * | expression_sortie [[AS] nom_sortie] [, ...]]

Dans votre cas, je pense que vous voulez ceci:

UPDATE vehicles_vehicle AS v 
SET price = s.price_per_vehicle
FROM shipments_shipment AS s
WHERE v.shipment_id = s.id 

Si la mise à jour repose sur une liste complète de jointures de table, celles-ci doivent-elles figurer dans la section UPDATE ou FROM?
ted.strauss

11
@ ted.strauss: le FROM peut contenir une liste de tables.
Mark Byers

140

Permettez-moi d'expliquer un peu plus par mon exemple.

Tâche: information correcte, où les abiturients (étudiants sur le point de quitter le secondaire) ont soumis des demandes à l'université plus tôt, qu'ils ont obtenu des certificats scolaires (oui, ils ont obtenu des certificats plus tôt, qu'ils n'ont été délivrés (à la date du certificat spécifiée). Donc, nous allons augmenter la date de soumission de la demande pour l'adapter à la date de délivrance du certificat.

Donc. prochaine déclaration de type MySQL:

UPDATE applications a
JOIN (
    SELECT ap.id, ab.certificate_issued_at
    FROM abiturients ab
    JOIN applications ap 
    ON ab.id = ap.abiturient_id 
    WHERE ap.documents_taken_at::date < ab.certificate_issued_at
) b
ON a.id = b.id
SET a.documents_taken_at = b.certificate_issued_at;

Devient comme PostgreSQL de cette manière

UPDATE applications a
SET documents_taken_at = b.certificate_issued_at         -- we can reference joined table here
FROM abiturients b                                       -- joined table
WHERE 
    a.abiturient_id = b.id AND                           -- JOIN ON clause
    a.documents_taken_at::date < b.certificate_issued_at -- Subquery WHERE

Comme vous pouvez le voir, JOINla ONclause de la sous-requête d'origine est devenue l'une des WHEREconditions, qui sont conjuguées à ANDd'autres, qui ont été déplacées de la sous-requête sans aucun changement. Et il n'est plus nécessaire de se JOINdéposer avec lui-même (comme c'était le cas dans la sous-requête).


16
Comment rejoindriez-vous une troisième table?
Growler

19
Vous venez JOINcomme d'habitude dans la FROMliste:FROM abiturients b JOIN addresses c ON c.abiturient_id = b.id
Envek

@Envek - Vous ne pouvez pas utiliser JOIN il hélas, je viens de vérifier. postgresql.org/docs/10/static/sql-update.html
Adrian Smith

3
@AdrianSmith, vous ne pouvez pas utiliser JOIN dans UPDATE lui-même, mais vous pouvez l'utiliser dans la from_listclause UPDATE (qui est l'extension SQL de PostgreSQL). Consultez également les remarques concernant la jointure des tableaux sur le lien que vous avez fourni.
Envek

@Envek - Ah, merci pour la clarification, ça m'a manqué.
Adrian Smith

130

La réponse de Mark Byers est optimale dans cette situation. Bien que dans des situations plus complexes, vous pouvez prendre la requête de sélection qui retourne les Rowid et les valeurs calculées et la joindre à la requête de mise à jour comme ceci:

with t as (
  -- Any generic query which returns rowid and corresponding calculated values
  select t1.id as rowid, f(t2, t2) as calculatedvalue
  from table1 as t1
  join table2 as t2 on t2.referenceid = t1.id
)
update table1
set value = t.calculatedvalue
from t
where id = t.rowid

Cette approche vous permet de développer et de tester votre requête de sélection et de la convertir en deux étapes en requête de mise à jour.

Ainsi, dans votre cas, la requête de résultat sera:

with t as (
    select v.id as rowid, s.price_per_vehicle as calculatedvalue
    from vehicles_vehicle v 
    join shipments_shipment s on v.shipment_id = s.id 
)
update vehicles_vehicle
set price = t.calculatedvalue
from t
where id = t.rowid

Notez que les alias de colonne sont obligatoires, sinon PostgreSQL se plaindra de l'ambiguïté des noms de colonne.


1
J'aime vraiment celui-ci parce que je suis toujours un peu nerveux de retirer mon "select" du haut et de le remplacer par une "mise à jour", en particulier avec plusieurs jointures. Cela réduit le nombre de vidages SQL que je devrais avoir à faire avant les mises à jour en masse. :)
dannysauer

5
Je ne sais pas pourquoi, mais la version CTE de cette requête est bien plus rapide que les solutions de "jointure simple" ci
paul.ago

L'autre avantage de cette solution est la possibilité de joindre à partir de plus de deux tables pour atteindre votre valeur calculée finale en utilisant plusieurs jointures dans l'instruction with / select.
Alex Muro

1
C'est génial. J'ai fait fabriquer ma sélection et, comme @dannysauer, j'avais peur de la conversion. Cela le fait simplement pour moi. Parfait!
frostymarvelous

1
Votre premier exemple SQL contient une erreur de syntaxe. "update t1" ne peut pas utiliser l'alias de la sous-requête t, il doit utiliser le nom de la table: "update table1". Vous faites cela correctement dans votre deuxième exemple.
EricS

80

Pour ceux qui souhaitent réellement faire un, JOINvous pouvez également utiliser:

UPDATE a
SET price = b_alias.unit_price
FROM      a AS a_alias
LEFT JOIN b AS b_alias ON a_alias.b_fk = b_alias.id
WHERE a_alias.unit_name LIKE 'some_value' 
AND a.id = a_alias.id;

Vous pouvez utiliser l'alias a_alias dans la SETsection à droite du signe égal si nécessaire. Les champs à gauche du signe égal ne nécessitent pas de référence de table car ils sont réputés provenir de la table "a" d'origine.


4
Étant donné que c'est la première réponse avec une jointure réelle (et non à l'intérieur d'une sous-requête), cela devrait être la vraie réponse acceptée. Soit cela, soit cette question doit être renommée pour éviter toute confusion si postgresql prend en charge les jointures dans la mise à jour ou non.
collier

Ça marche. Mais se sent comme un hack
M. Habib

Il est à noter que selon la documentation ( postgresql.org/docs/11/sql-update.html ), la liste de la table cible dans la clause from entraînera l'auto-jointure de la table cible. Avec moins de confiance, il me semble également qu'il s'agit d'une auto-jointure croisée, qui peut avoir des résultats inattendus et / ou des implications de performance.
Ben Collins

14

Pour ceux qui souhaitent faire un JOIN qui met à jour UNIQUEMENT les lignes que votre jointure retourne utiliser:

UPDATE a
SET price = b_alias.unit_price
FROM      a AS a_alias
LEFT JOIN b AS b_alias ON a_alias.b_fk = b_alias.id
WHERE a_alias.unit_name LIKE 'some_value' 
AND a.id = a_alias.id
--the below line is critical for updating ONLY joined rows
AND a.pk_id = a_alias.pk_id;

Cela a été mentionné ci-dessus, mais uniquement par le biais d'un commentaire .. Puisqu'il est essentiel d'obtenir le résultat correct en affichant une nouvelle réponse qui fonctionne


7

Et c'est parti:

update vehicles_vehicle v
set price=s.price_per_vehicle
from shipments_shipment s
where v.shipment_id=s.id;

Aussi simple que j'ai pu le faire. Merci les gars!

Peut également faire ceci:

-- Doesn't work apparently
update vehicles_vehicle 
set price=s.price_per_vehicle
from vehicles_vehicle v
join shipments_shipment s on v.shipment_id=s.id;

Mais alors vous avez la table des véhicules là-dedans deux fois, et vous n'êtes autorisé à l'alias qu'une seule fois, et vous ne pouvez pas utiliser l'alias dans la partie "set".


@littlegreen Vous en êtes sûr? Cela ne le joincontraint-il pas?
mpen

4
@mpen Je peux confirmer qu'il met à jour tous les enregistrements à une seule valeur. il ne fait pas ce que vous attendez.
Adam Bell

1

Voici un SQL simple qui met à jour Mid_Name sur la table Name3 en utilisant le champ Middle_Name de Name:

update name3
set mid_name = name.middle_name
from name
where name3.person_id = name.person_id;


0

Nom du premier tableau: tbl_table1 (tab1). Deuxième nom de table: tbl_table2 (tab2).

Définissez la colonne ac_status de tbl_table1 sur "INACTIVE"

update common.tbl_table1 as tab1
set ac_status= 'INACTIVE' --tbl_table1's "ac_status"
from common.tbl_table2 as tab2
where tab1.ref_id= '1111111' 
and tab2.rel_type= 'CUSTOMER';
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.