(ฉันมาถึงคำถามนี้เมื่อพยายามค้นพบบทความเกี่ยวกับหัวข้อนี้อีกครั้งตอนนี้ฉันได้พบแล้วฉันกำลังโพสต์ไว้ที่นี่ในกรณีที่คนอื่นกำลังติดตามตัวเลือกอื่นสำหรับคำตอบที่เลือกในปัจจุบัน - หน้าต่างด้วยrow_number()
)
ฉันมีกรณีใช้เดียวกันนี้ สำหรับแต่ละระเบียนใส่เข้าไปในโครงการเฉพาะใน SaaS ของเราเราต้องไม่ซ้ำกันจำนวนที่เพิ่มขึ้นซึ่งสามารถสร้างขึ้นในการเผชิญกับความพร้อมกันINSERT
และเป็นความนึกคิดที่ไม่มีช่องโหว่
บทความนี้จะอธิบายวิธีแก้ปัญหาที่ดีซึ่งฉันจะสรุปที่นี่เพื่อความสะดวกและลูกหลาน
- มีตารางแยกต่างหากซึ่งทำหน้าที่เป็นตัวนับเพื่อให้ค่าถัดไป มันจะมีสองคอลัมน์และ
document_id
จะเป็นอีกวิธีหนึ่งถ้าคุณมีเอนทิตีที่จัดกลุ่มเวอร์ชันทั้งหมดแล้วcounter
counter
DEFAULT 0
document
counter
อาจเพิ่มได้
- เพิ่ม
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)