วิธีการเลียนแบบ“ การเพิกเฉยการแทรก” และ“ การอัปเดตคีย์ซ้ำ” (sql merge) ด้วย postgresql ได้อย่างไร


140

เซิร์ฟเวอร์ SQL บางตัวมีคุณสมบัติที่INSERTถูกข้ามไปถ้ามันจะละเมิดข้อ จำกัด ของคีย์หลัก / เฉพาะ ยกตัวอย่างเช่น MySQL INSERT IGNOREมี

วิธีที่ดีที่สุดในการจำลองINSERT IGNOREและON DUPLICATE KEY UPDATEใช้ PostgreSQL คืออะไร


ดูเพิ่มเติมที่: stackoverflow.com/questions/5269590/…
เดฟจาร์วิส

ดูเพิ่มเติมได้ที่: stackoverflow.com/q/1109061/330315 และdba.stackexchange.com/questions/78510/…
a_horse_with_no_name

6
ตั้งแต่ 9.5 เป็นไปได้โดยกำเนิด: stackoverflow.com/a/34639631/4418
warren

การจำลอง MySQL: ON DUPLICATE KEY UPDATEบน PgSQL 9.5 นั้นยังค่อนข้างเป็นไปไม่ได้เนื่องจาก PgSQL ที่ON CLAUSEเทียบเท่ากันกำหนดให้คุณต้องระบุชื่อข้อ จำกัด ในขณะที่ MySQL สามารถจับข้อ จำกัด ใด ๆ โดยไม่จำเป็นต้องกำหนด สิ่งนี้ทำให้ฉันไม่สามารถ "เลียนแบบ" ฟีเจอร์นี้โดยไม่ต้องเขียนข้อความค้นหาซ้ำ
NeverEndingQueue

คำตอบ:


35

ลองอัปเดต หากไม่ได้แก้ไขแถวใด ๆ ที่หมายความว่าไม่มีอยู่ให้ทำการแทรก เห็นได้ชัดว่าคุณทำเช่นนี้ในการทำธุรกรรม

แน่นอนคุณสามารถห่อสิ่งนี้ในฟังก์ชั่นถ้าคุณไม่ต้องการใส่รหัสพิเศษในฝั่งไคลเอ็นต์ นอกจากนี้คุณยังต้องวนซ้ำสำหรับสภาพการแข่งขันที่หายากมากในการคิดนั้น

มีตัวอย่างของสิ่งนี้ในเอกสาร: http://www.postgresql.org/docs/9.3/static/plpgsql-control-structures.htmlตัวอย่าง 40-2 ที่ด้านล่าง

นั่นเป็นวิธีที่ง่ายที่สุด คุณสามารถทำเวทมนตร์ด้วยกฎ แต่มันอาจจะยุ่งมาก ฉันขอแนะนำวิธีการแบบปิดฟังก์ชั่นในแต่ละวัน

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


4
"หากคุณกำลังติดต่อกับแถวจำนวนมาก" นั่นเป็นกรณีของฉัน ฉันต้องการที่จะอัปเดต / แทรกแถวจำนวนมากและด้วย mysql ฉันสามารถทำได้ด้วยการค้นหาเพียงครั้งเดียวโดยไม่ต้องวนซ้ำใด ๆ ตอนนี้ฉันสงสัยว่าสิ่งนี้เป็นไปได้ด้วย postgresql เช่นกัน: ใช้เพียงหนึ่งแบบสอบถามเพื่ออัปเดตหรือแทรกเป็นกลุ่ม คุณพูดว่า: "คุณดีที่สุดในการแยกออกเป็นสองแบบสอบถามหนึ่งรายการสำหรับ INSERT และอีกรายการสำหรับ UPDATE" แต่ฉันจะแทรกได้อย่างไรซึ่งไม่ทำให้เกิดข้อผิดพลาดในคีย์ที่ซ้ำกัน (เช่น "INSERT IGNORE")
gpilotino

