จะอัพเดต + เข้าร่วมใน PostgreSQL ได้อย่างไร?


508

โดยทั่วไปฉันต้องการทำสิ่งนี้:

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

ฉันค่อนข้างแน่ใจว่าจะทำงานใน MySQL (พื้นหลังของฉัน) แต่ดูเหมือนจะไม่ทำงานใน postgres ข้อผิดพลาดที่ฉันได้รับคือ:

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

แน่นอนมีวิธีที่ง่ายในการทำเช่นนี้ แต่ฉันไม่สามารถหาไวยากรณ์ที่เหมาะสม ดังนั้นฉันจะเขียนนี้ใน PostgreSQL อย่างไร


5
ไวยากรณ์ของ Postgres นั้นแตกต่างกัน: postgresql.org/docs/8.1/static/sql-update.html
Marc B

4
ยานพาหนะ _ การขนส่งพัสดุ _ เรือสินค้า? นั่นเป็นแบบแผนการตั้งชื่อตารางที่น่าสนใจ
CodeAndCats

3
@CodeAndCats ฮ่าฮ่า ... มันดูตลกใช่มั้ย ฉันคิดว่าฉันใช้ Django ในเวลานั้นและตารางถูกจัดกลุ่มตามคุณลักษณะ ดังนั้นจะมีvehicles_*ตารางการดูและบางshipments_*ตาราง
mpen

คำตอบ:


776

ไวยากรณ์ UPDATEคือ:

[ด้วย [RECURSIVE] with_query [, ... ]]
อัปเดต [เฉพาะ] ตาราง [[AS] นามแฝง]
    SET {column = {expression | ค่าเริ่มต้น} |
          (คอลัมน์ [, ... ]) = ({expression | DEFAULT} [, ... ])} [, ... ]
    [FROM from_list]
    เงื่อนไขไหน WHERE CURRENT OF cursor_name]
    [ผลตอบแทน * | output_expression [[AS] output_name] [, ... ]]

ในกรณีของคุณฉันคิดว่าคุณต้องการสิ่งนี้:

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

หากการอัปเดตนั้นขึ้นอยู่กับรายการทั้งหมดของการรวมตารางสิ่งเหล่านั้นควรอยู่ในส่วน UPDATE หรือส่วน FROM หรือไม่?
ted.strauss

11
@ ted.strauss: FROM สามารถมีรายการตาราง
Mark Byers

140

ให้ฉันอธิบายอีกหน่อยโดยตัวอย่างของฉัน

ภารกิจ: ข้อมูลที่ถูกต้องซึ่งผู้ที่ถูกทารุณกรรม (นักเรียนกำลังจะออกจากโรงเรียนมัธยม) ได้ส่งใบสมัครไปยังมหาวิทยาลัยเร็วกว่าที่พวกเขาได้รับใบรับรองโรงเรียน (ใช่พวกเขาได้รับใบรับรองเร็วกว่าที่พวกเขาออก (ตามวันที่ระบุใบรับรอง) ดังนั้นเราจะ เพิ่มวันที่ส่งใบสมัครให้พอดีกับวันที่ออกใบรับรอง

ดังนั้น. คำสั่งคล้าย 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;

กลายเป็นเหมือน 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

อย่างที่คุณสามารถเห็นJOINได้ONประโยคย่อยของแบบสอบถามดั้งเดิมได้กลายเป็นหนึ่งในWHEREเงื่อนไขที่ได้รับความสนใจจากANDผู้อื่นซึ่งถูกย้ายจากแบบสอบถามย่อยโดยไม่มีการเปลี่ยนแปลง และไม่จำเป็นต้องต่อJOINโต๊ะกับตัวเองอีกต่อไป (เหมือนเดิมในแบบสอบถามย่อย)


16
คุณจะเข้าร่วมโต๊ะที่สามได้อย่างไร
Growler

19
คุณทำได้JOINตามปกติในFROMรายการ:FROM abiturients b JOIN addresses c ON c.abiturient_id = b.id
Envek

@Envek - คุณไม่สามารถใช้เข้าร่วมที่นั่นได้ฉันเพิ่งตรวจสอบ postgresql.org/docs/10/static/sql-update.html
Adrian Smith

3
@AdrianSmith คุณไม่สามารถใช้ JOIN ใน UPDATE ได้ แต่สามารถใช้ใน UPDATE's from_listclause (ซึ่งเป็นส่วนเสริมของ SQL ของ PostgreSQL) นอกจากนี้โปรดดูหมายเหตุเกี่ยวกับการเข้าร่วมการเตือนตารางในลิงก์ที่คุณให้ไว้
Envek

@Envek - อ่าขอบคุณสำหรับความกระจ่างฉันพลาดไป
Adrian Smith

130

คำตอบของ Mark Byers นั้นดีที่สุดในสถานการณ์นี้ แม้ว่าในสถานการณ์ที่ซับซ้อนมากขึ้นคุณสามารถใช้คิวรีแบบเลือกที่ส่งคืน rowids และค่าจากการคำนวณและแนบไปกับคิวรีการอัปเดตดังนี้:

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

วิธีนี้ช่วยให้คุณพัฒนาและทดสอบคิวรีที่เลือกและในสองขั้นตอนจะแปลงเป็นคิวรีอัปเดต

ดังนั้นในกรณีของคุณแบบสอบถามผลลัพธ์จะเป็น:

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

โปรดทราบว่าจำเป็นต้องใช้ชื่อแทนคอลัมน์มิฉะนั้น PostgreSQL จะบ่นเกี่ยวกับความกำกวมของชื่อคอลัมน์


1
ฉันชอบอันนี้มากเพราะฉันรู้สึกประหม่าอยู่เสมอกับการ "เลือก" ออกด้านบนและแทนที่ด้วย "อัปเดต" โดยเฉพาะกับการรวมหลายรายการ สิ่งนี้ช่วยลดจำนวนการทิ้ง SQL ที่ฉันควรทำก่อนที่จะอัปเดตจำนวนมาก :)
dannysauer

