ใช้หลายเป้าหมายข้อขัดแย้งในคำสั่ง ON CONFLICT


101

ฉันมีสองคอลัมน์ในตารางcol1, col2พวกเขาทั้งสองจะไม่ซ้ำกันจัดทำดัชนี (col1 จะไม่ซ้ำกันและเพื่อให้เป็น col2)

ฉันต้องการที่จะแทรกลงในตารางนี้ใช้ON CONFLICTไวยากรณ์และอัปเดตคอลัมน์อื่น ๆ แต่ฉันไม่สามารถใช้ทั้งสองคอลัมน์ในconflict_targetประโยคได้

มันได้ผล:

INSERT INTO table
...
ON CONFLICT ( col1 ) 
DO UPDATE 
SET 
-- update needed columns here

แต่จะทำอย่างไรกับหลายคอลัมน์เช่นนี้:

...
ON CONFLICT ( col1, col2 )
DO UPDATE 
SET 
....

4
"col1, col2 ทั้งสองมีดัชนีที่ไม่ซ้ำกัน" นั่นหมายความว่า col1 ไม่ซ้ำกันและ col2 ไม่ซ้ำกันหรือการรวมกันของ col1, col2 ไม่ซ้ำกัน?
e4c5

1
นั่นหมายความว่า col1 มีลักษณะเฉพาะและ col2 ไม่ซ้ำกันทีละรายการ
Oto Shavadze

คำตอบ:


49

ตารางตัวอย่างและข้อมูล

CREATE TABLE dupes(col1 int primary key, col2 int, col3 text,
   CONSTRAINT col2_unique UNIQUE (col2)
);

INSERT INTO dupes values(1,1,'a'),(2,2,'b');

สร้างปัญหาซ้ำ

INSERT INTO dupes values(3,2,'c')
ON CONFLICT (col1) DO UPDATE SET col3 = 'c', col2 = 2

เรียกสิ่งนี้ว่า Q1 ผลลัพธ์คือ

ERROR:  duplicate key value violates unique constraint "col2_unique"
DETAIL:  Key (col2)=(2) already exists.

สิ่งที่เอกสารกล่าวว่า

ขัดแย้ง _target สามารถทำการอนุมานดัชนีที่ไม่ซ้ำกัน เมื่อทำการอนุมานประกอบด้วยคอลัมน์ index_column_name อย่างน้อยหนึ่งคอลัมน์และ / หรือนิพจน์ index_expression และ index_predicate ซึ่งเป็นทางเลือก ดัชนีที่ไม่ซ้ำกันของ table_name ทั้งหมดที่โดยไม่คำนึงถึงลำดับมีคอลัมน์ / นิพจน์ที่ระบุข้อขัดแย้งทั้งหมดถูกอนุมาน (ถูกเลือก) เป็นดัชนีชี้ขาด หากมีการระบุ index_predicate จะต้องเป็นไปตามข้อกำหนดเพิ่มเติมสำหรับการอนุมานเพื่อให้เป็นไปตามดัชนีอนุญาโตตุลาการ

สิ่งนี้ทำให้รู้สึกว่าแบบสอบถามต่อไปนี้ควรใช้งานได้ แต่ไม่ได้เป็นเพราะจริงๆแล้วจะต้องใช้ดัชนีเฉพาะร่วมกันบน col1 และ col2 อย่างไรก็ตามดัชนีดังกล่าวไม่สามารถรับประกันได้ว่า col1 และ col2 จะไม่ซ้ำกันซึ่งเป็นหนึ่งในข้อกำหนดของ OP

INSERT INTO dupes values(3,2,'c') 
ON CONFLICT (col1,col2) DO UPDATE SET col3 = 'c', col2 = 2

เรียกแบบสอบถามนี้ว่า Q2 (สิ่งนี้ล้มเหลวด้วยข้อผิดพลาดทางไวยากรณ์)

ทำไม?

