Wie wählt SQL Server einen Indexschlüssel für eine Fremdschlüsselreferenz aus?


9

Ich arbeite mit einer Legacy-Datenbank, die aus MS Access importiert wurde. Es gibt ungefähr zwanzig Tabellen mit nicht gruppierten, eindeutigen Primärschlüsseln, die während des MS Access> SQL Server-Upgrades erstellt wurden.

Viele dieser Tabellen haben auch eindeutige, nicht gruppierte Indizes, die Duplikate des Primärschlüssels sind.

Ich versuche das aufzuräumen.

Nachdem ich die Primärschlüssel als Clustered-Indizes neu erstellt und dann versucht habe, den Fremdschlüssel neu zu erstellen, verweist der Fremdschlüssel auf den alten, doppelten Index (der eindeutig war).

Ich weiß das, weil ich dadurch die doppelten Indizes nicht löschen kann.

Ich würde denken, SQL Server würde immer einen Primärschlüssel wählen, wenn einer existiert. Verfügt SQL Server über eine Methode zur Auswahl zwischen einem eindeutigen Index und einem Primärschlüssel?

So duplizieren Sie das Problem (unter SQL Server 2008 R2):

IF EXISTS (SELECT * FROM sys.tables WHERE name = 'Child') DROP TABLE Child
GO
IF EXISTS (SELECT * FROM sys.tables WHERE name = 'Parent') DROP TABLE Parent
GO

-- Create the parent table
CREATE TABLE Parent (ParentID INT NOT NULL IDENTITY(1,1)) 

-- Make the parent table a heap
ALTER TABLE Parent ADD CONSTRAINT PK_Parent PRIMARY KEY NONCLUSTERED (ParentID) 

-- Create the duplicate index on the parent table
CREATE UNIQUE NONCLUSTERED INDEX IX_Parent ON Parent (ParentID) 

-- Create the child table
CREATE TABLE Child  (ChildID  INT NOT NULL IDENTITY(1,1), ParentID INT NOT NULL ) 

-- Give the child table a normal PKey
ALTER TABLE Child ADD CONSTRAINT PK_Child PRIMARY KEY CLUSTERED (ChildID) 

-- Create a foreign key relationship with the Parent table on ParentID
ALTER TABLE Child ADD CONSTRAINT FK_Child FOREIGN KEY (ParentID) 
REFERENCES Parent (ParentID) ON DELETE CASCADE NOT FOR REPLICATION

-- Try to clean this up
-- Drop the foreign key constraint on the Child table
ALTER TABLE Child DROP CONSTRAINT FK_Child

-- Drop the primary key constraint on the Parent table
ALTER TABLE Parent DROP CONSTRAINT PK_Parent

-- Recreate the primary key on Parent as a clustered index
ALTER TABLE Parent ADD CONSTRAINT PK_Parent PRIMARY KEY CLUSTERED (ParentID) 

-- Recreate the foreign key in Child pointing to parent ID
ALTER TABLE Child ADD CONSTRAINT FK_Child FOREIGN KEY (ParentID) 
REFERENCES Parent (ParentID) ON DELETE CASCADE NOT FOR REPLICATION

-- Try to drop the duplicate index on Parent 
DROP INDEX IX_Parent ON Parent 

Fehlermeldung:

Meldung 3723, Ebene 16, Status 6, Zeile 36 Ein expliziter DROP INDEX ist für den Index 'Parent.IX_Parent' nicht zulässig. Es wird für die Durchsetzung von FOREIGN KEY-Einschränkungen verwendet.


Kommentare sind nicht für eine ausführliche Diskussion gedacht. Dieses Gespräch wurde in den Chat verschoben .
Paul White 9

Antworten:


7

Die (fehlende) Dokumentation legt nahe, dass dieses Verhalten ein Implementierungsdetail ist und daher undefiniert ist und jederzeit geändert werden kann.

Dies steht in krassem Gegensatz zu CREATE FULLTEXT INDEX , wo Sie den Namen eines Index angeben müssen, an den angehängt werden soll - AFAIK, es gibt keine undokumentierte FOREIGN KEYSyntax, um das Äquivalent zu tun (obwohl dies theoretisch in der Zukunft möglich sein könnte).

Wie bereits erwähnt, ist es sinnvoll, dass SQL Server den kleinsten physischen Index auswählt, dem der Fremdschlüssel zugeordnet werden soll. Wenn Sie das Skript ändern, um die eindeutige Einschränkung als zu erstellen CLUSTERED, "funktioniert" das Skript unter 2008 R2. Dieses Verhalten ist jedoch noch nicht definiert und sollte nicht als verlässlich angesehen werden.

