วิธีใช้ RETURNING ด้วย ON CONFLICT ใน PostgreSQL


149

ฉันมี UPSERT ต่อไปนี้ใน PostgreSQL 9.5:

INSERT INTO chats ("user", "contact", "name") 
           VALUES ($1, $2, $3), 
                  ($2, $1, NULL) 
ON CONFLICT("user", "contact") DO NOTHING
RETURNING id;

หากไม่มีข้อขัดแย้งมันจะคืนค่าดังนี้:

----------
    | id |
----------
  1 | 50 |
----------
  2 | 51 |
----------

แต่ถ้ามีข้อขัดแย้งมันจะไม่ส่งคืนแถวใด ๆ :

----------
    | id |
----------

ฉันต้องการส่งคืนidคอลัมน์ใหม่หากไม่มีความขัดแย้งหรือส่งคืนidคอลัมน์ที่มีอยู่ของคอลัมน์ที่ขัดแย้งกัน
สามารถทำได้หรือไม่ ถ้าเป็นเช่นนั้นได้อย่างไร


1
ใช้ON CONFLICT UPDATEเพื่อให้มีการเปลี่ยนแปลงในแถว จากนั้นRETURNINGจะจับภาพ
Gordon Linoff

1
@GordonLinoff จะเกิดอะไรขึ้นถ้าไม่มีอะไรจะอัพเดท?
Okku

1
หากไม่มีสิ่งใดที่จะอัปเดตก็หมายความว่าไม่มีข้อขัดแย้งดังนั้นมันจึงแทรกค่าใหม่และส่งคืน id ของพวกเขา
โซลา

1
คุณจะพบวิธีการอื่น ๆที่นี่ ฉันชอบที่จะทราบความแตกต่างระหว่างทั้งสองในแง่ของประสิทธิภาพแม้ว่า
Stanislasdrg Reinstate Monica

คำตอบ:


88

ฉันมีปัญหาเดียวกันทั้งหมดและฉันแก้ไขโดยใช้ 'ปรับปรุง' แทน 'ไม่ทำอะไรเลย' แม้ว่าฉันจะไม่มีอะไรจะอัพเดท ในกรณีของคุณมันจะเป็นแบบนี้:

INSERT INTO chats ("user", "contact", "name") 
       VALUES ($1, $2, $3), 
              ($2, $1, NULL) 
ON CONFLICT("user", "contact") DO UPDATE SET name=EXCLUDED.name RETURNING id;

แบบสอบถามนี้จะส่งคืนแถวทั้งหมดโดยไม่คำนึงว่าเพิ่งแทรกหรือมีอยู่ก่อนหน้า


11
ปัญหาอย่างหนึ่งของวิธีนี้คือหมายเลขลำดับของคีย์หลักจะเพิ่มขึ้นตามความขัดแย้งทุกครั้ง (การอัพเดตปลอม) ซึ่งโดยทั่วไปหมายความว่าคุณอาจจบลงด้วยช่องว่างขนาดใหญ่ในลำดับ ความคิดใด ๆ วิธีการหลีกเลี่ยงที่?
Mischa

9
@Mischa: อะไรนะ ลำดับจะไม่รับประกันว่าจะไม่มีช่องว่างในสถานที่แรกและช่องว่างไม่สำคัญ (และถ้าพวกเขาทำลำดับเป็นสิ่งที่ผิดที่ต้องทำ)
a_horse_with_no_name

24
ฉันจะไม่แนะนำให้ใช้ในกรณีส่วนใหญ่ ฉันเพิ่มคำตอบว่าทำไม
Erwin Brandstetter

4
คำตอบนี้ไม่ปรากฏเพื่อให้ได้มาซึ่งDO NOTHINGลักษณะของคำถามต้นฉบับ - สำหรับฉันดูเหมือนว่าจะอัปเดตฟิลด์ที่ไม่ขัดแย้ง (ที่นี่ "ชื่อ") สำหรับทุกแถว
PeterJCLaw

