リレーショナルデータベースでは、ユニーク性の確保はしばしばUNIQUE制約に依存しています。しかし、ビジネスの実務には、NULL値を含むフィールドの組み合わせに対してユニーク性が求められる状況があります(たとえば、emailと電話のユニークな組み合わせ。ただし、電話は不明である可能性があります)。
問題の歴史: SQLの標準的なUNIQUE制約は、列の1つがNULLである場合にはユニーク性を保証しません。仕様では、これらの値は相互に排除されるとされています。
問題: NULL値を含む複雑なキーのため、標準的な制約は重複を引き起こすことがあります。これは、データの整合性が必要である場合(インポート、マイグレーション、大規模な更新時)には特に深刻です。
解決策: すべての値(NULLを含む)を考慮する計算列を使用し、その列にユニーク性を制約するか、トリガーロジックを使用します。
コードの例:
ALTER TABLE my_table ADD computed_uniqueness AS ( ISNULL(email, '') + '#' + ISNULL(phone, '') ); CREATE UNIQUE INDEX idx_my_table_computed_uniqueness ON my_table(computed_uniqueness);
または(NULLが異なるものとして扱われるインデックス式をサポートしているPostgreSQL用):
CREATE UNIQUE INDEX idx_unique_email_phone ON my_table ((COALESCE(email, '##')),(COALESCE(phone, '##')));
主な特徴:
部分的にNULLが許可される列に対して通常のUNIQUEインデックスを使用してユニーク性を保証できますか?
いいえ。ANSI SQLに従うと、UNIQUEインデックスは、組み合わせの中にNULLを含む場合には、複数の行を許可します。なぜなら、NULLは他のいかなる値とも等しくなく、NULLを含むことがあるからです。
式に対するユニークインデックスと、ユニーク性をチェックするためのBEFORE INSERTトリガーの使用の違いは何ですか?
ユニークインデックスはサポートが簡単で、動作が速いですが、複雑なビジネスルール(例:例外やカスタムの組み合わせ)を実装できるとは限りません。トリガーは柔軟ですが、遅く、メンテナンスが難しくなります。
例:
CREATE OR REPLACE FUNCTION check_custom_unique() RETURNS TRIGGER AS $$ BEGIN IF EXISTS ( SELECT 1 FROM my_table WHERE COALESCE(NEW.email, '##') = COALESCE(email, '##') AND COALESCE(NEW.phone, '##') = COALESCE(phone, '##') ) THEN RAISE EXCEPTION 'Duplicate found'; END IF; RETURN NEW; END; $$ LANGUAGE plpgsql; CREATE TRIGGER trg_custom_unique BEFORE INSERT OR UPDATE ON my_table FOR EACH ROW EXECUTE FUNCTION check_custom_unique();
ユニーク性の問題をアプリケーションレベルで解決するためにSELECTでDISTINCTを使用できますか?
できますが、選択のみに対してであり、データの変更時に重複を防ぐものではなく、テーブルレベルでの制約の代わりにもなりません。
企業がUNIQUE(email, phone)を介してemailと電話のユニーク性を実装します。電話がNULLの場合、データベースにemailの重複が現れます。
利点:
欠点:
計算列(COALESCE(email, '##') + '#' + COALESCE(phone, ''))を使用し、それにユニークインデックスを設定します。
利点:
欠点: