Index unique différable dans les postgres

14

En regardant dans la documentation postgres pour alter table , il semble que les contraintes régulières peuvent être marquées comme DEFERRABLE(plus concrètement, INITIALLY DEFERREDc'est 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;

Pour être INITIALLY DEFERRED, ce qui signifie que le caractère unique « contrainte » ne sera vérifiée à 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

jcristovao
la source

Réponses:

15

Un index ne peut pas être différé - peu importe qu'il soit UNIQUEou non, partiel ou non, seulement une UNIQUEcontrainte. D' autres types de contraintes ( FOREIGN KEY, PRIMARY KEY, EXCLUDE) sont également reportables - mais pas les CHECKcontraintes.

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 qu'il booking_statusn'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 EXCLUDEcontrainte:

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é chez dbfiddle .

Ce que fait ce qui précède:

  • L' CASEexpression devient NULLquand booking_statusest nulle ou différente de 1. On pourrait écrire (CASE WHEN booking_status = 1 THEN TRUE END)comme (booking_status = 1 OR NULL)si cela le rendait 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 WITHopérateurs le sont =donc il agit comme une UNIQUEcontrainte.

  • Ces deux combinés font agir la contrainte comme un index unique filtré.

  • Mais c'est une contrainte et les EXCLUDEcontraintes peuvent être différées.

ypercubeᵀᴹ
la source
2
+1 pour la version EXCLUDE, c'était ce dont j'avais besoin. Voici un autre exemple montrant les capacités d'EXCLUDE: cybertec-postgresql.com/en/postgresql-exclude-beyond-unique
Benjamin Peter
(CASE WHEN booking_status = 1 THEN TRUE END) WITH =)devrait être remplacé par ) WHERE (booking_status = 1)car "Les contraintes d'exclusion sont implémentées à l'aide d'un index", et cet index partiel avec WHEREsera plus petit et plus rapide - postgresql.org/docs/current/sql-createtable.html et postgresql.org/docs/current/sql- createindex.html
Denis Ryzhkov
1

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 à un tableau 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:

Contraintes SQL reportables en profondeur

NON DÉFÉRRABLE versus DÉFÉRRABLE INITIALEMENT IMMÉDIATE

David Campos
la source