ดังที่อธิบายไว้ในคำตอบที่ยาวมากด้านล่างการใช้ "การอัพเดท" สำหรับฟิลด์ที่ไม่ได้เปลี่ยนไม่ใช่วิธีการ "สะอาด" และอาจทำให้เกิดปัญหาอื่น ๆ
Bill Worthington

202

คำตอบที่ได้รับการยอมรับในขณะนี้ดูเหมือนว่า ok สำหรับเป้าหมายความขัดแย้งเดียวความขัดแย้งไม่กี่ tuples ขนาดเล็กและไม่มีการเรียก มันหลีกเลี่ยงปัญหาการเกิดพร้อมกัน 1 (ดูด้านล่าง) ด้วยกำลังดุร้าย ทางออกที่ง่ายมีการอุทธรณ์ผลข้างเคียงอาจมีความสำคัญน้อยกว่า

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

  • อาจเป็นสาเหตุของไฟที่ไม่ควรถูกยิง

  • มันเขียนล็อคแถว "ผู้บริสุทธิ์" ซึ่งอาจมีค่าใช้จ่ายที่เกิดขึ้นสำหรับการทำธุรกรรมพร้อมกัน

  • อาจทำให้แถวดูเหมือนใหม่แม้ว่าจะเก่าแล้ว (การประทับเวลาของธุรกรรม)

  • สิ่งสำคัญที่สุดคือด้วยโมเดล MVCC ของ PostgreSQL รุ่นแถวใหม่จะถูกเขียนขึ้นสำหรับทุก ๆ คนUPDATEไม่ว่าข้อมูลแถวจะเปลี่ยนไปก็ตาม สิ่งนี้ทำให้เกิดการลงโทษประสิทธิภาพสำหรับ UPSERT เอง, ตารางขยาย, ดัชนีขยายตัว, ค่าปรับประสิทธิภาพสำหรับการดำเนินการตามตารางVACUUMราคา ผลเล็กน้อยสำหรับการทำซ้ำเล็กน้อย แต่มีขนาดใหญ่สำหรับการดักจับส่วนใหญ่

พลัสON CONFLICT DO UPDATEบางครั้งมันไม่จริงหรือแม้กระทั่งไปได้ที่จะใช้ คู่มือ:

สำหรับON CONFLICT DO UPDATEที่conflict_targetจะต้องให้

เดียว "เป้าหมายของความขัดแย้ง" เป็นไปไม่ได้ถ้าดัชนีหลาย / ข้อ จำกัด ที่มีส่วนเกี่ยวข้อง

คุณสามารถทำได้ (เกือบ) เหมือนกันโดยไม่มีการอัพเดตว่างเปล่าและผลข้างเคียง บางส่วนของการแก้ปัญหาต่อไปนี้ยังทำงานร่วมกับON CONFLICT DO NOTHING(ไม่ "เป้าหมายของความขัดแย้ง") ที่จะจับทุกความขัดแย้งที่เป็นไปได้ที่อาจเกิดขึ้น - ซึ่งอาจจะหรืออาจจะไม่เป็นที่น่าพอใจ

ไม่มีโหลดการเขียนพร้อมกัน

WITH input_rows(usr, contact, name) AS (
   VALUES
      (text 'foo1', text 'bar1', text 'bob1')  -- type casts in first row
    , ('foo2', 'bar2', 'bob2')
    -- more?
   )
, ins AS (
   INSERT INTO chats (usr, contact, name) 
   SELECT * FROM input_rows
   ON CONFLICT (usr, contact) DO NOTHING
   RETURNING id  --, usr, contact              -- return more columns?
   )
SELECT 'i' AS source                           -- 'i' for 'inserted'
     , id  --, usr, contact                    -- return more columns?
FROM   ins
UNION  ALL
SELECT 's' AS source                           -- 's' for 'selected'
     , c.id  --, usr, contact                  -- return more columns?