5
ไม่แน่ใจว่าทำไม แต่รุ่น CTE ของแบบสอบถามนี้เป็นวิธีที่เร็วกว่าโซลูชัน "เข้าร่วมธรรมดา" ข้างต้น
paul.ago

ข้อได้เปรียบอื่น ๆ ของการแก้ปัญหานี้คือความสามารถในการเข้าร่วมจากมากกว่าสองตารางเพื่อรับค่าการคำนวณสุดท้ายของคุณโดยใช้การรวมหลายรายการในคำสั่ง with / select
Alex Muro

1
นี่มันเจ๋งมาก. ฉันเลือกงานฝีมือของฉันแล้วและชอบ @dannysauer ฉันกลัวการแปลง มันทำเพื่อฉัน ที่สมบูรณ์แบบ!
frostymarvelous

1
ตัวอย่าง SQL แรกของคุณมีข้อผิดพลาดทางไวยากรณ์ "update t1" ไม่สามารถใช้นามแฝงจากแบบสอบถามย่อย t ได้ต้องใช้ชื่อตาราง: "update table1" คุณทำอย่างถูกต้องในตัวอย่างที่สองของคุณ
EricS

80

สำหรับผู้ที่ต้องการทำจริงJOINคุณสามารถใช้:

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;

คุณสามารถใช้ a_alias ในSETส่วนด้านขวาของเครื่องหมายเท่ากับหากจำเป็น เขตข้อมูลทางด้านซ้ายของเครื่องหมายเท่ากับไม่จำเป็นต้องมีการอ้างอิงตารางเนื่องจากจะถือว่ามาจากตาราง "a" ดั้งเดิม


4
เมื่อพิจารณาว่านี่เป็นคำตอบแรกที่มีการเข้าร่วมจริง (และไม่ได้อยู่ในกับแบบสอบถามย่อย) นี่ควรเป็นคำตอบที่แท้จริง ควรเปลี่ยนชื่อคำถามนี้หรือไม่เพื่อหลีกเลี่ยงความสับสนว่า postgresql รองรับการรวมในการอัพเดทหรือไม่
สร้อยคอ

มันได้ผล. แต่รู้สึกเหมือนแฮ็ค
เอ็มฮาบิบ

ควรสังเกตว่าตามเอกสาร ( postgresql.org/docs/11/sql-update.html ) การแสดงตารางเป้าหมายในส่วนคำสั่งจากจะทำให้ตารางเป้าหมายเข้าร่วมด้วยตนเอง มีความมั่นใจน้อยกว่าฉันก็ปรากฏว่านี่เป็นการเข้าร่วมข้ามตนเองซึ่งอาจมีผลลัพธ์ที่ไม่ได้ตั้งใจและ / หรือความหมายของประสิทธิภาพ
Ben Collins

14

สำหรับผู้ที่ต้องการเข้าร่วมที่อัปเดตเฉพาะแถวที่คุณเข้าร่วมจะได้รับผลตอบแทน:

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;

สิ่งนี้ถูกกล่าวถึงข้างต้น แต่ผ่านความคิดเห็น .. เนื่องจากเป็นสิ่งสำคัญที่จะต้องได้รับผลลัพธ์ที่ถูกต้องโพสต์คำตอบใหม่ที่ได้ผล


7

ไปเลย:

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

เรียบง่ายอย่างที่ฉันสามารถทำได้ ขอบคุณเพื่อน!

ยังสามารถทำสิ่งนี้:

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

แต่จากนั้นคุณมีตารางยานพาหนะอยู่สองครั้งและคุณได้รับอนุญาตให้ใช้นามแฝงเพียงครั้งเดียวและคุณไม่สามารถใช้นามแฝงในส่วน "ตั้งค่า"


@littlegreen คุณแน่ใจหรือไม่ ไม่joinจำกัด หรือไม่
mpen

4
@mpen ฉันสามารถยืนยันได้ว่าจะอัปเดตระเบียนทั้งหมดเป็นค่าเดียว มันไม่ได้ทำในสิ่งที่คุณคาดหวัง
Adam Bell

1

นี่คือ SQL แบบธรรมดาที่อัพเดต Mid_Name บนตาราง Name3 โดยใช้ฟิลด์ Middle_Name จากชื่อ:

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

0

ลิงค์ด้านล่างมีตัวอย่างที่ช่วยแก้ไขและช่วยให้เข้าใจได้ดีขึ้นเกี่ยวกับการใช้งานupdateและjoinpostgres

UPDATE product
SET net_price = price - price * discount
FROM
product_segment
WHERE
product.segment_id = product_segment.id;

ดู: http://www.postgresqltutorial.com/postgresql-update-join/


0

ชื่อตารางแรก: tbl_table1 (แท็บ 1) ชื่อตารางที่สอง: tbl_table2 (tab2)

ตั้งค่าคอลัมน์ ac_status ของ tbl_table1 เป็น "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';
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.