N'importe quelle façon autour de l'index unique 16 colonnes max

8

Selon la CREATE INDEXdocumentation:

Jusqu'à 16 colonnes peuvent être combinées en une seule clé d'index composite.

Nous avons un tableau avec environ 18 colonnes qui doivent former une combinaison unique. Ce tableau n'est pas sensible aux performances - nous mettons rarement à jour les valeurs / insérer des enregistrements. Nous devons juste nous assurer d'éviter la duplication de nos enregistrements ... et nous avons pensé que nous pourrions imposer une simple contrainte d'unicité.

Des idées? Je suis prêt à éviter complètement l'index / la contrainte unique s'il existe une meilleure solution.

Nick B
la source
4
Voilà une table.
@Joe: pas inhabituel dans certaines circonstances lorsque vous avez combiné des sous-types similaires en un seul. Dans mon cas, une clé de 15 colonnes est requise au lieu de 50+ tables différentes. Une décision de mise en œuvre ...
gbn
Bien que ce que vous demandez soit possible, je ne suis pas sûr que ce soit sage. Vous ne suivez pas les sentiers battus. En tant que tel, vous êtes surpris. Vous êtes plus susceptible d'apprendre sur vos propres erreurs que sur celles des autres. À long terme, il pourrait être plus facile d'essayer une approche plus conventionnelle. Si vous publiez plus de détails, nous pourrions vous aider avec la mise en œuvre.
AK
Je sais que cela fait un moment, mais qu'est-ce qui vous a empêché d'utiliser simplement une colonne d'identité GUID?
Robert Harvey

Réponses:

14

Ajoutez une colonne calculée persistante qui combine les 18 clés, puis créez un index unique sur la colonne calculée:

alter table t add all_keys as c1+c2+c3+...+c18 persisted;
create unique index i18 on t (all_keys);

Voir Création d'index sur des colonnes calculées .

Une autre approche consiste à créer une vue indexée:

create view v 
with schemabinding
as select c1+c2+c3+...+c18 as all_keys
from dbo.t;

create unique clustered index c18 on v(all_keys);

Voir Création de vues indexées .

Les deux approches permettent un agrégat de clé partiel: agrégat c1 + c2 + c3 comme k1, c4 + c5 + c6 comme k2 etc. puis indexer / créer une vue indexée sur (k1, k2, ...). Thia pourrait être bénéfique pour les balayages de plage (l'index peut être utilisé pour la recherche sur c1 + c2 + c3.

Bien sûr, toutes les +opérations dans mon exemple sont une agrégation de chaînes, l'opérateur réel à utiliser dépend des types de toutes ces colonnes (c'est-à-dire que vous devrez peut-être utiliser des transtypages explicites).

PS. Comme les contraintes uniques sont appliquées par un index unique, toute restriction sur les index uniques s'appliquera également aux contraintes uniques:

create table t (
    c1 char(3), c2 char(3), c3 char(3), c4 char(3),
    c5 char(3), c6 char(3), c7 char(3), c8 char(3),
    c9 char(3), c10 char(3), c11 char(3), c12 char(3),
    c13 char(3), c14 char(3), c15 char(3), c16 char(3),
    c17 char(3), c18 char(3), c19 char(3), c20 char(3),
    constraint unq unique
      (c1,c2,c3,c4,c5,c6,c7,c8,c9,c10,c11,c12,c13,c14,c15,c16,c17,c18));
go  


Msg 1904, Level 16, State 1, Line 3
The index '' on table 't' has 18 column names in index key list. 
The maximum limit for index or statistics key column list is 16.
Msg 1750, Level 16, State 0, Line 3
Could not create constraint. See previous errors.

Cependant, la création de la contrainte sur une colonne calculée persistante fonctionne:

create table t (
    c1 char(3), c2 char(3), c3 char(3), c4 char(3),
    c5 char(3), c6 char(3), c7 char(3), c8 char(3),
    c9 char(3), c10 char(3), c11 char(3), c12 char(3),
    c13 char(3), c14 char(3), c15 char(3), c16 char(3),
    c17 char(3), c18 char(3), c19 char(3), c20 char(3),
    all_c as 
        c1+c2+c3+c4+c5+c6+c7+c8+c9+c10+c11+
        c12+c13+c14+c15+c16+c17+c18 
        persisted
        constraint unq unique (all_c));
go  

De toute évidence, la colonne persistante consomme l'espace sur le disque, donc l'approche peut être mauvaise pour une très grande table. L'approche de la vue indexée n'a pas ce problème, elle consomme uniquement l'espace pour l' index , pas l'espace pour la colonne et l' index calculés .

Remus Rusanu
la source
1
Surveillez la limite de la clé d'index de 900 octets bien sûr ...
gbn
1
@gbn Oui, et c'est pourquoi j'ai fini par utiliser la fonction HashBytes comme suggéré par RBarryYoung. Cependant, j'ai accepté cette réponse car elle fournissait plus d'explications et d'exploration de différentes méthodes. (c'est-à-dire que j'ai beaucoup appris ici)
Nick B
13