FROM   input_rows
JOIN   chats c USING (usr, contact);           -- columns of unique index

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

การJOIN chatsทำงานขั้นสุดท้ายเพราะแทรกแถวใหม่จากCTE การแก้ไขข้อมูลที่แนบมายังไม่สามารถมองเห็นได้ในตารางต้นแบบ (ทุกส่วนของคำสั่ง SQL เดียวกันดูสแน็ปช็อตเดียวกันของตารางต้นแบบ)

เนื่องจากการVALUESแสดงออกเป็นแบบอิสระ (ไม่ได้แนบโดยตรงกับINSERT) Postgres จึงไม่สามารถรับชนิดข้อมูลจากคอลัมน์เป้าหมายและคุณอาจต้องเพิ่มประเภทการส่งแบบชัดแจ้ง คู่มือ:

เมื่อVALUESมีการใช้งานINSERTค่าต่างๆจะถูกรวมเข้ากับประเภทข้อมูลของคอลัมน์ปลายทางที่เกี่ยวข้องโดยอัตโนมัติ เมื่อมันถูกใช้ในบริบทอื่น ๆ มันอาจจำเป็นต้องระบุชนิดข้อมูลที่ถูกต้อง หากรายการเป็นค่าคงที่ตัวอักษรที่ยกมาทั้งหมดการข่มขู่ครั้งแรกก็เพียงพอที่จะกำหนดประเภทสันนิษฐานสำหรับทั้งหมด

แบบสอบถามเอง (ไม่นับผลข้างเคียง) อาจมีราคาแพงกว่าเล็กน้อยสำหรับdupe สองสามอันเนื่องจากค่าโสหุ้ยของ CTE และเพิ่มเติมSELECT(ซึ่งควรมีราคาถูกเนื่องจากดัชนีที่สมบูรณ์แบบจะมีตามคำจำกัดความ - ข้อ จำกัด เฉพาะถูกนำมาใช้กับ ดัชนี)

อาจเร็วขึ้นสำหรับการทำซ้ำหลายรายการ ค่าใช้จ่ายที่มีประสิทธิภาพของการเขียนเพิ่มเติมขึ้นอยู่กับหลายปัจจัย

แต่มีผลข้างเคียงน้อยลงและมีค่าใช้จ่ายแอบแฝงในทุกกรณี มันอาจถูกกว่ามาก

ลำดับที่แนบมานั้นยังคงขั้นสูงเนื่องจากค่าเริ่มต้นจะถูกกรอกก่อนที่จะทดสอบความขัดแย้ง

เกี่ยวกับ CTEs:

พร้อมโหลดการเขียนพร้อมกัน

สมมติว่าเริ่มต้นแยกรายการREAD COMMITTED ที่เกี่ยวข้อง:

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

ปัญหาการเกิดพร้อมกัน 1

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

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

หากธุรกรรมอื่นสิ้นสุดลงตามปกติ (โดยนัยหรือชัดเจนCOMMIT) คุณINSERTจะตรวจพบข้อขัดแย้ง ( UNIQUEดัชนี / ข้อ จำกัด นั้นแน่นอน) และDO NOTHINGดังนั้นจะไม่ส่งคืนแถว (และไม่สามารถล็อกแถวดังที่แสดงในปัญหาการทำงานพร้อมกันที่ 2ด้านล่างเนื่องจากไม่สามารถมองเห็นได้ ) การSELECTดูสแนปชอตเดียวกันจากจุดเริ่มต้นของแบบสอบถามและยังไม่สามารถส่งคืนแถวที่มองไม่เห็นได้

แถวใดหายไปจากชุดผลลัพธ์ (แม้ว่าจะมีอยู่ในตารางที่อยู่ข้างใต้)!

นี้อาจจะตกลงเป็น โดยเฉพาะอย่างยิ่งถ้าคุณไม่คืนแถวเหมือนในตัวอย่างและพอใจที่รู้ว่าแถวนั้นอยู่ที่นั่น หากยังไม่ดีพอมีหลายวิธีที่อยู่รอบ ๆ

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

