(Я прийшов до цього питання, намагаючись знову відкрити статтю про цю тему. Тепер, коли я знайшов її, я розміщую її тут у випадку, якщо інші шукають альтернативного варіанту до обраної на даний момент відповіді - відкривають вікно з row_number()
)
У мене є такий самий випадок використання. Для кожного запису, вставленого в конкретний проект нашого SaaS, нам потрібен унікальний приріст числа, який може бути згенерований перед обличчям одночасних INSERT
s і в ідеалі має безліч.
У цій статті описано приємне рішення , яке я підсумую тут для зручності та нащадків.
- Майте окрему таблицю, яка виступає лічильником для надання наступного значення. Він матиме два стовпці
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
s набагато більш прямим вперед і цілісність даних більш надійною перед 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)