Je pense que vous feriez beaucoup mieux de placer votre vérification d'index unique sur une colonne calculée qui est générée en utilisant HASHBYTES('MD5', ...)la combinaison de vos 18 colonnes.

RBarryYoung
la source
2

J'ai rencontré ce problème et mon DBA senior a suggéré d'utiliser une fonction de vérification d'unicité. Mes insertions sont relativement petites et peu fréquentes (~ 1000 lignes, insérées au début de chaque mois) et ma seule préoccupation est de faire respecter l'unicité.

CREATE FUNCTION dbo.fn_UQ_table1 ()  
RETURNS BIT

AS
BEGIN
      DECLARE @ResultBit BIT = 1

      IF EXISTS(
      SELECT COUNT(*)
      FROM [table1]
      GROUP BY [c1],[c2],[c3],[c4],[c5],[c6],
            [c7],[c8],[c9],[c10],[c11],[c12],
            [c13],[c14],[c15],[c16]
      HAVING COUNT(*) > 1)
      SELECT @ResultBit = 0

      RETURN      @ResultBit

END

SELECT dbo.fn_UQ_table1()

ALTER TABLE [table1]  
WITH NOCHECK ADD  
CONSTRAINT [CK_UQ] CHECK  (([dbo].[fn_UQ_table1]()=1))

@RBarryYoung, je n'ai pas encore de représentant pour commenter, mais j'ai eu des problèmes avec la solution HASHBYTES car l'un de mes types de données était un datetime, et j'ai commis l'erreur newbie (?) De ne pas fournir l'argument de style optionnel à mon Fonction CONVERT lors de la conversion en varchar. Sans le style, vous obtenez l'erreur suivante lorsque vous essayez d'ajouter les PERSISTED UNIQUE NONCLUSTEREDcontraintes:

"column 'key_hash' in table 'table1' cannot be persisted because 
the column is non-deterministic."
Apoxy
la source
0

Vous pouvez combiner certaines des valeurs afin de créer une nouvelle valeur unique et la stocker en plus des données actuelles.

Créez une fonction définie par l'utilisateur pour créer les nouvelles valeurs et un déclencheur pour remplir le champ lorsque des données sont ajoutées, vous n'avez donc pas beaucoup plus de temps pour gérer le champ.

La combinaison de deux ou trois de vos domaines vous mettrait sous la limite de 16.

Tony
la source
-1 Je ne suis pas d'accord avec l'idée de dénormaliser le tableau dans le but d'abaisser le nombre de colonnes.
Matt M
@Matt M - Je suis intéressé de savoir pourquoi vous avez rejeté ma réponse alors qu'elle n'est pas trop différente de la première suggestion dans la réponse acceptée à cette question? J'aimerais aussi savoir pourquoi vous n'êtes pas d'accord, quelle serait votre solution?
Tony
En fait, votre suggestion est en fait différente de la solution acceptée. Vous préconisez la combinaison de colonnes, tandis que la solution acceptée préconise la création d'une nouvelle colonne contenant les valeurs combinées. Votre solution pourrait potentiellement présenter des problèmes de performances via des requêtes trop complexes pour séparer les données utiles de vos colonnes combinées. Personnellement, je préconiserais la solution présentée par RBarryYoung qui utilise une colonne calculée combinée HashBytes PERSISTED placée dans un index unique. Inversement, j'ai voté pour sa solution.
Matt M
@Matt M - Merci pour votre explication, mais j'ai dit "... créer une nouvelle valeur unique et la stocker en plus des données actuelles." Je voulais que la nouvelle colonne clé soit un nouveau champ complétant les données existantes et ne les remplaçant pas. Je suis d'accord que l'utilisation d'un champ calculé persistant est meilleure que ma suggestion d'un UDF mais, dans l'esprit, ma solution était la même.
Tony
Il semble que j'aie mal lu votre solution et je m'en excuse. Cela étant dit, la combinaison de quelques colonnes n'est pas une bonne solution, à mon avis, comme la solution HashBytes donnée. Je vais rétracter mon -1. Encore une fois, je m'excuse pour ma compréhension de la lecture.
Matt M
0

Vous pouvez utiliser un déclencheur pour insert/ update. Effectuez un regroupement sélectionné par vos colonnes avec une clause de having count(*) > 1. Si cela revient non vide, annulez.

Ben Thul
la source
0

Voici ce que je ferais. Je créerais un déclencheur AFTER pour INSERT, UPDATE qui fait une ROW_NUMBER ()fonction et partitionne chacune des 18 colonnes uniques. Si le nombre maximal de lignes est supérieur à un, faites a ROLLBACK.

Thomas Stringer
la source