หรือตรวจสอบการขาดหายไปแถวผลภายในแบบสอบถามเดียวกันและเขียนทับผู้ที่มีเคล็ดลับแรงเดรัจฉานแสดงให้เห็นในคำตอบของ Alextoni

WITH input_rows(usr, contact, name) AS ( ... )  -- see above
, ins AS (
   INSERT INTO chats AS c (usr, contact, name) 
   SELECT * FROM input_rows
   ON     CONFLICT (usr, contact) DO NOTHING
   RETURNING id, usr, contact                   -- we need unique columns for later join
   )
, sel AS (
   SELECT 'i'::"char" AS source                 -- 'i' for 'inserted'
        , id, usr, contact
   FROM   ins
   UNION  ALL
   SELECT 's'::"char" AS source                 -- 's' for 'selected'
        , c.id, usr, contact
   FROM   input_rows
   JOIN   chats c USING (usr, contact)
   )
, ups AS (                                      -- RARE corner case
   INSERT INTO chats AS c (usr, contact, name)  -- another UPSERT, not just UPDATE
   SELECT i.*
   FROM   input_rows i
   LEFT   JOIN sel   s USING (usr, contact)     -- columns of unique index
   WHERE  s.usr IS NULL                         -- missing!
   ON     CONFLICT (usr, contact) DO UPDATE     -- we've asked nicely the 1st time ...
   SET    name = c.name                         -- ... this time we overwrite with old value
   -- SET name = EXCLUDED.name                  -- alternatively overwrite with *new* value
   RETURNING 'u'::"char" AS source              -- 'u' for updated
           , id  --, usr, contact               -- return more columns?
   )
SELECT source, id FROM sel
UNION  ALL
TABLE  ups;

มันเหมือนกับแบบสอบถามด้านบน แต่เราเพิ่มอีกหนึ่งขั้นตอนกับ CTE upsก่อนที่เราจะส่งคืนชุดผลลัพธ์ที่สมบูรณ์ CTE สุดท้ายนั้นจะไม่ทำอะไรส่วนใหญ่ เฉพาะในกรณีที่แถวหายไปจากผลตอบแทนที่เราใช้แรงเดรัจฉาน

ค่าใช้จ่ายเพิ่มเติมยัง ยิ่งความขัดแย้งกับแถวที่มีอยู่แล้วมีความเป็นไปได้สูงที่วิธีนี้จะมีประสิทธิภาพมากกว่าวิธีง่าย ๆ

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

ปัญหาการเกิดพร้อมกัน 2

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

...
ON CONFLICT (usr, contact) DO UPDATE
SET name = name WHERE FALSE  -- never executed, but still locks the row
...

และเพิ่มข้อล็อคไปSELECTFOR UPDATEด้วยเช่น

สิ่งนี้ทำให้การดำเนินการเขียนแบบแข่งขันรอจนถึงจุดสิ้นสุดของธุรกรรมเมื่อการล็อกทั้งหมดได้รับการปล่อยตัว ดังนั้นจะสั้น

รายละเอียดเพิ่มเติมและคำอธิบาย:

deadlocks?

ป้องกันการติดตายโดยการแทรกแถวในการสั่งซื้อที่สอดคล้องกัน ดู:

ชนิดข้อมูลและการปลดเปลื้อง

ตารางที่มีอยู่เป็นเทมเพลตสำหรับชนิดข้อมูล ...

ประเภทที่ชัดเจนได้ปลดเปลื้องสำหรับแถวแรกของข้อมูลในVALUESนิพจน์ยืนฟรีอาจไม่สะดวก มีวิธีการอยู่รอบ ๆ คุณสามารถใช้ความสัมพันธ์ใด ๆ ที่มีอยู่ (ตารางมุมมอง ... ) เป็นแม่แบบแถว ตารางเป้าหมายเป็นตัวเลือกที่ชัดเจนสำหรับกรณีการใช้งาน ข้อมูลอินพุตถูกบีบบังคับให้เป็นชนิดที่เหมาะสมโดยอัตโนมัติเช่นในVALUESข้อของINSERT:

