Postgresの一意の制約とインデックス


156

ドキュメントを理解できるので、次の定義は同等です。

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);    

ただし、Postgres 9.4のマニュアル、次のように記載されています

テーブルに一意性制約を追加する推奨方法はALTER TABLE ... ADD CONSTRAINTです。インデックスを使用して一意の制約を適用することは、直接アクセスすべきではない実装の詳細と考えることができます。

(編集:このメモはPostgres 9.5のマニュアルから削除されました。)

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


23
(唯一の)実用的な違いは、一意の制約ではなく一意のインデックスに対する外部キーを作成できることです。
a_horse_with_no_name 2014年

29
他の方法の利点(最近別の質問で出てきたように)は、「Unique(foo)Where bar Is Null」のように部分的に一意のインデックスを持つことができることです。私の知る限り、制約でそれを行う方法はありません。
IMSoP 2014年

3
@a_horse_with_no_nameこれがいつ起こったかはわかりませんが、これは本当ではないようです。このSQLフィドルは、一意のインデックスへの外部キー参照を許可します 。sqlfiddle.com / #!17 / 20ee9 ; 編集:一意のインデックスに「フィルター」を追加すると、これが機能しなくなります(予想どおり)
user1935361

1
postgresのドキュメントから:テーブルに一意の制約または主キーが定義されている場合、PostgreSQLは自動的に一意のインデックスを作成します。postgresql.org/docs/9.4/static/indexes-unique.html
maggu 2018年

@ user1935361に同意します(少なくともPG 10で)一意のインデックスへの外部キーを作成できなかった場合、私はずっと前にこの問題に遭遇したでしょう。
アンディ

回答:


131

この基本的で重要な問題について疑問があったので、例を挙げて学ぶことにしました。

一意の制約を持つcon_idと一意のインデックスでインデックスが付けられたind_idの 2つの列を持つテストテーブルマスターを作成してみましょう。

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つの外部キーを持つ詳細テーブルを定義します。

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 ...。一意のインデックス宣言では、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.

実際の情報ですか?特に部分インデックスについて
アナトール

1
@anatol-はい、そうです。
klin

30

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


4
私の知る限り、一意のインデックスを同時に削除することはできません。postgresql.org/docs/9.3/static/sql-dropindex.html "このオプションを使用する場合は、いくつかの注意点があります。指定できるインデックス名は1つだけで、CASCADEオプションはサポートされていません(したがって、インデックスUNIQUEまたはPRIMARY KEY制約サポートがこのようにドロップすることができない)」。
ラファウCieślak

15

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

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

全文

スピード性能は同じでなければなりません


4

私が遭遇したもう1つのことは、一意のインデックスではSQL式を使用できますが、制約では使用できないことです。

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

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

しかし、次の作品。

CREATE TABLE users (
    name text
);
CREATE UNIQUE INDEX uq_name on users (lower(name));

citext拡張機能を使用します。
ceving

@cevingはユースケースによって異なります。大文字と小文字を区別しない一意性を確保しながら、大文字と小文字を維持したい場合がある
Sampson Crowley

2

さまざまな人々が一意制約よりも一意索引の利点を提供しているため、ここに欠点があります。一意制約は据え置くことができ(トランザクションの最後にのみチェックされる)、一意索引は遅延できません。


すべての一意の制約に一意のインデックスがある場合、これはどのようになりますか?
クリス

1
インデックスには延期用のAPIがないため、制約のみが存在するため、延期機構は隠れて一意の制約をサポートしますが、インデックスを延期可能として宣言したり、延期したりすることはできません。
Masklinn

0

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

追加table_constraint [無効です]

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

制約を追加することで、これを「部分的な一意性」と呼んでいると思います。

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

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

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

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

この問題をどうやって見ますか?

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


1
「つまり、制約を追加することで、これを「部分的な一意性」と呼んでいると思います。」インデックスは、where句を通じてレコードの明確に定義されたサブセットにのみ適用できるため、レコードがいくつかの条件を満たす一意のIFFであると定義できます。これは単に、作成される制約より前の未定義のレコードセットの制約を無効にするだけです。それは完全に異なり、後者はあまり有用ではありませんが、私が推測する漸進的な移行には便利です。
Masklinn

0

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


0

制約でのみ実行でき、インデックスでは実行できない非常にマイナーなことは、ON CONFLICT ON CONSTRAINT句を使用することです(この質問も参照)。

これは機能しません:

CREATE TABLE T (a INT PRIMARY KEY, b INT, c INT);
CREATE UNIQUE INDEX u ON t(b);

INSERT INTO T (a, b, c)
VALUES (1, 2, 3)
ON CONFLICT ON CONSTRAINT u
DO UPDATE SET c = 4
RETURNING *;

それは生成します:

[42704]: ERROR: constraint "u" for table "t" does not exist

インデックスを制約に変換します。

DROP INDEX u;
ALTER TABLE t ADD CONSTRAINT u UNIQUE (b);

そして、INSERTステートメントは今機能します。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.