4
แมกนัสหมายความว่าคุณใช้แบบสอบถามเช่นนี้: "เริ่มต้นการทำธุรกรรมสร้างชั่วคราว Temporary_table เป็นเลือก * จากการทดสอบที่เป็นเท็จคัดลอก Temporary_table จาก 'data_file.csv'; การทดสอบล็อคตารางการปรับปรุงชุดทดสอบข้อมูล data = temporary_table.data จาก Temporary_table test.id = temporary_table.id; แทรกลงในการทดสอบเลือก * จาก temporary_table โดยที่ id ไม่ได้อยู่ใน (เลือก id จากการทดสอบ) เป็น "
Tometzky

25
ปรับปรุง:กับ PostgreSQL 9.5 INSERT ... ON CONFLICT DO NOTHING;ตอนนี้เป็นง่ายๆเป็น ดูเพิ่มเติมตอบstackoverflow.com/a/34639631/2091700
Alphaaa

ที่สำคัญ SQL มาตรฐานMERGEคือไม่เห็นพ้องด้วย upsert ปลอดภัยจนกว่าคุณจะใช้LOCK TABLEครั้งแรก ผู้คนใช้วิธีนั้น แต่มันผิด
Craig Ringer

1
ด้วย v9.5 ตอนนี้เป็นฟีเจอร์ 'ดั้งเดิม' ดังนั้นโปรดตรวจสอบความคิดเห็นของ @Alphaaa (เพียงแค่โฆษณาความคิดเห็นที่โฆษณาคำตอบ)
Camilo Delvasto

178

ด้วย PostgreSQL 9.5 ตอนนี้เป็นฟังก์ชันดั้งเดิม (เช่นMySQL มีมาหลายปีแล้ว):

INSERT ... ตามความขัดแย้งห้ามทำการอัปเดต ("UPSERT")

9.5 นำการสนับสนุนสำหรับการดำเนินงาน "UPSERT" INSERT ถูกขยายเพื่อยอมรับ ON ON CONFLICT DO UPDATE / IGNORE ข้อนี้ระบุการกระทำทางเลือกที่จะใช้ในกรณีที่มีการละเมิดซ้ำซ้อน

...

ตัวอย่างเพิ่มเติมของไวยากรณ์ใหม่:

INSERT INTO user_logins (username, logins)
VALUES ('Naomi',1),('James',1) 
ON CONFLICT (username)
DO UPDATE SET logins = user_logins.logins + EXCLUDED.logins;

100

แก้ไข: ในกรณีที่คุณไม่ได้รับคำตอบจากวอร์เรนตอนนี้ PG9.5 จะมีคุณสมบัตินี้ ถึงเวลาอัพเกรดแล้ว!


การสร้างคำตอบของ Bill Karwin เพื่อค้นหาว่าวิธีการตามกฎจะมีลักษณะอย่างไร (ถ่ายโอนจากสคีอื่นในฐานข้อมูลเดียวกันและมีคีย์หลักแบบหลายคอลัมน์):

CREATE RULE "my_table_on_duplicate_ignore" AS ON INSERT TO "my_table"
  WHERE EXISTS(SELECT 1 FROM my_table 
                WHERE (pk_col_1, pk_col_2)=(NEW.pk_col_1, NEW.pk_col_2))
  DO INSTEAD NOTHING;
INSERT INTO my_table SELECT * FROM another_schema.my_table WHERE some_cond;
DROP RULE "my_table_on_duplicate_ignore" ON "my_table";

หมายเหตุ: กฎนี้ใช้กับINSERTการดำเนินการทั้งหมดจนกว่ากฎจะถูกดร็อปดังนั้นจึงไม่ได้เป็นแบบเฉพาะกิจ


@sema คุณหมายถึงถ้าanother_schema.my_tableมีซ้ำกันตามข้อ จำกัด ของmy_table?
EoghanM

2
@EoghanM ฉันทดสอบกฎใน postgresql 9.3 และยังสามารถแทรกรายการที่ซ้ำกันด้วยคำสั่งแทรกหลายแถวเช่น INSERT INTO "my_table" (a, b), (a, b); (สมมติว่าแถว (a, b) ยังไม่มีใน "my_table")
sema

