En regardant documentation postgres pour alter table , il semble que les contraintes régulières peuvent être marquées comme DEFERRABLE
(plus concrètement, INITIALLY DEFERRED
, ce qui m'intéresse).
Les index peuvent également être associés à une contrainte, tant que:
L'index ne peut pas avoir de colonnes d'expression ni être un index partiel
Ce qui m'amène à croire qu'il n'y a actuellement aucun moyen d'avoir un index unique avec des conditions, comme:
CREATE UNIQUE INDEX unique_booking
ON public.booking
USING btree
(check_in, check_out)
WHERE booking_status = 1;
Être INITIALLY DEFERRED
, ce qui signifie que la "contrainte" d'unicité ne sera vérifiée qu'à la fin de la transaction (si SET CONSTRAINTS ALL DEFERRED;
est utilisé).
Mon hypothèse est-elle correcte et, dans l'affirmative, existe-t-il un moyen d'obtenir le comportement souhaité?
Merci
Un index ne peut pas être différé - peu importe qu'il soit UNIQUE
ou non, partiel ou non, seulement une contrainte UNIQUE
. Autres types de contraintes (FOREIGN KEY
, PRIMARY KEY
, EXCLUDE
) sont également reportables - mais pas les contraintes CHECK
.
Ainsi, l'index partiel unique (et la contrainte implicite qu'il implémente) sera vérifié à chaque instruction (et en fait après chaque insertion/mise à jour de ligne dans l'implémentation actuelle), pas à la fin de la transaction.
Ce que vous pourriez faire, si vous souhaitez implémenter cette contrainte comme reportable, est d'ajouter une table de plus dans la conception. Quelque chose comme ça:
CREATE TABLE public.booking_status
( booking_id int NOT NULL, -- same types
check_in timestamp NOT NULL, -- as in
check_out timestamp NOT NULL, -- booking
CONSTRAINT unique_booking
UNIQUE (check_in, check_out)
DEFERRABLE INITIALLY DEFERRED,
CONSTRAINT unique_booking_fk
FOREIGN KEY (booking_id, check_in, check_out)
REFERENCES public.booking (booking_id, check_in, check_out)
DEFERRABLE INITIALLY DEFERRED
) ;
Avec cette conception et en supposant que booking_status
n'a que 2 options possibles (0 et 1), vous pouvez le supprimer entièrement de booking
(s'il y a une ligne à booking_status
, c'est 1, sinon 0).
Une autre façon serait (ab) d'utiliser une contrainte EXCLUDE
:
ALTER TABLE booking
ADD CONSTRAINT unique_booking
EXCLUDE
( check_in WITH =,
check_out WITH =,
(CASE WHEN booking_status = 1 THEN TRUE END) WITH =
)
DEFERRABLE INITIALLY DEFERRED ;
Testé à dbfiddle.
Ce que fait ce qui précède:
L'expression CASE
devient NULL
lorsque booking_status
est nul ou différent de 1. Nous pourrions écrire (CASE WHEN booking_status = 1 THEN TRUE END)
comme (booking_status = 1 OR NULL)
si cela le rend plus clair.
Les contraintes uniques et d'exclusion acceptent les lignes où une ou plusieurs des expressions sont NULL. Il agit donc comme un index filtré avec WHERE booking_status = 1
.
Tous les opérateurs WITH
sont =
donc il agit comme une contrainte UNIQUE
.
Ces deux combinés font agir la contrainte comme un index unique filtré.
Mais c'est une contrainte et les contraintes EXCLUDE
peuvent être différées.
Bien que les années de cette question soient passées, je voudrais clarifier pour les hispanophones, les tests ont été effectués dans Postgres:
La contrainte suivante a été ajoutée à une table de 1337 enregistrements, où le kit est la clé primaire:
**Bloque 1**
ALTER TABLE ele_kitscompletos
ADD CONSTRAINT unique_div_nkit
PRIMARY KEY (div_nkit)
Cela crée une clé primaire par défaut NON DEFERRED pour la table, donc lors de la prochaine mise à jour, nous obtenons une erreur:
update ele_kitscompletos
set div_nkit = div_nkit + 1;
ERREUR: la clé en double viole la restriction d'unicité "unique_div_nkit"
Dans Postgres, l'exécution d'une MISE À JOUR pour chaque ROW vérifie que la RESTRICTION ou la CONTRAINTE est respectée.
Le CONSTRAINT IMMEDIATE est maintenant créé et chaque instruction est exécutée séparément:
ALTER TABLE ele_kitscompletos
ADD CONSTRAINT unique_div_nkit
PRIMARY KEY (div_nkit)
DEFERRABLE INITIALLY IMMEDIATE
**Bloque 2**
BEGIN;
UPDATE ele_kitscompletos set div_nkit = div_nkit + 1;
INSERT INTO public.ele_kitscompletos(div_nkit, otro_campo)
VALUES
(1338, '888150502');
COMMIT;
Requête OK, 0 lignes affectées (temps d'exécution: 0 ms; temps total: 0 ms) Requête OK, 1328 lignes affectées (temps d'exécution: 858 ms; temps total: 858 ms) ERREUR: llave duplicada alto restricción de unicidad "unique_div_nkit" DÉTAIL : Ya existe la llave (div_nkit) = (1338).
Ici, SI permet de changer la clé primaire car il exécute toute la première phrase complète (1328 lignes); mais bien qu'il soit en transaction (BEGIN), la CONTRAINTE est validée immédiatement à la fin de chaque phrase sans avoir effectué COMMIT, génère donc l'erreur lors de l'exécution de INSERT. Enfin, nous avons créé le CONSTRAINT DEFERRED, procédez comme suit:
**Bloque 3**
ALTER TABLE public.ele_edivipol
DROP CONSTRAINT unique_div_nkit RESTRICT;
ALTER TABLE ele_edivipol
ADD CONSTRAINT unique_div_nkit
PRIMARY KEY (div_nkit)
DEFERRABLE INITIALLY DEFERRED
Si nous exécutons chaque instruction du ** bloc 2 **, chaque phrase séparément, aucune erreur n'est générée dans l'INSERT car il ne valide pas mais le COMMIT final est exécuté là où il trouve une incohérence.
Pour des informations complètes en anglais, je vous suggère de vérifier les liens: