Wie mache ich ein Update + Join in PostgreSQL?


508

Grundsätzlich möchte ich Folgendes tun:

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

Ich bin mir ziemlich sicher, dass das in MySQL (meinem Hintergrund) funktionieren würde, aber es scheint nicht in Postgres zu funktionieren. Der Fehler, den ich bekomme, ist:

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

Sicher gibt es einen einfachen Weg, dies zu tun, aber ich kann nicht die richtige Syntax finden. Wie würde ich das in PostgreSQL schreiben?



4
Fahrzeuge_Fahrzeug, Sendungen_Schiff? Das ist eine interessante Namenskonvention für Tabellen
CodeAndCats

3
@CodeAndCats Haha ... es sieht lustig aus, nicht wahr? Ich glaube, ich habe damals Django verwendet, und die Tabellen sind nach Funktionen gruppiert. Es hätte also Ansichtstabellen vehicles_*und einige shipments_*Tabellen gegeben.
Mpen

Antworten:


776

Die UPDATE-Syntax lautet:

[WITH [RECURSIVE] with_query [, ...]]
UPDATE [ONLY] table [[AS] alias]
    SET {column = {expression | STANDARD} |
          (Spalte [, ...]) = ({Ausdruck | STANDARD} [, ...])} [, ...]
    [FROM from_list]
    [WO Bedingung | WO AKTUELLER Cursorname]
    [RÜCKKEHR * | output_expression [[AS] output_name] [, ...]]

In Ihrem Fall denke ich, dass Sie dies wollen:

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

Wenn sich das Update auf eine ganze Liste von Tabellenverknüpfungen stützt, sollten sich diese im Abschnitt UPDATE oder im Abschnitt FROM befinden?
Ted.strauss

11
@ ted.strauss: Das FROM kann eine Liste von Tabellen enthalten.
Mark Byers

140

Lassen Sie mich anhand meines Beispiels etwas mehr erklären.

Aufgabe: Richtige Informationen, bei denen Abiturienten (Schüler, die kurz vor dem Verlassen der Sekundarschule stehen) Anträge früher an der Universität eingereicht haben, als sie Schulzeugnisse erhalten haben (ja, sie haben Zertifikate früher erhalten, als sie ausgestellt wurden (bis zum angegebenen Zertifikatdatum). Also werden wir Erhöhen Sie das Einreichungsdatum des Antrags, um es an das Ausstellungsdatum des Zertifikats anzupassen.

Somit. nächste MySQL-ähnliche Anweisung:

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;

Wird so zu PostgreSQL-ähnlich

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

Wie Sie sehen können, ist JOINdie ONKlausel der ursprünglichen Unterabfrage zu einer der WHEREBedingungen geworden, die ANDmit anderen verbunden sind, die ohne Änderungen aus der Unterabfrage verschoben wurden. Und es besteht keine Notwendigkeit mehr, JOINmit sich selbst zu tischen (wie es bei Unterabfragen der Fall war).


16
Wie würden Sie sich einem dritten Tisch anschließen?
Growler

19
Sie haben es einfach JOINwie gewohnt in der FROMListe:FROM abiturients b JOIN addresses c ON c.abiturient_id = b.id
Envek

@Envek - Du kannst JOIN dort leider nicht verwenden, ich habe es gerade überprüft. postgresql.org/docs/10/static/sql-update.html
Adrian Smith

3
@AdrianSmith, Sie können JOIN nicht in UPDATE selbst verwenden, sondern in der UPDATE- from_listKlausel (der PostgreSQL-Erweiterung von SQL). Weitere Informationen zum Verbinden von Tabellen finden Sie unter dem von Ihnen angegebenen Link.
Envek

@Envek - Ah, danke für die Klarstellung, das habe ich verpasst.
Adrian Smith

130

Die Antwort von Mark Byers ist in dieser Situation optimal. In komplexeren Situationen können Sie die Auswahlabfrage, die Zeilen-IDs und berechnete Werte zurückgibt, wie folgt an die Aktualisierungsabfrage anhängen:

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

Mit diesem Ansatz können Sie Ihre ausgewählte Abfrage entwickeln, testen und in zwei Schritten in die Aktualisierungsabfrage konvertieren.

In Ihrem Fall lautet die Ergebnisabfrage also:

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

Beachten Sie, dass Spaltenaliasnamen obligatorisch sind, da sich PostgreSQL sonst über die Mehrdeutigkeit der Spaltennamen beschwert.


1
Ich mag dieses wirklich, weil ich immer ein bisschen nervös bin, wenn ich meine "Auswahl" von oben nehme und sie durch ein "Update" ersetze, insbesondere bei mehreren Verknüpfungen. Dies reduziert die Anzahl der SQL-Dumps, die ich vor Massenaktualisierungen durchführen muss. :)
dannysauer

5
Ich
weiß

Der andere Vorteil dieser Lösung ist die Möglichkeit, aus mehr als zwei Tabellen einen Join zu erstellen, um mithilfe mehrerer Joins in der with / select-Anweisung zu Ihrem endgültigen berechneten Wert zu gelangen.
Alex Muro

1
Das ist fantastisch. Ich ließ meine Auswahl herstellen und hatte wie @dannysauer Angst vor der Konvertierung. Das macht es einfach für mich. Perfekt!
frostymarvelous

1
Ihr erstes SQL-Beispiel weist einen Syntaxfehler auf. "update t1" kann den Alias ​​aus der t-Unterabfrage nicht verwenden, sondern muss den Tabellennamen "update table1" verwenden. Sie machen das in Ihrem zweiten Beispiel richtig.
EricS

80

Für diejenigen, die tatsächlich eine machen wollen, JOINkönnen Sie auch verwenden:

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;

Sie können den a_alias bei Bedarf im SETAbschnitt rechts neben dem Gleichheitszeichen verwenden. Die Felder links vom Gleichheitszeichen erfordern keine Tabellenreferenz, da sie aus der ursprünglichen "a" -Tabelle stammen.


4
Da dies die erste Antwort mit einem tatsächlichen Join-In ist (und nicht innerhalb einer With-Unterabfrage), sollte dies die wirklich akzeptierte Antwort sein. Entweder diese oder diese Frage sollte umbenannt werden, um Verwirrung zu vermeiden, ob postgresql Joins in Update unterstützt oder nicht.
Halskette

Es klappt. Aber fühlt sich an wie ein Hack
M. Habib

Es ist zu beachten, dass gemäß der Dokumentation ( postgresql.org/docs/11/sql-update.html ) die Auflistung der Zieltabelle in der from-Klausel dazu führt, dass die Zieltabelle selbst verbunden wird. Weniger sicher scheint es mir auch, dass dies ein Cross-Self-Join ist, der unbeabsichtigte Ergebnisse und / oder Auswirkungen auf die Leistung haben kann.
Ben Collins

14

Für diejenigen, die einen JOIN ausführen möchten, der NUR die Zeilen aktualisiert, die Ihr Join zurückgibt, verwenden Sie:

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;

Dies wurde oben erwähnt, aber nur durch einen Kommentar. Da es wichtig ist, das richtige Ergebnis zu erhalten, wird eine NEUE Antwort veröffentlicht, die funktioniert


7

Auf geht's:

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

So einfach ich es schaffen konnte. Danke Leute!

Kann auch:

-- 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;

Aber dann haben Sie die Fahrzeugtabelle zweimal dort und dürfen sie nur einmal als Alias ​​verwenden, und Sie können den Alias ​​im Abschnitt "Set" nicht verwenden.


@littlegreen Bist du dir da sicher? Nicht das joines einschränken?
Mpen

4
@mpen Ich kann bestätigen, dass alle Datensätze auf einen Wert aktualisiert werden. es macht nicht das, was Sie erwarten würden.
Adam Bell

1

Hier ist eine einfache SQL, die Mid_Name in der Name3-Tabelle mithilfe des Middle_Name-Felds von Name aktualisiert:

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


0

Name der ersten Tabelle: tbl_table1 (tab1). Zweiter Tabellenname: tbl_table2 (tab2).

Setzen Sie die ac_status-Spalte von tbl_table1 auf "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';
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.