Wie bei den meisten Legacy-Anwendungen müssen Sie sich nur auf das Wesentliche konzentrieren und die Dinge bereinigen.


"SQL Server wählt den kleinsten physischen Index aus, mit dem der Fremdschlüssel verknüpft werden soll", nicht unbedingt tatsächlich. In der benachbarten Antwort gibt es ein Beispiel, in dem SqlServer einen Index auswählt, der nicht die kleinste physische Größe hat.
i-one

3

Verfügt SQL Server über eine Methode zur Auswahl zwischen einem eindeutigen Index und einem Primärschlüssel?

Zumindest ist es möglich, SqlServer anzuweisen, auf den Primärschlüssel zu verweisen, wenn ein Fremdschlüssel erstellt wird und alternative Schlüsselbeschränkungen oder eindeutige Indizes für die Tabelle vorhanden sind, auf die verwiesen wird.

Wenn auf den Primärschlüssel verwiesen werden muss, sollte in der Fremdschlüsseldefinition nur der Name der Tabelle angegeben werden, auf die verwiesen wird, und die Liste der Spalten, auf die verwiesen wird, sollte weggelassen werden:

ALTER TABLE Child
    ADD CONSTRAINT FK_Child_Parent FOREIGN KEY (ParentID)
        -- omit key columns of the referenced table
        REFERENCES Parent /*(ParentID)*/;

Weitere Details unten.


Betrachten Sie das folgende Setup:

CREATE TABLE T (id int NOT NULL, a int, b int, c uniqueidentifier, filler binary(1000));
CREATE TABLE TRef (tid int NULL);

Dabei TRefbeabsichtigt die Tabelle, auf die Tabelle zu verweisen T.

Um eine referenzielle Einschränkung zu erstellen, kann ein ALTER TABLEBefehl mit zwei Alternativen verwendet werden:

ALTER TABLE TRef
    ADD CONSTRAINT FK_TRef_T_1 FOREIGN KEY (tid) REFERENCES T (id);

ALTER TABLE TRef
    ADD CONSTRAINT FK_TRef_T_2 FOREIGN KEY (tid) REFERENCES T;

Beachten Sie, dass im zweiten Fall keine Spalten der Tabelle angegeben werden, auf die verwiesen wird ( REFERENCES Tversus REFERENCES T (id)).

Da noch keine Schlüsselindizes vorhanden sind T, führt die Ausführung dieser Befehle zu Fehlern.

Der erste Befehl gibt folgenden Fehler zurück:

Meldung 1776, Ebene 16, Status 0, Zeile 4

Die referenzierte Tabelle 'T' enthält keine Primär- oder Kandidatenschlüssel , die mit der Liste der referenzierenden Spalten im Fremdschlüssel 'FK_TRef_T_1' übereinstimmen.

Der zweite Befehl gibt jedoch einen anderen Fehler zurück:

Nachricht 1773, Ebene 16, Status 0, Zeile 4

Der Fremdschlüssel 'FK_TRef_T_2' verweist implizit auf das Objekt 'T', auf dem kein Primärschlüssel definiert ist.

Sehen Sie, dass im ersten Fall die Erwartung Primär- oder Kandidatenschlüssel ist , während im zweiten Fall die Erwartung nur der Primärschlüssel ist .

Lassen Sie uns überprüfen, ob SqlServer mit dem zweiten Befehl etwas anderes als den Primärschlüssel verwendet oder nicht.

Wenn wir einige eindeutige Indizes und einen eindeutigen Schlüssel hinzufügen für T:

CREATE UNIQUE INDEX IX_T_1 on T(id) INCLUDE (filler);
CREATE UNIQUE INDEX IX_T_2 on T(id) INCLUDE (c);
CREATE UNIQUE INDEX IX_T_3 ON T(id) INCLUDE (a, b);

ALTER TABLE T
    ADD CONSTRAINT UQ_T UNIQUE CLUSTERED (id);

Befehl zum FK_TRef_T_1Erstellen ist erfolgreich, aber Befehl zum FK_TRef_T_2Erstellen schlägt mit Msg 1773 immer noch fehl.

Zum Schluss, wenn wir den Primärschlüssel hinzufügen T:

ALTER TABLE T
    ADD CONSTRAINT PK_T PRIMARY KEY NONCLUSTERED (id);

Befehl zur FK_TRef_T_2Erstellung erfolgreich.

Lassen Sie uns überprüfen, auf welche Indizes der Tabelle Tvon Fremdschlüsseln der Tabelle verwiesen wird TRef:

select
    ix.index_id,
    ix.name as index_name,
    ix.type_desc as index_type_desc,
    fk.name as fk_name
from sys.indexes ix
    left join sys.foreign_keys fk on
        fk.referenced_object_id = ix.object_id
        and fk.key_index_id = ix.index_id
        and fk.parent_object_id = object_id('TRef')
where ix.object_id = object_id('T');

dies gibt zurück:

index_id  index_name  index_type_desc   fk_name
--------- ----------- ----------------- ------------
1         UQ_T        CLUSTERED         NULL
2         IX_T_1      NONCLUSTERED      FK_TRef_T_1
3         IX_T_2      NONCLUSTERED      NULL
4         IX_T_3      NONCLUSTERED      NULL
5         PK_T        NONCLUSTERED      FK_TRef_T_2

siehe das FK_TRef_T_2entspricht PK_T.

Also, ja, bei Verwendung der REFERENCES TSyntax wird der Fremdschlüssel von TRefdem Primärschlüssel von zugeordnet T.

Ich konnte ein solches Verhalten, das in der SqlServer-Dokumentation beschrieben ist, nicht direkt finden, aber die dedizierte Meldung 1773 legt nahe, dass es nicht zufällig ist. Wahrscheinlich bietet eine solche Implementierung die Konformität mit dem SQL-Standard. Nachfolgend finden Sie einen kurzen Auszug aus Abschnitt 11.8 von ANSI / ISO 9075-2: 2003

11 Schemadefinition und -manipulation

11.8 <Definition der referenziellen Einschränkung>

Funktion
Geben Sie eine referenzielle Einschränkung an.

Format

<referential constraint definition> ::=
    FOREIGN KEY <left paren> <referencing columns> <right paren>
        <references specification>

<references specification> ::=
    REFERENCES <referenced table and columns>
    [ MATCH <match type> ]
    [ <referential triggered action> ]
...

Syntaxregeln
...
3) Fall:
...
b) Wenn in der <referenzierten Tabelle und Spalten> keine <Referenzspaltenliste> angegeben ist, muss der Tabellendeskriptor der referenzierten Tabelle eine eindeutige Einschränkung enthalten, die PRIMARY KEY angibt. Sei referenzierte Spalten die Spalte oder Spalten, die durch die eindeutigen Spalten in dieser eindeutigen Einschränkung identifiziert werden, und sei referenzierte Spalte eine solche Spalte. Es wird davon ausgegangen, dass die <referenzierte Tabelle und Spalten> implizit eine <Referenzspaltenliste> angeben, die mit dieser <eindeutigen Spaltenliste> identisch ist.
...

Transact-SQL unterstützt und erweitert ANSI SQL. Es entspricht jedoch nicht genau dem SQL-Standard. Es gibt ein Dokument mit dem Namen SQL Server Transact-SQL ISO / IEC 9075-2-Standardunterstützungsdokument (kurz MS-TSQLISO02, siehe hier ), das den Unterstützungsgrad beschreibt, der von Transact-SQL bereitgestellt wird. Das Dokument listet Erweiterungen und Variationen des Standards auf. Beispielsweise wird dokumentiert, dass die MATCHKlausel in der Definition der referenziellen Einschränkung nicht unterstützt wird. Es gibt jedoch keine dokumentierten Abweichungen, die für das zitierte Stück Standard relevant sind. Meiner Meinung nach ist das beobachtete Verhalten ausreichend dokumentiert.

Und unter Verwendung der REFERENCES T (<reference column list>)Syntax scheint es, dass SqlServer den ersten geeigneten nicht gruppierten Index unter den Indizes der Tabelle auswählt, auf die verwiesen wird (der mit der geringsten index_idscheinbaren, nicht der mit der kleinsten physischen Größe, wie in den Fragenkommentaren angenommen), oder den gruppierten Index, falls dies der Fall ist Anzüge und es gibt keine geeigneten nicht gruppierten Indizes. Ein solches Verhalten scheint seit SqlServer 2008 (Version 10.0) konsistent zu sein. Dies ist natürlich nur eine Beobachtung, in diesem Fall keine Garantie.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.