@sema, gotcha - นั่นต้องหมายความว่ากฎจะถูกดำเนินการเมื่อเริ่มต้นข้อมูลทั้งหมดที่จะถูกแทรกและไม่ดำเนินการอีกครั้งหลังจากแทรกแต่ละแถวแล้ว วิธีหนึ่งคือการแทรกข้อมูลของคุณลงในตารางชั่วคราวอื่นก่อนซึ่งไม่มีข้อ จำกัด ใด ๆ จากนั้นทำINSERT INTO "my_table" SELECT DISTINCT ON (pk_col_1, pk_col_2) * FROM the_tmp_table;
EoghanM

@EoghanM อีกวิธีหนึ่งคือการผ่อนคลายข้อ จำกัด ที่ซ้ำกันชั่วคราวและยอมรับรายการที่ซ้ำกันในส่วนแทรก แต่ลบรายการที่ซ้ำกันออกภายหลังด้วยDELETE FROM my_table WHERE ctid IN (SELECT ctid FROM (SELECT ctid,ROW_NUMBER() OVER (PARTITION BY pk_col_1,pk_col_2) AS rn FROM my_table) AS dups WHERE dups.rn > 1);
sema

ฉันมีปัญหาที่อธิบายโดย @sema ถ้าฉันแทรก (a, b), (a, b) มันจะพ่นข้อผิดพลาด มีวิธีระงับข้อผิดพลาดหรือไม่ในกรณีนี้
Diogo Melo

35

สำหรับบรรดาของคุณที่มี Postgres 9.5 หรือสูงกว่าไวยากรณ์ON ON ความขัดแย้งใหม่ที่ไม่มีอะไรควรทำงาน:

INSERT INTO target_table (field_one, field_two, field_three ) 
SELECT field_one, field_two, field_three
FROM source_table
ON CONFLICT (field_one) DO NOTHING;

สำหรับพวกเราที่มีรุ่นก่อนหน้าการเข้าร่วมที่ถูกต้องนี้จะทำงานแทน:

INSERT INTO target_table (field_one, field_two, field_three )
SELECT source_table.field_one, source_table.field_two, source_table.field_three
FROM source_table 
LEFT JOIN target_table ON source_table.field_one = target_table.field_one
WHERE target_table.field_one IS NULL;

วิธีที่สองไม่ทำงานเมื่อทำการแทรกขนาดใหญ่ในสภาพแวดล้อมพร้อมกัน คุณจะได้รับUnique violation: 7 ERROR: duplicate key value violates unique constraintเมื่อtarget_tableมีแถวอื่นแทรกลงในขณะที่แบบสอบถามนี้จะถูกดำเนินการถ้าคีย์ของพวกเขาแน่นอนซ้ำกัน ฉันเชื่อว่าการล็อกtarget_tableจะช่วยได้ แต่การเห็นพ้องด้วยจะประสบ
G. Kashtanov

1
ON CONFLICT (field_one) DO NOTHINGเป็นส่วนที่ดีที่สุดของคำตอบ
Abel Callejo

24

ในการรับตรรกะการเพิกเฉยต่อการแทรกคุณสามารถทำสิ่งต่อไปนี้ ฉันพบว่าการแทรกจากคำสั่ง select ของค่าตัวอักษรทำงานได้ดีที่สุดจากนั้นคุณสามารถซ่อนคีย์ที่ซ้ำกันโดยใช้ประโยค NO EXISTS หากต้องการรับการอัพเดตเกี่ยวกับลอจิกที่ซ้ำกันฉันสงสัยว่าจะต้องมีการวนซ้ำ pl / pgsql

INSERT INTO manager.vin_manufacturer
(SELECT * FROM( VALUES
  ('935',' Citroën Brazil','Citroën'),
  ('ABC', 'Toyota', 'Toyota'),
  ('ZOM',' OM','OM')
  ) as tmp (vin_manufacturer_id, manufacturer_desc, make_desc)
  WHERE NOT EXISTS (
    --ignore anything that has already been inserted
    SELECT 1 FROM manager.vin_manufacturer m where m.vin_manufacturer_id = tmp.vin_manufacturer_id)
)

