(ฉันมาถึงคำถามนี้เมื่อพยายามค้นพบบทความเกี่ยวกับหัวข้อนี้อีกครั้งตอนนี้ฉันได้พบแล้วฉันกำลังโพสต์ไว้ที่นี่ในกรณีที่คนอื่นกำลังติดตามตัวเลือกอื่นสำหรับคำตอบที่เลือกในปัจจุบัน - หน้าต่างด้วยrow_number())
ฉันมีกรณีใช้เดียวกันนี้ สำหรับแต่ละระเบียนใส่เข้าไปในโครงการเฉพาะใน SaaS ของเราเราต้องไม่ซ้ำกันจำนวนที่เพิ่มขึ้นซึ่งสามารถสร้างขึ้นในการเผชิญกับความพร้อมกันINSERTและเป็นความนึกคิดที่ไม่มีช่องโหว่
บทความนี้จะอธิบายวิธีแก้ปัญหาที่ดีซึ่งฉันจะสรุปที่นี่เพื่อความสะดวกและลูกหลาน
- มีตารางแยกต่างหากซึ่งทำหน้าที่เป็นตัวนับเพื่อให้ค่าถัดไป มันจะมีสองคอลัมน์และ
document_id จะเป็นอีกวิธีหนึ่งถ้าคุณมีเอนทิตีที่จัดกลุ่มเวอร์ชันทั้งหมดแล้วcountercounterDEFAULT 0documentcounterอาจเพิ่มได้ 
- เพิ่ม
BEFORE INSERTทริกเกอร์ในdocument_versionsตารางที่เพิ่มตัวนับจำนวนอะตอม ( UPDATE document_revision_counters SET counter = counter + 1 WHERE document_id = ? RETURNING counter) จากนั้นตั้งค่าNEW.versionเป็นตัวนับนั้น 
อีกวิธีหนึ่งคุณอาจใช้ CTE เพื่อทำสิ่งนี้ได้ในเลเยอร์ของแอปพลิเคชัน (แต่ฉันชอบที่จะเป็นตัวกระตุ้นให้เห็นถึงความมั่นคง):
WITH version AS (
  UPDATE document_revision_counters
    SET counter = counter + 1 
    WHERE document_id = 1
    RETURNING counter
)
INSERT 
  INTO document_revisions (document_id, rev, other_data)
  SELECT 1, version.counter, 'some other data'
  FROM "version";
นี่เป็นหลักการที่คล้ายกับวิธีที่คุณพยายามแก้ไขในตอนแรกยกเว้นว่าโดยการปรับเปลี่ยนแถวเคาน์เตอร์ในคำสั่งเดียวมันบล็อกจะอ่านค่าเก่าค้างจนกว่า INSERTจะมีความมุ่งมั่น
นี่คือหลักฐานจากการpsqlแสดงสิ่งนี้ในทางปฏิบัติ:
scratch=# CREATE TABLE document_revisions (document_id integer, rev integer, other_data text, PRIMARY KEY (document_id, rev));
CREATE TABLE
scratch=# CREATE TABLE document_revision_counters (document_id integer PRIMARY KEY, counter integer DEFAULT 0);
CREATE TABLE
scratch=# WITH version AS (
    INSERT INTO document_revision_counters (document_id) VALUES (2)
      ON CONFLICT (document_id)
      DO UPDATE SET counter = document_revision_counters.counter + 1
      RETURNING counter;
  )
  INSERT 
    INTO document_revisions (document_id, rev, other_data)
    SELECT 2, version.counter, 'doc 1 v1'
    FROM "version";
INSERT 0 1
scratch=# WITH version AS (
    INSERT INTO document_revision_counters (document_id) VALUES (2)
      ON CONFLICT (document_id)
      DO UPDATE SET counter = document_revision_counters.counter + 1
      RETURNING counter;
  )
  INSERT 
    INTO document_revisions (document_id, rev, other_data)
    SELECT 2, version.counter, 'doc 1 v2'
    FROM "version";
INSERT 0 1
scratch=# WITH version AS (
    INSERT INTO document_revision_counters (document_id) VALUES (2)
      ON CONFLICT (document_id)
      DO UPDATE SET counter = document_revision_counters.counter + 1
      RETURNING counter;
  )
  INSERT 
    INTO document_revisions (document_id, rev, other_data)
    SELECT 2, version.counter, 'doc 2 v1'
    FROM "version";
INSERT 0 1
scratch=# SELECT * FROM document_revisions;
 document_id | rev | other_data 
-------------+-----+------------
           2 |   1 | doc 1 v1
           2 |   2 | doc 1 v2
           2 |   1 | doc 2 v1
(3 rows)
อย่างที่คุณเห็นคุณต้องระวังเกี่ยวกับการINSERTเกิดขึ้นของมันด้วยเหตุนี้รุ่นทริกเกอร์ซึ่งมีลักษณะดังนี้:
CREATE OR REPLACE FUNCTION set_doc_revision()
RETURNS TRIGGER AS $$ BEGIN
  WITH version AS (
    INSERT INTO document_revision_counters (document_id, counter) VALUES (NEW.document_id, 1)
    ON CONFLICT (document_id)
    DO UPDATE SET counter = document_revision_counters.counter + 1
    RETURNING counter
  )
  SELECT INTO NEW.rev counter FROM version; RETURN NEW; END;
$$ LANGUAGE 'plpgsql';
CREATE TRIGGER set_doc_revision BEFORE INSERT ON document_revisions
FOR EACH ROW EXECUTE PROCEDURE set_doc_revision();
สิ่งนี้ทำให้INSERTตรงไปตรงมามากขึ้นและความสมบูรณ์ของข้อมูลที่แข็งแกร่งยิ่งขึ้นเมื่อเผชิญกับINSERTต้นกำเนิดจากแหล่งข้อมูลโดยพลการ:
scratch=# INSERT INTO document_revisions (document_id, other_data) VALUES (1, 'baz');
INSERT 0 1
scratch=# INSERT INTO document_revisions (document_id, other_data) VALUES (1, 'foo');
INSERT 0 1
scratch=# INSERT INTO document_revisions (document_id, other_data) VALUES (1, 'bar');
INSERT 0 1
scratch=# INSERT INTO document_revisions (document_id, other_data) VALUES (42, 'meaning of life');
INSERT 0 1
scratch=# SELECT * FROM document_revisions;
 document_id | rev |   other_data    
-------------+-----+-----------------
           1 |   1 | baz
           1 |   2 | foo
           1 |   3 | bar
          42 |   1 | meaning of life
(4 rows)