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


94

次のような表があります。

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_CできますNULLか?実際、本当の問題は、「pure sql」でこのような一意性を保証できますか、それともより高いレベル(私の場合はjava)で実装する必要がありますか?


だから、あなたが値を持っていると言う(1,2,1)と、(1,2,2)(A,B,C)の列。を(1,2,NULL)追加することを許可する必要がありますか?
ypercubeᵀᴹ

AとBはnullにできませんが、Cはnullまたは任意の正の整数値にできます。(1,2,3)と(2,4、null)は有効ですが、(null、2,3)または(1、null、4)は無効です。また、[(1,2、null)、(1,2,3)]は一意制約を解除しませんが、[(1,2、null)、(1,2、null)]はそれを解除する必要があります。
マヌエルルダック

2
これらの列に表示されることはありません任意の値が存在する(負の値のようにしますか?)
a_horse_with_no_name

pgで制約にラベルを付ける必要はありません。名前を自動的に生成します。ちょっとだけ。
エヴァンキャロル

回答:


94

純粋な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)

しかし、これらは二度とありません。

または、2つの部分UNIQUEインデックスを使用し、完全なインデックス(または制約)は使用しません。最適なソリューションは、要件の詳細に依存します。比較:

これは、UNIQUE索引内の単一のNULL可能列に対してはエレガントで効率的ですが、さらに多くの場合、手に負えなくなります。これについて議論します-部分インデックスでUPSERTを使用する方法:

側近

PostgreSQLでは、二重引用符のない大文字と小文字混在した識別子は使用できません。

を主キーまたはPostgres 10以降のと見なすことができserialます。関連する:IDENTITY

そう:

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は、bigint(8バイト)ではなく(4バイト)を検討してください。


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

12

同じ問題が発生し、テーブルに一意の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_bid_c一緒に一度だけNULLにすることができます。
部分インデックスでは、3つのフィールドが複数回NULLになる可能性があります。


3
>部分インデックスを作成する利点は何ですか?あなたがそれをやった方法はCOALESCE、重複を制限するのに効果的かもしれませんが、インデックスは、おそらくクエリ式と一致しない式インデックスとして、クエリではあまり役に立ちません。つまりSELECT COALESCE(col, -1) ...、インデックスにヒットしない場合を除きます。
ボージーンズ

@BoJeanesインデックスはパフォーマンスの問題のために作成されていません。ビジネス要件を満たすために作成されました。
リュックM

8

Nullは、その行の値が現時点では不明であるが、既知の場合は将来的に追加される(FinishDate実行中の例Project)か、その行に値を適用できない(EscapeVelocityブラックホールの例Star)ことを意味します。

私の意見では、通常、すべてのNullを削除してテーブルを正規化する方が適切です。

あなたの場合、あなたNULLsはあなたのコラムで許可したいのですが、あなたは1つだけを許可したいですNULL。どうして?2つのテーブル間のこれはどのような関係ですか?

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


2
私の場合、NULLは本当にNULLです(id_Cはexempleのtable_cの外部キーなので、-1の値を持つことはできません)。これは、「my_table」と「table_c」の間に関係がないことを意味します。したがって、機能的な意味があります。ところで、[(1、1,1、null)、(2、1,2、null)、(3,2,4、null)]は挿入されたデータの有効なリストです。
マヌエルルダック

1
すべての行に1つだけが必要なため、SQLで使用されるような実際にはNullではありません。データベーススキーマを変更するには、-1をtable_cに追加するか、別のテーブル(スーパータイプからサブタイプtable_c)を追加します。
ypercubeᵀᴹ

3
@Manuelに指摘したいのは、この回答のヌルに関する意見は普遍的に保持されているものではなく、多くの議論があることです。多くの人は、私のように、nullはあなたが望むあらゆる目的に使用できると考えています(ただし、フィールドごとに1つだけを意味し、フィールド名または列コメントで文書化する必要があります)
Jack Douglas

1
列が外部キーである場合、ダミー値を使用できません。
リュックM

1
+1私はあなたと一緒です:列の組み合わせを一意にする場合、この列の組み合わせがPKであるエンティティを考慮する必要があります。OPのデータベーススキーマは、おそらく親テーブルと子テーブルに変更する必要があります。
AK
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.