จะเกิดอะไรขึ้นหาก tmp มีแถวที่ซ้ำกันซึ่งสามารถเกิดขึ้นได้
Henley Chiu

คุณสามารถเลือกด้วยคำหลักที่แตกต่างกันได้เสมอ
Keyo

5
เช่นเดียวกับ FYI เคล็ดลับ "ที่ไม่มีอยู่" ไม่ทำงานในหลายธุรกรรมเนื่องจากธุรกรรมต่าง ๆ ไม่สามารถมองเห็นข้อมูลที่เพิ่มเข้ามาใหม่จากธุรกรรมอื่น ๆ
Dave Johansen

21
INSERT INTO mytable(col1,col2) 
    SELECT 'val1','val2' 
    WHERE NOT EXISTS (SELECT 1 FROM mytable WHERE col1='val1')

อะไรคือผลกระทบของการทำธุรกรรมหลายรายการที่พยายามทำสิ่งเดียวกัน เป็นไปได้ไหมว่าระหว่างที่ที่ไม่มีการดำเนินการอยู่และการแทรกการดำเนินการธุรกรรมอื่นแทรกแถว? และหาก Postgres สามารถป้องกันปัญหานั้นได้ Postgres จะไม่แนะนำการซิงโครไนซ์ข้ามธุรกรรมทั้งหมดเมื่อพวกเขาทำสิ่งนี้
Καrτhικ

สิ่งนี้ใช้ไม่ได้กับหลายธุรกรรมเนื่องจากข้อมูลใหม่ที่เพิ่มเข้ามาไม่สามารถมองเห็นได้
Dave Johansen

12

ดูเหมือน PostgreSQL สนับสนุนวัตถุคีมาที่เรียกว่ากฎ

http://www.postgresql.org/docs/current/static/rules-update.html

คุณสามารถสร้างกฎON INSERTสำหรับตารางที่กำหนดทำให้มันเกิดขึ้นNOTHINGถ้าแถวมีอยู่ด้วยค่าคีย์หลักที่กำหนดหรือมิฉะนั้นทำให้มันทำUPDATEแทนINSERTถ้าแถวมีอยู่ด้วยค่าคีย์หลักที่กำหนด

ฉันไม่ได้ลองตัวเองดังนั้นฉันจึงไม่สามารถพูดจากประสบการณ์หรือเสนอตัวอย่าง


1
หากฉันเข้าใจดีว่ากฎเหล่านี้เป็นตัวกระตุ้นให้ทำงานทุกครั้งที่มีการเรียกใช้คำสั่ง จะเป็นอย่างไรถ้าฉันต้องการใช้กฎสำหรับแบบสอบถามเดียวเท่านั้น ฉันต้องสร้างกฎแล้วทำลายมันทันทีหรือไม่ (แล้วสภาพการแข่งขันล่ะ)
gpilotino

3
ใช่ฉันมีคำถามเดียวกันเช่นกัน กลไกการปกครองเป็นสิ่งที่ใกล้เคียงที่สุดที่ฉันสามารถหาได้ใน PostgreSQL ไปยัง INSERT IGNORE ของ MySQL หรือในการอัปเดตรหัสซ้ำ หากเรา google สำหรับ "postgresql จากการอัปเดตคีย์ซ้ำ" คุณจะพบว่ามีคนอื่นแนะนำกลไก Rule ถึงแม้ว่า Rule จะใช้กับ INSERT ใด ๆ ไม่ใช่เฉพาะใน Ad Hoc เท่านั้น
Bill Karwin

4
PostgreSQL สนับสนุน DDL ของทรานแซคชันซึ่งหมายความว่าหากคุณสร้างกฎและวางไว้ในธุรกรรมเดียวกฎจะไม่ปรากฏให้เห็นภายนอก (และดังนั้นจะไม่มีผลใด ๆ ภายนอก) ธุรกรรมนั้น
cdhowie

6

ในฐานะที่เป็น @hanmari กล่าวถึงในความคิดเห็นของเขา เมื่อแทรกลงในตาราง postgres, on ขัดแย้ง (.. ) ทำอะไรเป็นรหัสที่ดีที่สุดที่จะใช้สำหรับการไม่ใส่ข้อมูลที่ซ้ำกัน:

query = "INSERT INTO db_table_name(column_name)
         VALUES(%s) ON CONFLICT (column_name) DO NOTHING;"

บรรทัดรหัส ON ON ความขัดแย้งจะช่วยให้คำสั่งแทรกยังคงแทรกแถวของข้อมูล แบบสอบถามและรหัสค่าเป็นตัวอย่างของวันที่แทรกจาก Excel ลงในตาราง postgres db ฉันมีข้อ จำกัด ที่เพิ่มลงในตาราง postgres ที่ฉันใช้เพื่อให้แน่ใจว่าฟิลด์ ID นั้นไม่ซ้ำกัน แทนที่จะเรียกใช้การลบแถวของข้อมูลที่เหมือนกันฉันเพิ่มบรรทัดของรหัส sql ที่สร้างหมายเลขคอลัมน์เริ่มต้นที่ 1 ตัวอย่าง:

q = 'ALTER id_column serial RESTART WITH 1'

หากข้อมูลของฉันมีฟิลด์ ID ฉันจะไม่ใช้สิ่งนี้เป็น ID หลัก / รหัสประจำตัวฉันจะสร้างคอลัมน์ ID และฉันตั้งเป็นซีเรียล ฉันหวังว่าข้อมูลนี้จะเป็นประโยชน์กับทุกคน * ฉันไม่มีวุฒิปริญญาตรีด้านการพัฒนา / เข้ารหัสซอฟต์แวร์ ทุกสิ่งที่ฉันรู้ในการเขียนโปรแกรมฉันศึกษาด้วยตัวเอง


สิ่งนี้ใช้ไม่ได้กับดัชนีที่ไม่ซ้ำกันแบบรวม!
Nulik

4

วิธีนี้หลีกเลี่ยงการใช้กฎ:

BEGIN
   INSERT INTO tableA (unique_column,c2,c3) VALUES (1,2,3);
EXCEPTION 
   WHEN unique_violation THEN
     UPDATE tableA SET c2 = 2, c3 = 3 WHERE unique_column = 1;
END;

แต่มีข้อเสียเปรียบด้านประสิทธิภาพ (ดูPostgreSQL.org ):

บล็อกที่มีส่วนข้อยกเว้นมีราคาแพงกว่าการเข้าและออกมากกว่าบล็อกที่ไม่มี ดังนั้นอย่าใช้ข้อยกเว้นโดยไม่จำเป็น


1

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


2
วิธีการนี้จะค่อนข้างมีแนวโน้มที่จะเงื่อนไขการแข่งขันแปลกที่ผมไม่อยากจะแนะนำ ...
สตีเว่น Schlansker

1
+1 สิ่งนี้ง่ายและทั่วไป หากใช้ด้วยความระมัดระวังสิ่งนี้อาจเป็นทางออกที่ง่าย
Wouter van Nifterick

1
มันจะไม่ทำงานเมื่อมีการเปลี่ยนแปลงข้อมูลที่มีอยู่แล้ว (แต่ไม่ใช่ในคีย์ที่ซ้ำกัน) และเราต้องการที่จะอัปเดต นี่คือสถานการณ์จำลองเมื่อมีสคริปต์ SQL ที่เขียนขึ้นสำหรับระบบที่แตกต่างกันเล็กน้อยเช่นการอัพเดตฐานข้อมูลที่ทำงานกับการผลิต, QA, dev และระบบทดสอบ
Hanno Fietz

1
foreign key อาจไม่ใช่ปัญหาหากคุณสร้างด้วยDEFERRABLE INITIALLY DEFERREDค่าสถานะ
temoto

-1

สำหรับสคริปต์การนำเข้าข้อมูลหากต้องการแทนที่ "IF NOT EXISTS" จะมีสูตรที่ไม่แน่นอนอยู่บ้างซึ่งยังคงใช้งานได้:

DO
$do$
BEGIN
PERFORM id
FROM whatever_table;

IF NOT FOUND THEN
-- INSERT stuff
END IF;
END
$do$;
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.