(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 INSERT
dan 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_id
dan counter
. counter
akan menjadi DEFAULT 0
Atau, jika Anda sudah memiliki document
entitas yang mengelompokkan semua versi, a counter
dapat ditambahkan di sana.
- Tambahkan
BEFORE INSERT
pemicu ke document_versions
tabel yang secara atom menambah penghitung ( UPDATE document_revision_counters SET counter = counter + 1 WHERE document_id = ? RETURNING counter
) dan kemudian setel NEW.version
ke 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 INSERT
dikomit.
Berikut transkrip dari psql
menunjukkan 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 INSERT
terjadi, 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 INSERT
jauh lebih lurus ke depan dan integritas data lebih kuat dalam menghadapi yang INSERT
berasal 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)