SQL Serverは外部キー参照のインデックスキーをどのように選択しますか?


9

MS Accessからインポートされたレガシーデータベースを使用しています。MS Access> SQL Serverのアップグレード中に作成された、クラスター化されていない一意の主キーを持つ約20のテーブルがあります。

これらのテーブルの多くには、主キーの複製である一意の非クラスター化インデックスもあります。

私はこれを片付けようとしています。

しかし、主キーをクラスター化インデックスとして再作成した後、外部キーを再構築しようとすると、外部キーが(重複していた)古い重複インデックスを参照しています。

重複したインデックスを削除できないため、これを知っています。

SQL Serverでは、主キーが存在する場合は常にそれを選択すると思います。SQL Serverには、一意のインデックスと主キーの間で選択する方法がありますか?

問題を再現するには(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 

エラーメッセージ:

メッセージ3723、レベル16、状態6、行36明示的なDROP INDEXは、インデックス 'Parent.IX_Parent'では許可されていません。FOREIGN KEY制約の強制に使用されています。


コメントは詳細な議論のためのものではありません。この会話はチャットに移動しました
ポールホワイト9

回答:


7

ドキュメント(の欠如)は、この動作は実装の詳細であるため、定義されておらず、いつでも変更される可能性があることを示唆しています。

これは、アタッチするインデックスの名前を指定する必要があるCREATE FULLTEXT INDEXとはまったく対照的FOREIGN KEYです。

前述のように、SQL Serverが外部キーを関連付けるための最小の物理インデックスを選択することは理にかなっています。一意の制約を作成するようにスクリプトを変更した場合、スクリプトCLUSTEREDは2008 R2で「機能します」。ただし、その動作はまだ定義されていないため、信頼することはできません。

ほとんどのレガシーアプリケーションと同様に、要点を明確にし、クリーンアップする必要があります。


「SQL Serverは、必ずしも外部キーを関連付けるための最小の物理インデックスを選択します」実際には必ずしもそうではありません。SqlServerが最小の物理サイズではないインデックスを選択する隣接する回答の例があります。
i-one

3

SQL Serverには、一意のインデックスと主キーの間で選択する方法がありますか?

外部キーが作成されており、代替キー制約または一意のインデックスが参照されているテーブルに存在する場合、少なくともSqlServerが主キーを参照するように指示することが可能です。

主キーを参照する必要がある場合は、参照されるテーブルの名前のみを外部キー定義で指定し、参照される列のリストを省略する必要があります。

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

詳細は以下をご覧ください。


次の設定を検討してください。

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

ここでtable TRefはtable を参照することを意図していますT

参照制約を作成するには、ALTER TABLE次の2つの方法でコマンドを使用できます。

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;

2番目のケースでは、参照されるテーブルの列が指定されていないことに注意してください(REFERENCES TREFERENCES T (id))。

Tまだキーインデックスがないため、これらのコマンドを実行するとエラーが発生します。

最初のコマンドは次のエラーを返します:

メッセージ1776、レベル16、状態0、行4

参照されるテーブル 'T'に、外部キー 'FK_TRef_T_1'の参照列リストと一致する主キーまたは候補キーがありません。

ただし、2番目のコマンドは異なるエラーを返します。

メッセージ1773、レベル16、状態0、行4

外部キー 'FK_TRef_T_2'には、主キーが定義されていないオブジェクト 'T'への暗黙の参照があります。

最初のケースでは、期待値は主キーまたは候補キーですが、2番目のケースでは、期待値は主キーのみです。

SqlServerが2番目のコマンドで主キー以外のものを使用するかどうかを確認しましょう。

一意のインデックスと一意のキーを追加すると、次のようになります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);

FK_TRef_T_1作成のためのコマンドは成功しますが、作成のためのコマンドはFK_TRef_T_2まだメッセージ1773で失敗します。

最後に、主キーを追加するとT

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

FK_TRef_T_2作成コマンドが成功しました。

テーブルのT外部キーによって参照されているテーブルのインデックスを確認してみましょう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');

これは次を返します:

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

FK_TRef_T_2対応するものを参照してくださいPK_T

したがって、はい、REFERENCES T構文を使用して、外部キーがのTRef主キーにマップされますT

SqlServerのドキュメントに記載されているそのような動作を直接見つけることはできませんでしたが、専用のメッセージ1773は、それが偶然ではないことを示唆しています。おそらくそのような実装はSQL標準への準拠を提供します。以下は、ANSI / ISO 9075-2:2003のセクション11.8からの短い抜粋です。

11スキーマの定義と操作

11.8 <参照制約定義>

機能
参照制約を指定します。

フォーマット

<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> ]
...

構文規則
...
3)ケース:
...
b)<参照されるテーブルと列>が<参照列リスト>を指定しない場合、参照されるテーブルのテーブル記述子には、PRIMARY KEYを指定する一意の制約が含まれます。してみましょう参照される列は、そのユニーク制約でユニーク列によって識別される列または列こととしましょう参照される列は、 その一列です。<参照されるテーブルと列>は、その<一意の列リスト>と同一の<参照列リスト>を暗黙的に指定していると見なされます。
...

Transact-SQLは、ANSI SQLをサポートおよび拡張します。ただし、SQL標準には厳密には準拠していません。Transact-SQLによって提供されるサポートのレベルを説明するSQL Server Transact-SQL ISO / IEC 9075-2標準サポートドキュメント(略してMS-TSQLISO02、こちらを参照)という名前のドキュメントがあります。このドキュメントには、標準に対する拡張とバリエーションがリストされています。たとえば、そのMATCH句が参照制約定義でサポートされていないことを示しています。しかし、引用された規格に関連する文書化されたバリエーションはありません。だから、私の意見は、観察された行動は十分に文書化されているということです。

そしてREFERENCES T (<reference column list>)構文を使用すると、SqlServerは参照されているテーブルのインデックス(index_id見た目が最も少ないもの、質問のコメントで想定されている最小の物理サイズではないもの)から最初の適切な非クラスター化インデックスを選択するようです。適しており、適切な非クラスター化インデックスはありません。このような動作は、SqlServer 2008(バージョン10.0)以降一貫しています。もちろんこれは単なる観察であり、この場合の保証はありません。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.