contrainte unique conditionnelle

J'ai une situation où je dois appliquer une contrainte unique sur un ensemble de colonnes, mais seulement pour une valeur d'une colonne.

Par exemple, j'ai une table comme Table (ID, Name, RecordStatus).

RecordStatus ne peut avoir qu'une valeur 1 ou 2 (active ou supprimée), et je veux créer une contrainte unique sur (ID, RecordStatus) uniquement lorsque RecordStatus = 1, car je ne me soucie pas s'il y a plusieurs enregistrements supprimés avec le même ID.

En dehors de l'écriture de Déclencheurs, puis-je faire qui?

J'utilise SQL Server 2005.

77
demandé sur faintsignal 2009-05-15 01:57:06

6 réponses

Ajoutez une contrainte de vérification comme celle-ci. La différence est que vous retournerez false si Status = 1 et Count > 0.

Http://msdn.microsoft.com/en-us/library/ms188258.aspx

CREATE TABLE CheckConstraint
(
  Id TINYINT,
  Name VARCHAR(50),
  RecordStatus TINYINT
)
GO

CREATE FUNCTION CheckActiveCount(
  @Id INT
) RETURNS INT AS BEGIN

  DECLARE @ret INT;
  SELECT @ret = COUNT(*) FROM CheckConstraint WHERE Id = @Id AND RecordStatus = 1;
  RETURN @ret;

END;
GO

ALTER TABLE CheckConstraint
  ADD CONSTRAINT CheckActiveCountConstraint CHECK (NOT (dbo.CheckActiveCount(Id) > 1 AND RecordStatus = 1));

INSERT INTO CheckConstraint VALUES (1, 'No Problems', 2);
INSERT INTO CheckConstraint VALUES (1, 'No Problems', 2);
INSERT INTO CheckConstraint VALUES (1, 'No Problems', 2);
INSERT INTO CheckConstraint VALUES (1, 'No Problems', 1);

INSERT INTO CheckConstraint VALUES (2, 'Oh no!', 1);
INSERT INTO CheckConstraint VALUES (2, 'Oh no!', 2);
-- Msg 547, Level 16, State 0, Line 14
-- The INSERT statement conflicted with the CHECK constraint "CheckActiveCountConstraint". The conflict occurred in database "TestSchema", table "dbo.CheckConstraint".
INSERT INTO CheckConstraint VALUES (2, 'Oh no!', 1);

SELECT * FROM CheckConstraint;
-- Id   Name         RecordStatus
-- ---- ------------ ------------
-- 1    No Problems  2
-- 1    No Problems  2
-- 1    No Problems  2
-- 1    No Problems  1
-- 2    Oh no!       1
-- 2    Oh no!       2

ALTER TABLE CheckConstraint
  DROP CONSTRAINT CheckActiveCountConstraint;

DROP FUNCTION CheckActiveCount;
DROP TABLE CheckConstraint;
35
répondu D. Patrick 2009-05-14 23:03:22

Voici, l'index filtré. De la documentation (emphase mine):

Un index filtré est un index non clustered optimisé spécialement adapté pour couvrir les requêtes qui sélectionnent à partir d'un sous-ensemble de données bien défini. , Il utilise un prédicat de filtre pour indexer une partie des lignes de la table. un index filtré bien conçu peut améliorer les performances des requêtes et réduire les coûts de maintenance et de stockage des index par rapport aux index à table complète.

Et voici un exemple combinant un index unique avec un prédicat de filtre:

create unique index [MyIndex]
on [MyTable]([ID])
where [RecordStatus] = 1

, essentiellement, Cela impose l'unicité de ID lorsque RecordStatus est 1.

Remarque: L'index filtré a été introduit dans SQL Server 2008. Pour les versions antérieures de SQL Server, veuillez consulter cette réponse .

115
répondu canon 2017-05-23 12:03:05

Vous pouvez déplacer les enregistrements supprimés vers une table qui n'a pas la contrainte, et peut-être utiliser une vue avec L'UNION des deux tables pour préserver l'apparence d'une seule table.

9
répondu Carl Manaster 2009-05-14 22:01:58

Vous pouvez le faire d'une manière vraiment hacky...

Créez une vue schemabound sur votre table.

Créer une vue SELECT * from Table Où RecordStatus = 1

Créez maintenant une contrainte unique sur la vue avec les champs que vous voulez.

Une note sur les vues schemabound cependant, si vous modifiez les tables sous-jacentes, vous devrez recréer la vue. Beaucoup de pièges à cause de ça.

3
répondu Min 2009-05-14 22:43:29

Parce que, vous allez autoriser les doublons, une contrainte unique ne fonctionnera pas. Vous pouvez créer une colonne check constraint for RecordStatus et une procédure stockée pour INSERT qui vérifie les enregistrements actifs existants avant d'insérer des ID en double.

1
répondu ichiban 2009-05-14 21:59:14

Si vous ne pouvez pas utiliser NULL comme RecordStatus comme suggéré par Bill, vous pouvez combiner son idée avec un index basé sur la fonction. Créez une fonction qui renvoie NULL si RecordStatus n'est pas l'une des valeurs que vous souhaitez prendre en compte dans votre contrainte (et RecordStatus sinon) et créez un index sur cela.

Cela aura l'avantage de ne pas avoir à examiner explicitement les autres lignes de la table dans votre contrainte, ce qui pourrait vous causer des problèmes de performances.

Je devrais disons que je ne connais pas du tout SQL server, mais j'ai utilisé avec succès cette approche dans Oracle.

1
répondu Hobo 2009-05-14 22:48:37