(私はこのトピックに関する記事を再発見しようとしたときにこの質問に行きました。それを見つけたので、他の人が現在選択されている回答の代替オプションを追求している場合に備えて、ここに投稿します。row_number()
)
これと同じ使用例があります。SaaSの特定のプロジェクトに挿入された各レコードについて、同時INSERT
sに直面して生成でき、理想的にはギャップのない、一意の増分番号が必要です。
この記事では、簡単な方法と後世のためにここで要約する、優れたソリューションについて説明します。
- 次の値を提供するためのカウンターとして機能する別のテーブルを用意します。2つの列が
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
sがどのように発生するか、つまり次のようなトリガーバージョンに注意する必要があります。
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();
これにより、任意のソースから発信されたs 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)