Bagaimana cara melakukan pembaruan + bergabung di PostgreSQL?


508

Pada dasarnya, saya ingin melakukan ini:

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

Saya cukup yakin itu akan bekerja di MySQL (latar belakang saya), tetapi tampaknya tidak berfungsi di postgres. Kesalahan yang saya dapatkan adalah:

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

Tentunya ada cara mudah untuk melakukan ini, tetapi saya tidak dapat menemukan sintaks yang tepat. Jadi, bagaimana saya menulis ini di PostgreSQL?


5
Sintaks postgres
Marc B

4
vehicle_vehicle, shipments_shipment? Itu adalah konvensi penamaan tabel yang menarik
CodeAndCats

3
@CodeAndCats Haha ... memang terlihat lucu bukan? Saya pikir saya menggunakan Django pada saat itu, dan tabel dikelompokkan berdasarkan fitur. Jadi akan ada tampilan vehicles_*tabel, dan beberapa shipments_*tabel.
mpen

Jawaban:


776

The sintaks UPDATE adalah:

[DENGAN [RECURSIVE] with_query [, ...]]
UPDATE [ONLY] table [[AS] alias]
    SET {kolom = {ekspresi | DEFAULT} |
          (kolom [, ...]) = ({ekspresi | DEFAULT} [, ...])} [, ...]
    [FROM from_list]
    [Kondisi MANA | DI MANA SAJA DARI cursor_name]
    [PENGEMBALIAN * | output_expression [[AS] output_name] [, ...]]

Dalam kasus Anda, saya pikir Anda menginginkan ini:

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

Jika pembaruan bergantung pada seluruh daftar tabel bergabung, haruskah mereka berada di bagian PEMBARUAN atau bagian DARI?
ted.strauss

11
@ ted.strauss: FROM dapat berisi daftar tabel.
Mark Byers

140

Biarkan saya menjelaskan sedikit lebih banyak dengan contoh saya.

Tugas: perbaiki info, di mana pelaku aborsi (siswa yang akan meninggalkan sekolah menengah) telah mengajukan aplikasi ke universitas lebih awal, daripada mereka mendapatkan sertifikat sekolah (ya, mereka mendapat sertifikat lebih awal, daripada yang dikeluarkan (berdasarkan tanggal sertifikat yang ditentukan). Jadi, kami akan tingkatkan tanggal pengiriman aplikasi agar sesuai dengan tanggal penerbitan sertifikat.

Jadi. pernyataan seperti-MySQL berikutnya:

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;

Menjadi seperti PostgreSQL sedemikian rupa

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

Seperti yang Anda lihat, subquery asli JOIN's ONklausul telah menjadi salah satu WHEREkondisi, yang conjucted oleh ANDdengan orang lain, yang telah dipindahkan dari subquery dengan tidak ada perubahan. Dan tidak ada lagi kebutuhan untuk JOINmeja dengan dirinya sendiri (seperti di subquery).


16
Bagaimana Anda bergabung dengan tabel ketiga?
Growler

19
Anda hanya JOINseperti biasa dalam FROMdaftar:FROM abiturients b JOIN addresses c ON c.abiturient_id = b.id
Envek

@Envek - Anda tidak dapat menggunakan BERGABUNG di sana, saya baru saja memeriksa. postgresql.org/docs/10/static/sql-update.html
Adrian Smith

3
@AdrianSmith, Anda tidak dapat menggunakan BERGABUNG dalam UPDATE itu sendiri, tetapi dapat menggunakannya dalam from_listklausa UPDATE (yang merupakan ekstensi SQL PostgreSQL). Juga, lihat catatan tentang bergabung dengan tabel peringatan pada tautan yang Anda berikan.
Envek

@Envek - Ah, terima kasih atas klarifikasi, saya melewatkan itu.
Adrian Smith

130

Jawaban Mark Byers adalah yang optimal dalam situasi ini. Meskipun dalam situasi yang lebih kompleks, Anda dapat mengambil kueri pemilihan yang mengembalikan nilai baris pendek dan nilai terhitung dan melampirkannya ke kueri pembaruan seperti ini:

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

Pendekatan ini memungkinkan Anda mengembangkan dan menguji permintaan pilih Anda dan dalam dua langkah mengubahnya menjadi permintaan pembaruan.

Jadi, dalam kasus Anda, kueri hasil adalah:

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

Perhatikan bahwa alias kolom adalah wajib jika tidak PostgreSQL akan mengeluh tentang ambiguitas nama kolom.


1
Saya sangat suka yang ini karena saya selalu sedikit gugup dengan mengambil "pilih" dari atas dan menggantinya dengan "pembaruan," terutama dengan beberapa bergabung. Ini mengurangi jumlah dump SQL yang harus saya lakukan sebelum pembaruan massal. :)
dannysauer

