一意の組み合わせ行を防ぐためにPostgreSQL制約を作成する


9

単純なテーブルがあると想像してください:

name | is_active
----------------
A    | 0
A    | 0
B    | 0
C    | 1
...  | ...

次の状況で失敗する特別な一意の制約を作成する必要があります。異なるis_active値を同じname値に共存させることはできません。

許可される条件の例:

注:単純な複数列の一意のインデックスでは、このような組み合わせは許可されません。

A    | 0
A    | 0
B    | 0

許可される条件の例:

A    | 0
B    | 1

失敗した状態の例:

A    | 0
A    | 1
-- should be prevented, because `A 0` exists
-- same name, but different `is_active`

理想的には、一意の制約または一意の部分インデックスが必要です。トリガーは私にとってより問題です。

ダブルはA,0許可されて(A,0) (A,1)いますが、許可されていません。

回答:


17

で除外制約を使用できますbtree_gist

-- This is needed
CREATE EXTENSION btree_gist;

次に、次のような制約を追加します。

「同じ行nameと異なる行を2行にすることはできませんis_active

ALTER TABLE table_name
  ADD CONSTRAINT only_one_is_active_value_per_name
    EXCLUDE  USING gist
    ( name WITH =, 
      is_active WITH <>      -- if boolean, use instead:
                             -- (is_active::int) WITH <>
    );

いくつかのメモ:

  • is_active整数またはブール値にすることができ、除外制約に違いはありません。(実際には、列がブール値の場合は使用する必要があります(is_active::int) WITH <>。)
  • nameまたはis_activenullの行は制約によって無視され、許可されます。
  • 制約は、テーブルにさらに列がある場合にのみ意味があります。それ以外の場合、テーブルにこれらの2つの列しかない場合、単独でのUNIQUE制約(name)がより簡単で適切になります。複数の同一の行を格納する理由がわかりません。
  • デザインは2NFに違反しています。除外制約によって更新の異常から解放されますが、パフォーマンスの問題からではない場合があります。たとえば1000行ありname = 'A'、is_activeステータスを0から3に更新する場合、1000をすべて更新する必要があります。設計の正規化がより効率的かどうかを調べる必要があります。(この場合は、テーブルからis_activeステータスを削除し、名前がis_activeで一意の制約が設定された2列のテーブルを追加するという意味を正規化し(name)ます。is_activeがブール値の場合、完全に削除され、追加のテーブルが1つの列のテーブルのみで、 「アクティブ」な名前のみ。)

is_activeをブール値にすることはできませんERROR: data type boolean has no default operator class for access method "gist"
Evan Carroll

1
@EvanCarroll私が投稿したときにこれをどれだけうまくテストしたか思い出せません。ただし、intおよびで動作しsmallintます。
ypercubeᵀᴹ

EXCLUDE USING gist (name WITH =, (is_active::int) WITH <>)ブール値の場合も使用できます。そして、質問があり01、ないtruefalse)、ブール値でテスト、それはむしろそうですので、I
ypercubeᵀᴹ

すべて良い、私はdba.stackexchange.com/a/175922/2639に対して除外制約を使用し、ブール値の使用に問題があったため、検索を開始しました。私はbtree_gistがブールをカバーすると思っていましたが、そうではありませんでした。
エヴァンキャロル

3

これは、一意のインデックスを使用できる場合ではありません。トリガーで条件をテストできます。例:

create or replace function a_table_trigger()
returns trigger language plpgsql as $$
declare
    active int;
begin
    select is_active into active
    from a_table
    where name = new.name;

    if found and active is distinct from new.is_active then
        raise exception 'The value of is_active for "%" should be %', new.name, active;
    end if;
    return new;
end $$;

ここでテストしてください。

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