WITH input_rows AS (
  (SELECT usr, contact, name FROM chats LIMIT 0)  -- only copies column names and types
   UNION ALL
   VALUES
      ('foo1', 'bar1', 'bob1')  -- no type casts here
    , ('foo2', 'bar2', 'bob2')
   )
   ...

สิ่งนี้ใช้ไม่ได้กับข้อมูลบางประเภท ดู:

... และชื่อ

ใช้ได้กับทุกประเภทข้อมูล

ขณะแทรกลงในคอลัมน์ (นำหน้า) ทั้งหมดของตารางคุณสามารถละเว้นชื่อคอลัมน์ สมมติว่าตารางchatsในตัวอย่างประกอบด้วย 3 คอลัมน์ที่ใช้ใน UPSERT เท่านั้น:

WITH input_rows AS (
   SELECT * FROM (
      VALUES
      ((NULL::chats).*)         -- copies whole row definition
      ('foo1', 'bar1', 'bob1')  -- no type casts needed
    , ('foo2', 'bar2', 'bob2')
      ) sub
   OFFSET 1
   )
   ...

นอกเหนือ: อย่าใช้คำสงวนเช่นเดียว"user"กับตัวระบุ นั่นคือปืนพกที่โหลด ใช้ตัวระบุที่ถูกต้องตัวพิมพ์เล็กและตัวใหญ่ usrฉันแทนที่มันด้วย


2
คุณบอกเป็นนัยว่าวิธีนี้จะไม่สร้างช่องว่างใน serials แต่เป็น: INSERT ... ON ความขัดแย้งไม่เพิ่มอนุกรมในแต่ละครั้งจากสิ่งที่ฉันเห็น
อันตราย

1
ไม่ใช่ว่ามันสำคัญมาก แต่ทำไม serials จึงเพิ่มขึ้น? และไม่มีวิธีที่จะหลีกเลี่ยงปัญหานี้หรือไม่?
เด่นชัด

1
@salient: เช่นเดียวกับที่ฉันเพิ่มข้างต้น: ค่าเริ่มต้นของคอลัมน์จะถูกกรอกก่อนที่การทดสอบความขัดแย้งและลำดับจะไม่ย้อนกลับเพื่อหลีกเลี่ยงความขัดแย้งกับการเขียนพร้อมกัน
Erwin Brandstetter

7
เหลือเชื่อ ทำงานเหมือนจับใจและเข้าใจง่ายเมื่อคุณมองอย่างระมัดระวัง ฉันยังหวังว่าจะON CONFLICT SELECT...มีบางสิ่งที่ :)
Roshambo

3
เหลือเชื่อ ผู้สร้าง Postgres ดูเหมือนจะทรมานผู้ใช้ ทำไมไม่เพียงแค่ส่งคืนข้อมักจะคืนค่าเสมอโดยไม่คำนึงว่ามีส่วนแทรกหรือไม่?
Anatoly Alekseev

16

Upsert การเป็นส่วนหนึ่งของINSERTแบบสอบถามสามารถกำหนดได้ด้วยสองพฤติกรรมที่แตกต่างกันในกรณีของความขัดแย้ง จำกัด : หรือDO NOTHINGDO UPDATE

INSERT INTO upsert_table VALUES (2, 6, 'upserted')
   ON CONFLICT DO NOTHING RETURNING *;

 id | sub_id | status
----+--------+--------
 (0 rows)

ทราบเช่นกันว่าRETURNINGไม่มีผลตอบแทนเนื่องจากไม่มีการแทรกสิ่งอันดับ ขณะนี้มีความDO UPDATEเป็นไปได้ที่จะดำเนินการกับ tuple ที่มีข้อขัดแย้ง โปรดทราบว่าสิ่งสำคัญคือต้องกำหนดข้อ จำกัด ซึ่งจะใช้เพื่อกำหนดว่ามีข้อขัดแย้ง