Postgresql ทำงานในลักษณะนี้เป็นเพราะสิ่งที่ควรเกิดขึ้นเมื่อความขัดแย้งเกิดขึ้นในคอลัมน์ที่สองไม่ได้กำหนดไว้อย่างดี มีจำนวนของความเป็นไปได้ ตัวอย่างเช่นในแบบสอบถาม Q1 ข้างต้นควรอัพเดต postgresql col1เมื่อมีข้อขัดแย้งบนcol2? แต่ถ้านั่นนำไปสู่ความขัดแย้งอีกcol1ล่ะ? postgresql คาดว่าจะจัดการอย่างไร

วิธีแก้ปัญหา

วิธีการแก้ปัญหาคือการรวมเกี่ยวกับความขัดแย้งกับUpsert สมัยเก่า

CREATE OR REPLACE FUNCTION merge_db(key1 INT, key2 INT, data TEXT) RETURNS VOID AS
$$
BEGIN
    LOOP
        -- first try to update the key
        UPDATE dupes SET col3 = data WHERE col1 = key1 and col2 = key2;
        IF found THEN
            RETURN;
        END IF;

        -- not there, so try to insert the key
        -- if someone else inserts the same key concurrently, or key2
        -- already exists in col2,
        -- we could get a unique-key failure
        BEGIN
            INSERT INTO dupes VALUES (key1, key2, data) ON CONFLICT (col1) DO UPDATE SET col3 = data;
            RETURN;
        EXCEPTION WHEN unique_violation THEN
            BEGIN
                INSERT INTO dupes VALUES (key1, key2, data) ON CONFLICT (col2) DO UPDATE SET col3 = data;
                RETURN;
            EXCEPTION WHEN unique_violation THEN
                -- Do nothing, and loop to try the UPDATE again.
            END;
        END;
    END LOOP;
END;
$$
LANGUAGE plpgsql;

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

SELECT merge_db(3,2,'c');
SELECT merge_db(1,2,'d');

3
นี่เป็นวิธีที่ใช้ได้ผล แต่ต้องทำงาน / ตรรกะมากกว่าที่จำเป็นเล็กน้อยสิ่งที่คุณต้องทำจริงๆคือสร้างข้อ จำกัด เฉพาะในสองคอลัมน์ ดูคำตอบของฉันด้านล่าง
Jubair

ฉันสามารถใช้โซลูชัน merge_db ได้หรือไม่หากฉันแทรก VALUES หลายชุดพร้อมกัน
daniyel

@daniyel คุณจะต้องเขียนฟังก์ชันที่เก็บไว้ใหม่
e4c5

3
ยังไม่ชัดเจนสำหรับฉันว่าการแนะนำให้ใช้ upsert แบบเก่ามีประโยชน์อย่างไร - คำถามนี้มีการอ้างอิงอย่างดีสำหรับ "postgres upsert 9.5" และอาจดีกว่าโดยอธิบายวิธีใช้กับตัวเลือก constraint_names ทั้งหมด
ปาก

3
@ Pak ไม่ชัดเจนสำหรับคุณเนื่องจากคุณไม่ได้อ่านคำถามอย่างชัดเจน op ไม่ได้มองหาคีย์ผสมในฟิลด์เหล่านั้น คำตอบอื่นใช้ได้กับคีย์คอมโพสิต
e4c5

66

ON CONFLICTต้องการดัชนีเฉพาะ * เพื่อทำการตรวจจับความขัดแย้ง ดังนั้นคุณต้องสร้างดัชนีเฉพาะในทั้งสองคอลัมน์:

t=# create table t (id integer, a text, b text);
CREATE TABLE
t=# create unique index idx_t_id_a on t (id, a);
CREATE INDEX
t=# insert into t values (1, 'a', 'foo');
INSERT 0 1
t=# insert into t values (1, 'a', 'bar') on conflict (id, a) do update set b = 'bar';
INSERT 0 1
t=# select * from t;
 id | a |  b  
----+---+-----
  1 | a | bar

* นอกจากดัชนีที่ไม่ซ้ำกันแล้วคุณยังสามารถใช้ข้อ จำกัด การยกเว้นได้อีกด้วย สิ่งเหล่านี้ค่อนข้างกว้างกว่าข้อ จำกัด เฉพาะเล็กน้อย สมมติว่าตารางของคุณมีคอลัมน์สำหรับidและvalid_time(และvalid_timeเป็น a tsrange) และคุณต้องการอนุญาตให้มีการทำซ้ำidแต่ไม่ใช่สำหรับช่วงเวลาที่ทับซ้อนกัน ข้อ จำกัด ที่ไม่เหมือนใครจะไม่ช่วยคุณได้ แต่ด้วยข้อ จำกัด ในการยกเว้นคุณสามารถพูดว่า "ยกเว้นระเบียนใหม่หากมีidค่าเท่ากับข้อมูลเก่าidและvalid_timeทับซ้อนกันvalid_time"


4
สิ่งที่สร้างคือดัชนีที่ไม่ซ้ำกันสร้างดัชนีเฉพาะ idx_t_id_a บน t (id, a); แน่นอนว่า OP ไม่ได้ระบุอย่างชัดเจนว่าทั้งสองคอลัมน์ไม่ซ้ำกันหรือรวมกัน
e4c5

ทำไมบางครั้ง postgres จึงบอกว่าไม่มีคอลัมน์ที่ตั้งชื่อตามดัชนีและไม่สามารถใช้งานได้ON CONFLICT?
ปาก

@Pak ดูเหมือนว่าคุณควรเขียนคำถามของคุณเองด้วยคำสั่งเฉพาะที่คุณใช้และข้อความแสดงข้อผิดพลาดที่คุณได้รับ
Paul A Jungwirth

@PaulAJungwirth ฉันไม่รู้คำตอบของคุณคือจุด - ดัชนีที่ไม่ซ้ำกันเป็นข้อ จำกัด สำหรับon conflictคำสั่ง ข้อผิดพลาดเป็นเพียง "คอลัมน์ my_index_name ไม่มีอยู่"
ปาก

ฉันได้ลองสิ่งนี้แล้วโดยมีข้อ จำกัด ที่ไม่ซ้ำกันแยกกันในแต่ละคอลัมน์ตามที่ OP ขอ แต่ก็ไม่ได้ผล ไม่ใช่ว่าฉันคาดหวัง แต่ฉันหวัง
sudo

6

ในปัจจุบัน (ดูเหมือน) เป็นไปไม่ได้ ทั้งเวอร์ชันสุดท้ายของON CONFLICT ไวยากรณ์ไม่อนุญาตให้ทำซ้ำประโยคหรือกับCTEก็เป็นไปได้: ไม่สามารถทำลาย INSERT จาก ON CONFLICT เพื่อเพิ่มเป้าหมายความขัดแย้งเพิ่มเติมได้



2
  1. สร้างข้อ จำกัด (เช่นดัชนีต่างประเทศ)

หรือ / และ

  1. ดูข้อ จำกัด ที่มีอยู่ (\ d ใน psq)
  2. ใช้ ON CONSTRAINT (constraint_name) ในคำสั่ง INSERT

2

วลาดมีความคิดที่ถูกต้อง

ก่อนอื่นคุณต้องสร้างข้อ จำกัด เฉพาะของตารางในคอลัมน์col1, col2 จากนั้นเมื่อคุณทำได้คุณสามารถทำสิ่งต่อไปนี้:

INSERT INTO dupes values(3,2,'c') 
ON CONFLICT ON CONSTRAINT dupes_pkey 
DO UPDATE SET col3 = 'c', col2 = 2

5
ขออภัยคุณเข้าใจผิดคำถาม OP ไม่ต้องการข้อ จำกัด ที่เป็นเอกลักษณ์ร่วมกัน
e4c5

1

ประเภทของแฮ็ก แต่ฉันแก้ไขสิ่งนี้โดยการรวมค่าสองค่าจาก col1 และ col2 ลงในคอลัมน์ใหม่ col3 (คล้ายดัชนีของทั้งสอง) และเปรียบเทียบกับค่านั้น ใช้งานได้เฉพาะเมื่อคุณต้องการให้ตรงกับทั้ง col1 และ col2

INSERT INTO table
...
ON CONFLICT ( col3 ) 
DO UPDATE 
SET 
-- update needed columns here

โดยที่ col3 = การเชื่อมต่อของค่าจาก col1 และ col2


4
คุณสามารถสร้างดัชนีที่ไม่ซ้ำสำหรับผู้ที่เสาสองและให้ข้อ จำกัด on conflictว่าใน
Kishore Relangi