5
Tidak yakin mengapa, tetapi versi CTE dari kueri ini jauh lebih cepat daripada solusi "plain join" di atas
paul.ago

Keuntungan lain dari solusi ini adalah kemampuan bergabung dari lebih dari dua tabel untuk mencapai nilai akhir perhitungan Anda dengan menggunakan beberapa gabungan dalam pernyataan with / select.
Alex Muro

1
Ini luar biasa. Saya memilih craft dan menyukai @dannysauer, saya takut konversi. Ini cukup untuk saya. Sempurna!
frostymarvelous

1
Contoh SQL pertama Anda memiliki kesalahan sintaksis. "update t1" tidak dapat menggunakan alias dari t subquery, perlu menggunakan nama tabel: "update table1". Anda melakukan ini dengan benar dalam contoh kedua Anda.
EricS

80

Bagi mereka yang benar-benar ingin melakukan, JOINAnda juga dapat menggunakan:

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;

Anda dapat menggunakan a_alias di SETbagian di sebelah kanan tanda sama dengan jika diperlukan. Bidang di sebelah kiri tanda sama dengan tidak memerlukan referensi tabel karena mereka dianggap dari tabel "a" asli.


4
Mengingat ini adalah jawaban pertama dengan gabung yang sebenarnya (dan bukan di dalam dengan subquery), ini harus menjadi jawaban yang diterima. Entah itu atau pertanyaan ini harus diganti namanya untuk menghindari kebingungan apakah postgresql mendukung bergabung dalam pembaruan atau tidak.
kalung

Berhasil. Tetapi terasa seperti retasan
M. Habib

Perlu dicatat bahwa sesuai dengan dokumentasi ( postgresql.org/docs/11/sql-update.html ), daftar tabel target dalam klausa dari akan menyebabkan tabel target bergabung sendiri. Kurang percaya diri, bagi saya tampaknya ini adalah cross-self-join, yang mungkin memiliki hasil yang tidak diinginkan dan / atau implikasi kinerja.
Ben Collins

14

Bagi mereka yang ingin melakukan GABUNG yang memperbarui HANYA baris yang Anda kembalikan, gunakan:

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;

Ini disebutkan di atas tetapi hanya melalui komentar .. Karena sangat penting untuk mendapatkan hasil yang benar memposting jawaban BARU yang Berfungsi


7

Kita mulai:

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

Sesederhana mungkin. Terima kasih teman-teman!

Dapat juga melakukan ini:

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

Tapi kemudian Anda punya meja kendaraan di sana dua kali, dan Anda hanya diperbolehkan untuk menamainya sekali, dan Anda tidak bisa menggunakan alias di bagian "set".


@ Sedikit hijau Anda yakin tentang itu? Bukankah joinitu yang membatasi?
mpen

4
@mpen Saya dapat mengonfirmasi bahwa semua catatan diperbarui ke satu nilai. itu tidak melakukan apa yang Anda harapkan.
Adam Bell

1

Berikut adalah SQL sederhana yang memperbarui Mid_Name di tabel Name3 menggunakan bidang Middle_Name dari Name:

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


0

Nama Tabel Pertama: tbl_table1 (tab1). Nama Tabel Kedua: tbl_table2 (tab2).

Setel kolom ac_status tbl_table1 ke "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';
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.