web-dev-qa-db-ja.com

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

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
8kb

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

これは、アタッチするインデックスの名前を指定する必要がある CREATE FULLTEXT INDEX とはまったく対照的です-AFAIK、同等のことを行うドキュメント化されていないFOREIGN KEY構文はありません(ただし、理論的には、将来あるかもしれません)。

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

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

7
Jon Seigel

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

ここで、テーブルTRefはテーブルTを参照することを意図しています。

参照制約を作成するには、次の2つの選択肢がある_ALTER TABLE_コマンドを使用できます。

_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 T_とREFERENCES 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_作成のコマンドは成功しますが、Msg 1773で_FK_TRef_T_2_作成のコマンドはまだ失敗します。

最後に、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を指定する一意の制約が含まれます。 参照列をその一意制約の一意の列によって識別される1つまたは複数の列とし、参照列をそのような列の1つとします。 <参照されるテーブルと列>は、その<一意の列リスト>と同一の<参照列リスト>を暗黙的に指定すると見なされます。
...

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

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

3
i-one