0

โดยทั่วไปคุณสามารถ (ฉันคิดว่า) สร้างคำสั่งที่มีเพียงคำสั่งเดียวon conflictที่ระบุข้อ จำกัด เพียงข้อเดียวที่มีความเกี่ยวข้องสำหรับสิ่งที่คุณกำลังแทรก

เนื่องจากโดยทั่วไปข้อ จำกัด เพียงข้อเดียวคือข้อ จำกัด ที่ "เกี่ยวข้อง" ในแต่ละครั้ง (ถ้าหลายคนฉันสงสัยว่ามีอะไรแปลก ๆ / ออกแบบแปลก ๆ หรือเปล่าอืม)

ตัวอย่าง:
(ใบอนุญาต: ไม่ใช่ CC0, CC-By เท่านั้น)

// there're these unique constraints:
//   unique (site_id, people_id, page_id)
//   unique (site_id, people_id, pages_in_whole_site)
//   unique (site_id, people_id, pages_in_category_id)
// and only *one* of page-id, category-id, whole-site-true/false
// can be specified. So only one constraint is "active", at a time.

val thingColumnName = thingColumnName(notfificationPreference)

val insertStatement = s"""
  insert into page_notf_prefs (
    site_id,
    people_id,
    notf_level,
    page_id,
    pages_in_whole_site,
    pages_in_category_id)
  values (?, ?, ?, ?, ?, ?)
  -- There can be only one on-conflict clause.
  on conflict (site_id, people_id, $thingColumnName)   <—— look
  do update set
    notf_level = excluded.notf_level
  """

val values = List(
  siteId.asAnyRef,
  notfPref.peopleId.asAnyRef,
  notfPref.notfLevel.toInt.asAnyRef,
  // Only one of these is non-null:
  notfPref.pageId.orNullVarchar,
  if (notfPref.wholeSite) true.asAnyRef else NullBoolean,
  notfPref.pagesInCategoryId.orNullInt)

runUpdateSingleRow(insertStatement, values)

และ:

private def thingColumnName(notfPref: PageNotfPref): String =
  if (notfPref.pageId.isDefined)
    "page_id"
  else if (notfPref.pagesInCategoryId.isDefined)
    "pages_in_category_id"
  else if (notfPref.wholeSite)
    "pages_in_whole_site"
  else
    die("TyE2ABK057")

on conflictข้อมีการสร้างขึ้นขึ้นอยู่กับสิ่งที่ฉันพยายามที่จะทำ หากฉันกำลังแทรกการตั้งค่าการแจ้งเตือนสำหรับหน้า - อาจมีข้อขัดแย้งที่ไม่ซ้ำกันบนsite_id, people_id, page_idข้อ จำกัด และถ้าฉันกำหนดค่าการแจ้งเตือนล่วงหน้าสำหรับหมวดหมู่ - ฉันรู้ว่าข้อ จำกัด ที่สามารถละเมิดได้คือsite_id, people_id, category_idที่ได้รับการละเมิดคือ

ดังนั้นฉันสามารถและค่อนข้างเป็นไปได้ว่าคุณในกรณีของคุณสร้างสิ่งที่ถูกต้องon conflict (... columns )เพราะฉันรู้ว่าฉันต้องการทำอะไรจากนั้นฉันก็รู้ว่าข้อ จำกัด ที่ไม่ซ้ำกันข้อใดข้อหนึ่งที่สามารถละเมิดได้


-4

ON CONFLICT เป็นวิธีแก้ปัญหาที่เงอะงะมากรัน

UPDATE dupes SET key1=$1, key2=$2 where key3=$3    
if rowcount > 0    
  INSERT dupes (key1, key2, key3) values ($1,$2,$3);

ทำงานบน Oracle, Postgres และฐานข้อมูลอื่น ๆ ทั้งหมด


ไม่ใช่ปรมาณูดังนั้นอาจล้มเหลวและให้ผลลัพธ์ที่ไม่ถูกต้องในกรณีที่มีการเชื่อมต่อหลายรายการในเวลาเดียวกัน
Bogdan Mart
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.