INSERT INTO upsert_table VALUES (2, 2, 'inserted')
   ON CONFLICT ON CONSTRAINT upsert_table_sub_id_key
   DO UPDATE SET status = 'upserted' RETURNING *;

 id | sub_id |  status
----+--------+----------
  2 |      2 | upserted
(1 row)

2
เป็นวิธีที่ดีในการรับ id แถวที่ได้รับผลกระทบและรู้ว่ามันคือการแทรกหรือการเพิ่ม สิ่งที่ฉันต้องการ
Moby Duck

สิ่งนี้ยังคงใช้ "Do Update" ซึ่งมีการพูดถึงข้อเสียอยู่แล้ว
Bill Worthington

4

สำหรับการแทรกรายการเดียวฉันอาจจะใช้การรวมกันเมื่อส่งคืนรหัส:

WITH new_chats AS (
    INSERT INTO chats ("user", "contact", "name")
    VALUES ($1, $2, $3)
    ON CONFLICT("user", "contact") DO NOTHING
    RETURNING id
) SELECT COALESCE(
    (SELECT id FROM new_chats),
    (SELECT id FROM chats WHERE user = $1 AND contact = $2)
);

2
WITH e AS(
    INSERT INTO chats ("user", "contact", "name") 
           VALUES ($1, $2, $3), 
                  ($2, $1, NULL) 
    ON CONFLICT("user", "contact") DO NOTHING
    RETURNING id
)
SELECT * FROM e
UNION
    SELECT id FROM chats WHERE user=$1, contact=$2;

วัตถุประสงค์หลักของการใช้ON CONFLICT DO NOTHINGคือการหลีกเลี่ยงข้อผิดพลาดในการขว้างปา แต่จะทำให้ไม่มีการส่งคืนแถว ดังนั้นเราต้องการอีกอันSELECTเพื่อรับรหัสที่มีอยู่

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


โซลูชันนี้ทำงานได้ดีและหลีกเลี่ยงการเขียน (อัพเดต) ที่ไม่จำเป็นไปยังฐานข้อมูล !! ดี!
Simon C

0

ฉันแก้ไขคำตอบที่น่าอัศจรรย์โดย Erwin Brandstetter ซึ่งจะไม่เพิ่มลำดับและจะไม่ล็อคการเขียนแถวใด ๆ ฉันค่อนข้างใหม่กับ PostgreSQL ดังนั้นโปรดแจ้งให้เราทราบหากคุณเห็นข้อบกพร่องของวิธีนี้:

WITH input_rows(usr, contact, name) AS (
   VALUES
      (text 'foo1', text 'bar1', text 'bob1')  -- type casts in first row
    , ('foo2', 'bar2', 'bob2')
    -- more?
   )
, new_rows AS (
   SELECT 
     c.usr
     , c.contact
     , c.name
     , r.id IS NOT NULL as row_exists
   FROM input_rows AS r
   LEFT JOIN chats AS c ON r.usr=c.usr AND r.contact=c.contact
   )
INSERT INTO chats (usr, contact, name)
SELECT usr, contact, name
FROM new_rows
WHERE NOT row_exists
RETURNING id, usr, contact, name

นี้อนุมานว่าตารางchatsมีข้อ จำกัด (usr, contact)ที่ไม่ซ้ำกันในคอลัมน์

อัปเดต: เพิ่มการแก้ไขที่แนะนำจากspatar (ด้านล่าง) ขอบคุณ!


1
แทนที่จะเป็นเพียงแค่การเขียนCASE WHEN r.id IS NULL THEN FALSE ELSE TRUE END AS row_exists r.id IS NOT NULL as row_existsแทนที่จะเป็นเพียงแค่การเขียนWHERE row_exists=FALSE WHERE NOT row_exists
spatar
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.