はじめに
PostgreSQLは、多くの実用的なユースケースをサポートする豊富なデータ型をネイティブに提供します。この記事では、合成主キーの作成に通常使用されるシリアルデータ型の特別な実装を紹介します。
一意のキー
データベース設計理論の基本的な原則は、リレーション(つまり、テーブル)の各タプル(つまり、行)は、他のタプルから一意に識別される必要があるということです。 1つのタプルを他のすべてのタプルから明確に識別する属性または列は、「キー」と呼ばれます。一部の純粋主義者は、モデル化されたオブジェクトまたは概念は、キーとして機能できる属性または属性のセットを本質的に所有しており、このキー属性のセットを識別し、それらをタプルの一意の選択に利用することが重要であると主張します。
ただし、実際問題として、モデル化されたオブジェクトの一意性を保証する十分に大きな属性のセットを識別することは非現実的である可能性があるため、実際の実装では、開発者は代理として合成キーを使用することがよくあります。つまり、実際の属性の組み合わせに依存するのではなく、データベースの内部の値、通常は増分された整数値、または物理的な意味を持たない値がキーとして定義されます。単一の列キーの単純さに加えて、実際の依存関係がないという事実は、たとえば、人の名前が使用されている場合など、外部要因が値を変更する必要を強制することは決してないことを意味しますキーとして...そして、その人は結婚するか、連邦政府の証人保護プログラムに参加し、名前を変更しました。米国の社会保障番号など、素人が一般的に一意で不変であると考えている値でさえ、どちらでもありません。人は新しいSSNを取得でき、SSNは再利用されることがあります。
シリアルデータ型の宣言
PostgreSQLは、この合成キーのニーズを満たすために特別なデータ型宣言を提供します。データベーステーブル列をタイプSERIALとして宣言すると、新しいタプルの挿入時に一意の整数を提供することにより、合成キーの要件が満たされます。この疑似データ型は、増分された整数値を提供する関数呼び出しを介して導出された、関連付けられたデフォルト値を持つ整数データ型列を実装します。次のコードを実行して、シリアルタイプのid列を持つ単純なテーブルを作成します。
CREATE TABLE person (id serial, full_name text);
actually executes the following DDL
CREATE TABLE person (
id integer NOT NULL,
full_name text
);
CREATE SEQUENCE person_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
ALTER SEQUENCE person_id_seq OWNED BY person.id;
ALTER TABLE ONLY person
ALTER COLUMN id
SET DEFAULT nextval('person_id_seq'::regclass);
つまり、データ型指定としてのキーワード「serial」は、DDLステートメントの実行を意味し、NOT NULL制約、SEQUENCEを持つ整数型列を作成します。次に、列のデフォルトは、そのSEQUENCEにアクセスする組み込み関数を呼び出すためにALTEREDです。
組み込み関数nextvalは、自動インクリメントサービスを実行します。nextvalが呼び出されるたびに、指定されたシーケンスカウンターがインクリメントされ、新しくインクリメントされた値が返されます。
テーブルの定義を調べると、この効果の結果を確認できます。
postgres=# \d person
Table "public.person"
Column | Type | Modifiers
-----------+---------+-----------------------------------------
Id | integer | not null default nextval('person_id_seq'::regclass)
full_name | text |
シリアル値の挿入
自動インクリメント機能を利用するには、シリアル列のデフォルト値に基づいて行を挿入するだけです。
INSERT INTO person (full_name) VALUES ('Alice');
SELECT * FROM person;
id | full_name
----+-----------
1 | Alice
(1 row)
新しい「Alice」行に対応するid列の値が自動的に生成されていることがわかります。または、すべての列名を明示的にリストする必要がある場合は、DEFAULTキーワードを使用できます。
INSERT INTO person (id, full_name) VALUES (DEFAULT, 'Bob');
SELECT * FROM person;
id | full_name
----+-----------
1 | Alice
2 | Bob
(2 rows)
ここで、自動インクリメント機能がより明確に表示され、「Bob」の2番目の挿入の新しい行にserially-next値が割り当てられます。
複数の行を挿入することもできます:
INSERT INTO person (full_name) VALUES ('Cathy'), ('David');
SELECT * FROM person;
id | full_name
----+-----------
1 | Alice
2 | Bob
3 | Cathy
4 | David
(4 rows)
今日のホワイトペーパーをダウンロードするClusterControlを使用したPostgreSQLの管理と自動化PostgreSQLの導入、監視、管理、スケーリングを行うために知っておくべきことについて学ぶホワイトペーパーをダウンロードする シリアル値がありません
組み込みのnextval関数は、非ブロッキングで同時実行性の高いアプリケーション向けに最適化されているため、ロールバックを尊重しません。したがって、これはシーケンスに値が欠落している可能性があることを意味します。以下では、挿入をロールバックしますが、後続の挿入が、中止されたトランザクションに関連付けられていたはずの値をスキップする新しい値を取得することを確認します。
BEGIN TRANSACTION;
INSERT INTO person (full_name) VALUES ('Eve');
SELECT * FROM person;
id | full_name
----+-----------
1 | Alice
2 | Bob
3 | Cathy
4 | David
5 | Eve
(5 rows)
ROLLBACK;
INSERT INTO person (full_name) VALUES ('Fred');
SELECT * FROM person;
id | full_name
----+-----------
1 | Alice
2 | Bob
3 | Cathy
4 | David
6 | Fred
(5 rows)
ロールバックを尊重しないことの利点は、同時挿入を試みる他のセッションが他の挿入セッションによってブロックされないことです。
値が欠落してしまう別の方法は、行が削除された場合です:
DELETE FROM person WHERE full_name = 'Cathy';
SELECT * FROM person;
id | full_name
----+-----------
1 | Alice
2 | Bob
4 | David
6 | Fred
(4 rows)
最大の自動インクリメントID列の値に対応する最後に挿入された行を削除した後でも、シーケンスカウンターは元に戻らないことに注意してください。つまり、「Fred」に対応する行を削除した後でも、後続の挿入ではシーケンスカウンターが保持されます。既知の最大値とそこからの増分:
DELETE FROM person WHERE full_name = 'Fred';
INSERT INTO person (full_name) VALUES ('Gina');
SELECT * FROM person;
id | full_name
----+-----------
1 | Alice
2 | Bob
4 | David
7 | Gina
(4 rows)
上記のギャップまたは欠落値は、一部のアプリケーション開発者によって問題と見なされていると報告されています。これは、PostgreSQL Generalメーリングリストで、シリアル疑似データ型を使用するときにシーケンスギャップを回避する方法についての質問がゆっくりとしかし着実に繰り返されているためです。実際の基礎となるビジネス要件がない場合もあります。それは、価値の欠如に対する個人的な嫌悪感の問題です。しかし、数字の欠落を防ぐことが本当に必要な状況があり、それは次の記事の主題です。
いいえ、できません-はい、できます!
シリアル疑似データ型によって課されるNOTNULL制約は、そのような挿入の試行を拒否することにより、id列にNULLが挿入されるのを防ぎます。
INSERT INTO person (id, full_name) VALUES (NULL, 'Henry');
ERROR: null value in column "id" violates not-null constraint
DETAIL: Failing row contains (null, Henry).
したがって、その属性に値があることが保証されます。
ただし、一部の人が遭遇する問題は、上記で宣言したように、nextval関数の呼び出しによって導出されたデフォルトの自動インクリメント値をバイパスして、値の明示的な挿入を妨げるものは何もないということです。
INSERT INTO person (id, full_name) VALUES (9, 'Ingrid');
SELECT * FROM person;
id | full_name
----+-----------
1 | Alice
2 | Bob
4 | David
7 | Gina
9 | Ingrid
(5 rows)
ただし、シーケンス値に対する列値の制約チェックがない場合、後でデフォルトを使用して2回挿入すると、id列の値が重複します。
INSERT INTO person (full_name) VALUES ('James');
INSERT INTO person (full_name) VALUES ('Karen');
SELECT * FROM person;
id | full_name
----+-----------
1 | Alice
2 | Bob
4 | David
7 | Gina
9 | Ingrid
8 | James
9 | Karen
(7 rows)
実際にシリアルID列をキーとして使用している場合は、それをPRIMARY KEYとして宣言するか、少なくともUNIQUEINDEXを作成します。それを行った場合、上記の「カレン」挿入は重複キーエラーで失敗します。 PostgreSQLの最新リリースには、この落とし穴やシリアル疑似データ型に関連するその他のレガシー問題を回避する、「デフォルトでIDとして生成される」新しい制約宣言構文が含まれています。
シーケンス操作関数
シーケンスを進めて新しい値を返す前述のnextval関数に加えて、シーケンス値をクエリおよび設定するための関数がいくつかあります。currval関数は、指定されたシーケンスのnextvalで最後に取得された値であるlastval関数を返します。任意のシーケンスのnextvalで最後に取得された値を返し、setval関数はシーケンスの現在の値を設定します。これらの関数は、単純なクエリで呼び出されます。たとえば、
SELECT currval('person_id_seq');
currval
---------
9
(1 row)
また、実際に挿入を実行するのとは関係なくnextval関数を呼び出すと、シーケンスがインクリメントされ、後続の挿入に反映されることに注意してください。
SELECT nextval('person_id_seq');
nextval
---------
10
(1 row)
INSERT INTO person (full_name) VALUES ('Larry');
SELECT * FROM person;
id | full_name
----+-----------
1 | Alice
2 | Bob
4 | David
7 | Gina
9 | Ingrid
8 | James
9 | Karen
11 | Larry
(8 rows)
結論
自動インクリメントされた合成キーのPostgreSQLSERIAL疑似データ型の基本的な理解を紹介しました。この記事の説明では、4バイト整数列を作成するSERIAL型宣言を使用しました。 PostgreSQLは、それぞれ2バイトと8バイトの列サイズのSMALLSERIALとBIGSERIALの疑似データ型を使用して、さまざまな範囲のニーズに対応します。欠落値のないシーケンスの必要性に対処する1つの方法に関する将来の記事を探してください。