web-dev-qa-db-ja.com

Postgresユニーク制約とインデックス

documentation を理解できるように、次の定義は同等です:

create table foo (
    id serial primary key,
    code integer,
    label text,
    constraint foo_uq unique (code, label));

create table foo (
    id serial primary key,
    code integer,
    label text);
create unique index foo_idx on foo using btree (code, label);    

ただし、注を読むことができます:テーブルにユニークな制約を追加する好ましい方法は、ALTER TABLE ... ADD CONSTRAINTです。一意の制約を強制するためのインデックスの使用は、直接アクセスするべきではない実装の詳細と考えることができます

それは良いスタイルだけの問題ですか?これらのバリアントの1つを選択した場合の実際的な結果は何ですか(パフォーマンスなど)?

125
Adam Piotrowski

私はこの基本的であるが重要な問題についていくつかの疑念を抱いていたので、例から学ぶことにしました。

2つの列を持つテストテーブルmaster、一意の制約を持つcon_idind_id一意のインデックスでインデックス付けされています。

create table master (
    con_id integer unique,
    ind_id integer
);
create unique index master_unique_idx on master (ind_id);

    Table "public.master"
 Column |  Type   | Modifiers
--------+---------+-----------
 con_id | integer |
 ind_id | integer |
Indexes:
    "master_con_id_key" UNIQUE CONSTRAINT, btree (con_id)
    "master_unique_idx" UNIQUE, btree (ind_id)

テーブルの説明(psqlの\ d)では、一意のインデックスから一意の制約を伝えることができます。

ユニークさ

念のため、一意性を確認しましょう。

test=# insert into master values (0, 0);
INSERT 0 1
test=# insert into master values (0, 1);
ERROR:  duplicate key value violates unique constraint "master_con_id_key"
DETAIL:  Key (con_id)=(0) already exists.
test=# insert into master values (1, 0);
ERROR:  duplicate key value violates unique constraint "master_unique_idx"
DETAIL:  Key (ind_id)=(0) already exists.
test=#

期待どおりに動作します!

外部キー

次に、masterの2つの列を参照する2つの外部キーを持つdetailテーブルを定義します。

create table detail (
    con_id integer,
    ind_id integer,
    constraint detail_fk1 foreign key (con_id) references master(con_id),
    constraint detail_fk2 foreign key (ind_id) references master(ind_id)
);

    Table "public.detail"
 Column |  Type   | Modifiers
--------+---------+-----------
 con_id | integer |
 ind_id | integer |
Foreign-key constraints:
    "detail_fk1" FOREIGN KEY (con_id) REFERENCES master(con_id)
    "detail_fk2" FOREIGN KEY (ind_id) REFERENCES master(ind_id)

まあ、エラーはありません。動作することを確認しましょう。

test=# insert into detail values (0, 0);
INSERT 0 1
test=# insert into detail values (1, 0);
ERROR:  insert or update on table "detail" violates foreign key constraint "detail_fk1"
DETAIL:  Key (con_id)=(1) is not present in table "master".
test=# insert into detail values (0, 1);
ERROR:  insert or update on table "detail" violates foreign key constraint "detail_fk2"
DETAIL:  Key (ind_id)=(1) is not present in table "master".
test=#

両方の列は外部キーで参照できます。

インデックスを使用した制約

既存の一意のインデックスを使用してテーブル制約を追加できます。

alter table master add constraint master_ind_id_key unique using index master_unique_idx;

    Table "public.master"
 Column |  Type   | Modifiers
--------+---------+-----------
 con_id | integer |
 ind_id | integer |
Indexes:
    "master_con_id_key" UNIQUE CONSTRAINT, btree (con_id)
    "master_ind_id_key" UNIQUE CONSTRAINT, btree (ind_id)
Referenced by:
    TABLE "detail" CONSTRAINT "detail_fk1" FOREIGN KEY (con_id) REFERENCES master(con_id)
    TABLE "detail" CONSTRAINT "detail_fk2" FOREIGN KEY (ind_id) REFERENCES master(ind_id)

これで、列制約の説明に違いはありません。

部分インデックス

テーブル制約宣言では、部分インデックスを作成できません。 create table ...definition から直接取得します。一意のインデックス宣言では、WHERE clauseを設定して部分インデックスを作成できます。 式で(列だけでなく)インデックス を作成し、他のパラメーター(照合、ソート順、NULL配置)を定義することもできます。

部分インデックスを使用してテーブル制約を追加することはできません。

alter table master add column part_id integer;
create unique index master_partial_idx on master (part_id) where part_id is not null;

alter table master add constraint master_part_id_key unique using index master_partial_idx;
ERROR:  "master_partial_idx" is a partial index
LINE 1: alter table master add constraint master_part_id_key unique ...
                               ^
DETAIL:  Cannot create a primary key or unique constraint using such an index.
114
klin

UNIQUE INDEXUNIQUE CONSTRAINTを使用するもう1つの利点は、簡単にDROP/CREATEインデックス CONCURRENTLY を使用できますが、制約では使用できないことです。

27
Vadim Zingertal

一意性は制約です。特定の値がすでに存在するかどうかを判断するために、インデックスはすべての既存の値をすばやく検索できるため、一意のインデックスの作成によって実装されます。

概念的には、インデックスは実装の詳細であり、一意性は制約のみに関連付ける必要があります。

全文

したがって、速度パフォーマンスは同じでなければなりません

9
Eugen Konkov

私が遭遇した別のことは、制約ではなく一意のインデックスでSQL式を使用できることです。

したがって、これは機能しません:

CREATE TABLE users (
    name text,
    UNIQUE (lower(name))
);

しかし、次の作品。

CREATE TABLE users (
    name text
);
CREATE UNIQUE INDEX uq_name on users (lower(name));
2
김민준

私はドキュメントでこれを読みました:

ADD table_constraint [無効です]

このフォームは、CREATE TABLEと同じ構文に加えて、現在外部キー制約にのみ許可されているNOT VALIDオプションを使用して、新しい制約をテーブルに追加します。制約がNOT VALIDとマークされている場合、テーブル内のすべての行が制約を満たすことを確認するための潜在的に長い初期チェックはスキップです。 後続の挿入または更新(つまり、参照されるテーブルに一致する行がなければ失敗します)に対して、制約は引き続き適用されます。ただし、データベースは、VALIDATE CONSTRAINTオプションを使用して検証されるまで、テーブル内のすべての行に対して制約が保持されるとは見なしません

ですから、制約を追加することで「部分的な一意性」と呼ぶものだと思います。

そして、一意性を確保する方法について:

一意の制約を追加すると、制約にリストされている列または列のグループに一意のBツリーインデックスが自動的に作成されます。一部の行のみを対象とする一意性制限は、一意制約として記述できませんが、一意の部分インデックスを作成することにより、そのような制限を実施することができます。

注:テーブルに一意の制約を追加する好ましい方法は、ALTER TABLE…ADD CONSTRAINTです。一意の制約を強制するためのインデックスの使用は、直接アクセスすべきではない実装の詳細と見なすことができます。ただし、一意の列にインデックスを手動で作成する必要はないことに注意してください。そうすると、自動的に作成されたインデックスが複製されます。

したがって、一意性を確保するために、インデックスを作成する制約を追加する必要があります。

この問題はどのように見えますか?

「制約」の目的は、文法的に、この列が一意であることを保証し、法律、規則を確立することです。一方、「インデックス」はsemanticalであり、「実装方法、一意性を実現する方法、実装に関して固有の意味は何か」についてです。したがって、Postgresqlの実装方法は非常に論理的です。まず、列が一意であることを宣言してから、Postgresqlが一意のインデックスを追加する実装を追加します

0
WesternGun

ロックには違いがあります。
インデックスを追加しても、テーブルへの読み取りアクセスはブロックされません。
制約を追加すると、ALTER TABLEによって追加されるため、テーブルロックが設定されます(したがって、すべての選択がブロックされます)。

0
Bax