SQL Server : une contrainte conditionnelle
Mon besoin d’une contrainte conditionnelle s’était exprimé sur une contrainte de type UNIQUE, que je voulais restreindre à certaines lignes, c’est à dire les lignes pour lesquelles une condition du type MaColonne = X est satisfaite. Ce billet montre 2 manières différente de les créer.
Prenons l’exemple suivant :
CREATE TABLE Moron (
Id INT NOT NULL,
Name NVARCHAR(25),
IsDeleted BIT NOT NULL,
CONSTRAINT UQ_Moron_Name UNIQUE(Name)
)
On définit cette table comme étant les abrutis présents dans une classe.
On sait qu’il ne peut pas y avoir 2 abrutis avec le même nom dans une classe, on tape donc dans le marbre la contrainte UQ_Moron_Name pour que cette valeur de vérité soit toujours vérifiée.
Supposons maintenant qu’un abruti Aurélien s’en aille, on va le marquer IsDeleted = 1.
Supposons ensuite, qu’un autre abruti arrive en classe et qu’il s’appelle aussi Aurélien. Boum, on ne peut pas l’insérer dans la table à cause de la contrainte d’intégrité.
L’idéal serait donc de modifier cette contrainte (ça serait vraiment dommage de la supprimer, non ?), et ainsi prendre en compte cette nouvelle réalité, à savoir que ce qui nous intéresse est : il ne peut pas y avoir 2 abrutis avec le même nom dans une classe, c’est à dire que c’est la contrainte précédente mais uniquement lorsque IsDeleted = 0.
Et donc ?
Il faut trouver le moyen de réaliser une contrainte conditionnelle, et deux moyens au moins s’offrent à nous :
La vue
On peut créer une vue telle que ci-dessous :
CREATE View ActiveMoron
AS
SELECT Name
FROM Moron
WHERE IsDeleted = 0
GO
CREATE UNIQUE CLUSTERED INDEX IDX_Active_Moron ON ActiveMoron(Name)
On raffine ainsi la table aux colonnes essentielles à la condition (avec le SELECT), et aux lignes sur lesquelles on doit l’appliquer (avec le WHERE).
Il n’y a plus qu’à poser la contrainte, ici via un index UNIQUE.
L’index conditionnel
Disponible à partir de SQL Server 2008, il est possible de poser des index contenant une condition. Ainsi
CREATE UNIQUE INDEX IDX_Active_Moron ON ActiveMoron(Name) WHERE IsDeleted = 0
est complètement équivalent à la solution précédente, du point de vue de la contrainte, mais la vue en moins.