Jak zrobić aktualizację + dołączyć do PostgreSQL?


508

Zasadniczo chcę to zrobić:

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

Jestem pewien, że działałoby to w MySQL (moim tle), ale wydaje się, że nie działa w Postgresie. Występuje błąd:

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

Z pewnością jest na to łatwy sposób, ale nie mogę znaleźć właściwej składni. Jak więc napisać to w PostgreSQL?


5
Składnia Postgres jest inna: postgresql.org/docs/8.1/static/sql-update.html
Marc B

4
pojazdy, pojazdy? To interesująca konwencja nazewnictwa tabel
CodeAndCats

3
@CodeAndCats Haha ... wygląda śmiesznie, prawda? Myślę, że wtedy używałem Django, a tabele są pogrupowane według funkcji. Byłyby więc vehicles_*tabele widoków i kilka shipments_*tabel.
mpen

Odpowiedzi:


776

Składnia UPDATE jest:

[WITH [RECURSIVE] with_query [, ...]]
UPDATE [TYLKO] tabela [[AS] alias]
    ZESTAW {kolumna = {wyrażenie | DOMYŚLNE} |
          (kolumna [, ...]) = ({wyrażenie | DOMYŚLNE} [, ...])} [, ...]
    [FROM from_list]
    [GDZIE warunki | GDZIE BIEŻĄCA nazwa kursora]
    [ZWROT * | wyrażenie_wyjściowe [[AS] nazwa_wyjściowa] [, ...]]

W twoim przypadku myślę, że chcesz tego:

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

Jeśli aktualizacja opiera się na całej liście złączeń tabeli, czy powinny one znajdować się w sekcji UPDATE, czy w sekcji FROM?
ted.strauss,

11
@ ted.strauss: FROM może zawierać listę tabel.
Mark Byers,

140

Pozwólcie, że wyjaśnię nieco więcej w moim przykładzie.

Zadanie: poprawne informacje, gdy abiturienty (studenci, którzy kończą naukę w szkole średniej) złożyli wnioski na uniwersytet wcześniej, niż otrzymali świadectwa szkolne (tak, otrzymali świadectwa wcześniej, niż je wydali (według określonej daty świadectwa). zwiększ datę złożenia wniosku, aby dopasować datę wydania certyfikatu.

A zatem. następna instrukcja podobna do 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;

W ten sposób staje się podobny do PostgreSQL

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

Jak widać, oryginalne podzapytanie JOINjest ONklauzula stały się jednym z WHEREwarunków, który jest conjucted przez ANDz innymi, które zostały przeniesione z podzapytania bez zmian. I nie ma już potrzeby JOINtabelowania się z samym sobą (jak to było w podzapytaniu).


16
Jak dołączyłbyś do trzeciego stołu?
Growler

19
Po prostu JOINto jak zwykle na FROMliście:FROM abiturients b JOIN addresses c ON c.abiturient_id = b.id
Envek

@Envek - Niestety, nie możesz tam użyć DOŁĄCZ, właśnie sprawdziłem. postgresql.org/docs/10/static/sql-update.html
Adrian Smith

3
@AdrianSmith, nie możesz używać JOIN w samej aktualizacji, ale możesz użyć jej w from_listklauzuli UPDATE (która jest rozszerzeniem SQL PostgreSQL). Zobacz także uwagi na temat dołączania ostrzeżeń dotyczących tabel pod podanym linkiem.
Envek

@Envek - Ach, dziękuję za wyjaśnienie, tęskniłem za tym.
Adrian Smith

130

Odpowiedź Mark Byers jest optymalna w tej sytuacji. Chociaż w bardziej złożonych sytuacjach możesz pobrać zapytanie, które zwraca rowID i obliczone wartości, i dołączyć je do zapytania o aktualizację w następujący sposób:

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

Takie podejście pozwala opracować i przetestować wybrane zapytanie, a następnie w dwóch krokach przekształcić je w zapytanie dotyczące aktualizacji.

W twoim przypadku wynikowe zapytanie będzie:

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

Zauważ, że aliasy kolumn są obowiązkowe, w przeciwnym razie PostgreSQL będzie narzekał na niejednoznaczność nazw kolumn.


1
Naprawdę podoba mi się ten, ponieważ zawsze jestem trochę zdenerwowany tym, że zdejmuję „select” z góry i zastępuję go „aktualizacją”, zwłaszcza z wieloma połączeniami. Zmniejsza to liczbę zrzutów SQL, które powinienem wykonać przed masowymi aktualizacjami. :)
dannysauer,

5
Nie jestem pewien, dlaczego, ale wersja CTE tego zapytania jest o wiele szybsza niż powyższe rozwiązania „zwykłego łączenia”
paul.ago,

Inną zaletą tego rozwiązania jest możliwość łączenia z więcej niż dwóch tabel w celu uzyskania ostatecznej obliczonej wartości za pomocą wielu złączeń w instrukcji with / select.
Alex Muro,

1
To jest niesamowite. Miałem swój wybrany spreparowany i jak @ Dannyauer, bałem się konwersji. To mi po prostu robi. Doskonały!
frostymarvelous

1
Twój pierwszy przykład SQL zawiera błąd składniowy. „aktualizacja t1” nie może używać aliasu z podzapytania t, musi użyć nazwy tabeli: „aktualizacja table1”. Robisz to poprawnie w drugim przykładzie.
EricS,

80

Dla tych, którzy naprawdę chcą to zrobić JOIN, możesz również użyć:

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;

W razie potrzeby możesz użyć a_alias w SETsekcji po prawej stronie znaku równości. Pola po lewej stronie znaku równości nie wymagają odwołania do tabeli, ponieważ uważa się, że pochodzą z oryginalnej tabeli „a”.


4
Biorąc pod uwagę, że jest to pierwsza odpowiedź z rzeczywistym łączeniem (a nie wewnątrz z podzapytaniem), powinna to być prawdziwa akceptowana odpowiedź. Należy zmienić nazwę tego lub tego pytania, aby uniknąć nieporozumień, czy postgresql obsługuje dołączenia w aktualizacji, czy nie.
naszyjnik z

To działa. Ale czuje się jak hack
M. Habib

Należy zauważyć, że zgodnie z dokumentacją ( postgresql.org/docs/11/sql-update.html ) umieszczenie tabeli docelowej w klauzuli from spowoduje samozłączenie tabeli docelowej. Mniej pewnie wydaje mi się również, że jest to połączenie między sobą, które może mieć niezamierzone wyniki i / lub implikacje dotyczące wydajności.
Ben Collins

14

Dla tych, którzy chcą wykonać JOIN, który aktualizuje TYLKO wiersze, które zwraca sprzężenie:

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;

Zostało to wspomniane powyżej, ale tylko poprzez komentarz. Ponieważ kluczowe jest uzyskanie poprawnego wyniku, opublikowanie NOWEJ odpowiedzi, która działa


7

No to ruszamy:

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

Proste, jak mogłem to zrobić. Dzięki chłopaki!

Można to również zrobić:

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

Ale wtedy masz tam tablicę pojazdów dwa razy i możesz ją aliasować tylko raz, i nie możesz użyć aliasu w części „set”.


@littlegreen Jesteś tego pewien? Czy to nie joinogranicza?
mpen

4
@mpen Mogę potwierdzić, że aktualizuje wszystkie rekordy do jednej wartości. nie robi tego, czego można oczekiwać.
Adam Bell

1

Oto prosty SQL, który aktualizuje nazwę Mid_Name w tabeli Name3 za pomocą pola Middle_Name z Name:

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


0

Nazwa pierwszej tabeli: tbl_table1 (tab1). Nazwa drugiej tabeli: tbl_table2 (tab2).

Ustaw kolumnę ac_status tbl_table1 na „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';
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.