Jeder Weg um den eindeutigen Index 16 Spalte max

8

Laut CREATE INDEXDokumentation:

Bis zu 16 Spalten können zu einem einzigen zusammengesetzten Indexschlüssel kombiniert werden.

Wir haben eine Tabelle mit ~ 18 Spalten, die eine eindeutige Kombination bilden müssen. Diese Tabelle ist nicht leistungsabhängig - wir aktualisieren selten Werte / fügen Datensätze ein. Wir müssen nur sicherstellen, dass wir das Duplizieren unserer Datensätze vermeiden ... und dachten, wir könnten eine einfache Einschränkung der Eindeutigkeit auferlegen.

Irgendwelche Ideen? Ich bin offen dafür, den eindeutigen Index / die eindeutige Einschränkung vollständig zu vermeiden, wenn es einen besseren Weg gibt.

Nick B.
quelle
4
Das ist ein Tisch.
@ Joe: Unter bestimmten Umständen nicht ungewöhnlich, wenn Sie ähnliche Untertypen zu einem kombiniert haben. In meinem Fall ist ein Schlüssel mit 15 Spalten anstelle von mehr als 50 verschiedenen Tabellen erforderlich. Eine Umsetzungsentscheidung ...
gbn
Während das, was Sie fragen, möglich ist, bin ich mir nicht so sicher, ob es klug ist. Sie folgen nicht dem ausgetretenen Pfad. Als solches erwarten Sie Überraschungen. Es ist wahrscheinlicher, dass Sie aus Ihren eigenen Fehlern lernen als aus denen anderer. Auf lange Sicht könnte es einfacher sein, einen konventionelleren Ansatz zu versuchen. Wenn Sie weitere Details veröffentlichen, können wir Ihnen bei der Implementierung helfen.
AK
Ich weiß, dass es eine Weile her ist, aber was hat Sie daran gehindert, einfach eine GUID-Identitätsspalte zu verwenden?
Robert Harvey

Antworten:

14

Fügen Sie eine persistierte berechnete Spalte hinzu , die die 18 Schlüssel kombiniert, und erstellen Sie dann einen eindeutigen Index für die berechnete Spalte:

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

Siehe Erstellen von Indizes für berechnete Spalten .

Ein anderer Ansatz besteht darin, eine indizierte Ansicht zu erstellen:

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);

Siehe Erstellen indizierter Ansichten .

Beide Ansätze ermöglichen ein Teilschlüsselaggregat: Aggregat c1 + c2 + c3 als k1, c4 + c5 + c6 als k2 usw. Dann indizieren / erstellen Sie eine indizierte Ansicht auf (k1, k2, ...). Dies könnte für Entfernungsscans von Vorteil sein (der Index kann für die Suche nach c1 + c2 + c3 verwendet werden.

Natürlich sind alle +Operationen in meinem Beispiel Zeichenfolgenaggregation. Der tatsächlich zu verwendende Operator hängt von den Typen all dieser Spalten ab (dh Sie müssen möglicherweise explizite Umwandlungen verwenden).

PS. Da eindeutige Einschränkungen durch einen eindeutigen Index erzwungen werden, gilt jede Einschränkung für eindeutige Indizes auch für eindeutige Einschränkungen:

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.

Das Erstellen der Einschränkung für eine persistierte berechnete Spalte funktioniert jedoch wie folgt:

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  

Offensichtlich belegt die persistierte Spalte den Speicherplatz auf der Festplatte, sodass der Ansatz für eine sehr große Tabelle möglicherweise schlecht ist. Der Ansatz der indizierten Ansicht weist dieses Problem nicht auf, sondern belegt nur den Platz für den Index , nicht den Platz für die berechnete Spalte und den Index.

Remus Rusanu
quelle
1
Achten Sie natürlich auf das 900-Byte-Indexschlüssellimit ...
gbn
1
@gbn Ja, und deshalb habe ich mich für die von RBarryYoung vorgeschlagene HashBytes-Funktion entschieden. Ich habe diese Antwort jedoch akzeptiert, da sie eher eine Erklärung und Erforschung verschiedener Methoden bietet. (dh ich habe hier viel gelernt)
Nick B
13

Ich denke, dass Sie es viel besser machen würden, Ihre eindeutige Indexprüfung für eine berechnete Spalte durchzuführen, die mit HASHBYTES('MD5', ...)der Kombination Ihrer 18 Spalten generiert wird .

RBarryYoung
quelle
2

Ich bin auf dieses Problem gestoßen, und mein leitender DBA schlug vor, eine Funktion zur Überprüfung der Eindeutigkeit zu verwenden. Meine Beilagen sind relativ klein und selten (~ 1000 Zeilen, die zu Beginn eines jeden Monats eingefügt werden), und meine einzige Sorge ist die Durchsetzung der Eindeutigkeit.

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, ich habe noch keinen Repräsentanten, um einen Kommentar abzugeben, aber ich hatte Probleme mit der HASHBYTES-Lösung, da einer meiner Datentypen eine Datums- / Uhrzeitangabe war, und ich habe den Fehler des Neulings (?) Begehen, mein nicht das optionale Stilargument anzugeben CONVERT-Funktion beim Konvertieren in Varchar. Ohne den Stil wird der folgende Fehler angezeigt, wenn Sie versuchen, die PERSISTED UNIQUE NONCLUSTEREDEinschränkungen hinzuzufügen :

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

Sie können einige der Werte kombinieren, um einen neuen eindeutigen Wert zu erstellen und diesen zusätzlich zu den aktuellen Daten zu speichern.

Erstellen Sie eine benutzerdefinierte Funktion zum Erstellen der neuen Werte und einen Auslöser zum Auffüllen des Felds, wenn Daten hinzugefügt werden. Dann haben Sie nicht viel mehr Aufwand bei der Pflege des Felds.

Wenn Sie zwei oder drei Ihrer Felder kombinieren, unterschreiten Sie das Limit von 16.

Tony
quelle
-1 Ich bin nicht mit der Idee einverstanden, die Tabelle zu denormalisieren, um die Anzahl der Spalten zu verringern.
Matt M
@Matt M - Ich bin interessiert zu wissen, warum Sie meine Antwort abgelehnt haben, wenn sie sich nicht zu sehr vom ersten Vorschlag in der akzeptierten Antwort auf diese Frage unterscheidet. Ich würde auch gerne wissen, warum Sie nicht einverstanden sind, was wäre Ihre Lösung?
Tony
Tatsächlich unterscheidet sich Ihr Vorschlag tatsächlich von der akzeptierten Lösung. Sie befürworten das Kombinieren von Spalten, während die akzeptierte Lösung das Erstellen einer neuen Spalte befürwortet, die die kombinierten Werte enthält. Ihre Lösung kann möglicherweise Leistungsprobleme durch zu komplexe Abfragen verursachen, um nützliche Daten aus Ihren kombinierten Spalten aufzuteilen. Persönlich würde ich die von RBarryYoung vorgestellte Lösung befürworten, die eine kombinierte berechnete HashBytes PERSISTED-Spalte verwendet, die in einem eindeutigen Index platziert ist. Umgekehrt habe ich seine Lösung positiv bewertet.
Matt M
@Matt M - Vielen Dank für Ihre Erklärung, aber ich habe gesagt: "... erstellen Sie einen neuen eindeutigen Wert und speichern Sie diesen zusätzlich zu den aktuellen Daten." Ich wollte, dass die neue Schlüsselspalte ein neues Feld ist, das die vorhandenen Daten ergänzt und nicht ersetzt. Ich bin damit einverstanden, dass die Verwendung eines persistierten berechneten Feldes besser ist als mein Vorschlag eines UDF, aber im Geiste war meine Lösung dieselbe.
Tony
Es scheint, dass ich Ihre Lösung falsch verstanden habe, und ich entschuldige mich dafür. Allerdings ist das Kombinieren einiger Spalten meiner Meinung nach keine so gute Lösung wie die angegebene HashBytes-Lösung. Ich werde meine -1 zurückziehen. Wieder entschuldige ich mich für mein Leseverständnis.
Matt M
0

Sie könnten mit einem Auslöser für insert/ gehen update. Führen Sie eine ausgewählte Gruppierung nach Ihren Spalten mit der Klausel von durch having count(*) > 1. Wenn das nicht leer zurückkommt, rollen Sie zurück.

Ben Thul
quelle
0

Folgendes würde ich tun. Ich würde einen AFTER-Trigger für INSERT, UPDATE erstellen, der eine ROW_NUMBER ()Funktion ausführt, und Partitionen für alle 18 Ihrer eindeutigen Spalten. Wenn die maximale Zeilennummer größer als eins ist, führen Sie a aus ROLLBACK.

Thomas Stringer
quelle