(Saya sampai pada pertanyaan ini ketika mencoba menemukan kembali sebuah artikel tentang topik ini. Sekarang setelah saya menemukannya, saya mempostingnya di sini kalau-kalau ada orang lain yang sedang mengejar opsi alternatif untuk jawaban yang saat ini dipilih — berjendela dengan row_number())
Saya memiliki kasus penggunaan yang sama. Untuk setiap catatan yang dimasukkan ke dalam proyek spesifik dalam SaaS kami, kami membutuhkan angka yang unik dan bertambah yang dapat dihasilkan dalam menghadapi data bersamaan INSERTdan idealnya adalah tanpa celah.
Artikel ini menjelaskan solusi yang bagus , yang akan saya ringkas di sini untuk kemudahan dan keturunan.
- Memiliki tabel terpisah yang bertindak sebagai penghitung untuk memberikan nilai berikutnya. Ini akan memiliki dua kolom,
document_iddan counter. counterakan menjadi DEFAULT 0Atau, jika Anda sudah memiliki documententitas yang mengelompokkan semua versi, a counterdapat ditambahkan di sana.
- Tambahkan
BEFORE INSERTpemicu ke document_versionstabel yang secara atom menambah penghitung ( UPDATE document_revision_counters SET counter = counter + 1 WHERE document_id = ? RETURNING counter) dan kemudian setel NEW.versionke nilai penghitung itu.
Atau, Anda mungkin dapat menggunakan CTE untuk melakukan ini di lapisan aplikasi (meskipun saya lebih suka itu menjadi pemicu demi konsistensi):
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";
Ini pada prinsipnya mirip dengan bagaimana Anda mencoba menyelesaikannya pada awalnya, kecuali bahwa dengan memodifikasi baris tandingan dalam satu pernyataan, ia memblokir bacaan nilai basi hingga INSERTdikomit.
Berikut transkrip dari psqlmenunjukkan ini dalam aksi:
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)
Seperti yang Anda lihat, Anda harus berhati-hati tentang bagaimana ini INSERTterjadi, karenanya versi pemicu, yang terlihat seperti ini:
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();
Itu membuat INSERTjauh lebih lurus ke depan dan integritas data lebih kuat dalam menghadapi yang INSERTberasal dari sumber sewenang-wenang:
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)