Imaginez que vous avez une table simple:
name | is_active
----------------
A | 0
A | 0
B | 0
C | 1
... | ...
J'ai besoin de créer une contrainte unique spéciale qui échoue sur la situation suivante: Différent is_active
Les valeurs ne peuvent pas coexister pour la même valeur name
.
Exemple de condition autorisée:
Remarque: un index unique multi-colonne simple ne permettra pas de combiner comme ceci.
A | 0
A | 0
B | 0
Exemple de condition autorisée:
A | 0
B | 1
Exemple d'échec de la condition:
A | 0
A | 1
-- should be prevented, because `A 0` exists
-- same name, but different `is_active`
Idéalement, j'ai besoin d'une contrainte unique ou d'un indice partiel unique. Les déclencheurs sont plus problématiques pour moi.
Double A,0
autorisé, mais (A,0) (A,1)
n'est pas.
Vous pouvez utiliser une contrainte d'exclusion avec btree_Gist
,
-- This is needed
CREATE EXTENSION btree_Gist;
Ensuite, nous ajoutons une contrainte qui dit:
"Nous ne pouvons pas avoir 2 rangées qui ont le même name
et différent 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 <>
);
Quelques notes:
is_active
Peut être entier ou booléen, ne fait aucune différence pour la contrainte d'exclusion. (En réalité, si la colonne est booléenne, vous devez utiliser (is_active::int) WITH <>
.)name
ou is_active
Est NULL sera ignoré par la contrainte et ainsi autorisée.UNIQUE
sur (name)
Serait seule plus facile et plus appropriée. Je ne vois aucune raison de stocker plusieurs rangées identiques.name = 'A'
Et vous souhaitez mettre à jour l'état IS_ACTIVE de 0 à 3, tous les 1000 devront être mis à jour. Vous devez examiner si la normalisation de la conception serait plus efficace. (Normaliser la signification dans ce cas pour éliminer l'état is_active de la table et ajouter une table à 2 colonnes avec nom, is_active et une contrainte unique sur (name)
. Si is_active
Est booléen, il pourrait être totalement dépouillé et la table supplémentaire juste une seule table de colonne, stockant uniquement les noms "actifs".)Ce n'est pas un cas où vous pouvez utiliser un index unique. Vous pouvez tester la condition dans un déclencheur, par exemple:
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 $$;