web-dev-qa-db-ja.com

PostgreSQLの複数列の一意制約とNULL値

次のようなテーブルがあります。

create table my_table (
    id   int8 not null,
    id_A int8 not null,
    id_B int8 not null,
    id_C int8 null,
    constraint pk_my_table primary key (id),
    constraint u_constrainte unique (id_A, id_B, id_C)
);

そして、私は(id_A, id_B, id_C)がどのような状況でも区別できるようにしたいと思っています。したがって、次の2つの挿入はエラーになります。

INSERT INTO my_table VALUES (1, 1, 2, NULL);
INSERT INTO my_table VALUES (2, 1, 2, NULL);

ただし、ドキュメントによると、2つのNULL値は互いに比較されないため、期待どおりに動作しません。両方の挿入はエラーなしで成功します。

この場合、id_CNULLにできる場合でも、一意の制約をどのように保証できますか?実際、本当の質問は、「純粋なSQL」でこの種の一意性を保証できますか、それともより高いレベル(私の場合はJava)で実装する必要がありますか?

102
Manuel Leduc

pure SQLでそれを行うことができます。あなたが持っているものに加えて部分的な一意のインデックスに加えてを作成します:

CREATE UNIQUE INDEX ab_c_null_idx ON my_table (id_A, id_B) WHERE id_C IS NULL;

このように入力すると、(a, b, c)テーブル内:

(1, 2, 1)
(1, 2, 2)
(1, 2, NULL)

しかし、これらのどれも二度目ではありません。

または、two部分的なUNIQUEインデックスを使用し、完全なインデックス(または制約)は使用しないでください。最適なソリューションは、要件の詳細によって異なります。比較:

これはエレガントで、UNIQUEインデックスの単一のnull許容列に対して効率的ですが、多くの場合、すぐに手に負えなくなります。これについて議論し、そして部分インデックスでUPSERTを使用する方法:

アサイド

PostgreSQLでは 二重引用符なしの大文字と小文字が混在する識別子 は使用しません。

あなたmightserial column を主キーと見なすか、Postgres 10以降では IDENTITY column と見なします。関連:

そう:

CREATE TABLE my_table (
   my_table_id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY  -- for pg 10+
-- my_table_id bigserial PRIMARY KEY  -- for pg 9.6 or older
 , id_a int8 NOT NULL
 , id_b int8 NOT NULL
 , id_c int8
 , CONSTRAINT u_constraint UNIQUE (id_a, id_b, id_c)
);

テーブルの存続期間全体で20億行(> 2147483647)を超えると予想されない場合(無駄な行と削除された行を含む)、integer(8バイト)ではなくbigint(4バイト)を検討してください。

102

同じ問題があり、テーブルに一意のNULLを設定する別の方法を見つけました。

CREATE UNIQUE INDEX index_name ON table_name( COALESCE( foreign_key_field, -1) )

私の場合、フィールドforeign_key_fieldは正の整数であり、-1になることはありません。

したがって、Manual Leducに答えるには、別の解決策が考えられます

CREATE UNIQUE INDEX  u_constrainte (COALESCE(id_a, -1), COALESCE(id_b,-1),COALESCE(id_c, -1) )

IDは-1にならないと思います。

部分インデックスを作成する利点は何ですか?
NOT NULL句がない場合、id_aid_bおよびid_cをNULLにできるのは1回だけです。
部分インデックスを使用すると、3つのフィールドがNULLになることが2回以上あります。

12
Luc M

Nullは、現時点ではその行の値が不明であるが、既知の場合、将来追加される(例:FinishDate実行中のProject)こと、または値がその行に適用されます(ブラックホールの例EscapeVelocityStar)。

私の意見では、通常、すべてのヌルを削除してテーブルを正規化することをお勧めします。

あなたのケースでは、列でNULLsを許可したいが、NULLを1つだけ許可したい。どうして? 2つのテーブルの間にはどのような関係がありますか?

おそらく、列をNOT NULLに変更して、NULLの代わりに、決して表示されないことがわかっている特別な値(-1など)を格納できます。これにより、一意性制約の問題が解決されます(ただし、他の望ましくない副作用が発生する可能性があります。たとえば、-1を使用して「不明/該当しない」ことを意味すると、列の合計または平均の計算がゆがみます。計算では、特別な値を考慮に入れて無視する必要があります。)

8
ypercubeᵀᴹ