O SQL Server tem um método de escolher entre um índice exclusivo e uma chave primária?
Pelo menos, é possível direcionar o SqlServer para referenciar a chave primária, quando uma chave estrangeira estiver sendo criada e restrições de chave alternativas ou índices exclusivos existirem na tabela que está sendo referenciada.
Se a chave primária precisar ser referenciada, apenas o nome da tabela que está sendo referenciada deve ser especificado na definição de chave estrangeira e a lista de colunas que estão sendo referenciadas deve ser omitida:
ALTER TABLE Child
ADD CONSTRAINT FK_Child_Parent FOREIGN KEY (ParentID)
-- omit key columns of the referenced table
REFERENCES Parent /*(ParentID)*/;
Mais detalhes abaixo.
Considere a seguinte configuração:
CREATE TABLE T (id int NOT NULL, a int, b int, c uniqueidentifier, filler binary(1000));
CREATE TABLE TRef (tid int NULL);
onde tabela TRef
pretende fazer referência a tabela T
.
Para criar uma restrição referencial, pode-se usar o ALTER TABLE
comando com duas alternativas:
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;
observe que no segundo caso, nenhuma coluna da tabela que está sendo referenciada é especificada ( REFERENCES T
versus REFERENCES T (id)
).
Como ainda não há índices de chave T
, a execução desses comandos gerará erros.
O primeiro comando retorna o seguinte erro:
Msg 1776, Nível 16, Estado 0, Linha 4
Não há chaves primárias ou candidatas na tabela referenciada 'T' que correspondam à lista da coluna de referência na chave estrangeira 'FK_TRef_T_1'.
O segundo comando, no entanto, retorna um erro diferente:
Msg 1773, Nível 16, Estado 0, Linha 4
A chave estrangeira 'FK_TRef_T_2' tem referência implícita ao objeto 'T' que não possui uma chave primária definida.
veja que, no primeiro caso, a expectativa é chave primária ou candidata , enquanto que no segundo caso, a expectativa é apenas chave primária .
Vamos verificar se o SqlServer usará algo diferente da chave primária com o segundo comando ou não.
Se adicionarmos alguns índices exclusivos e chave exclusiva em 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);
comando para FK_TRef_T_1
criação é bem-sucedido, mas o comando para FK_TRef_T_2
criação ainda falha com a mensagem 1773.
Por fim, se adicionarmos a chave primária em T
:
ALTER TABLE T
ADD CONSTRAINT PK_T PRIMARY KEY NONCLUSTERED (id);
comando para FK_TRef_T_2
criação bem-sucedido.
Vamos verificar quais índices da tabela T
são referenciados por chaves estrangeiras da tabela 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');
isso retorna:
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
veja que FK_TRef_T_2
corresponde a PK_T
.
Portanto, sim, com o uso da REFERENCES T
sintaxe, a chave estrangeira de TRef
é mapeada para a chave primária de T
.
Não consegui encontrar esse comportamento descrito diretamente na documentação do SqlServer, mas a Msg 1773 dedicada sugere que não é acidental. Provavelmente, essa implementação fornece conformidade com o Padrão SQL, abaixo está um pequeno trecho da seção 11.8 da ANSI / ISO 9075-2: 2003
11 Definição e manipulação de esquema
11.8 <definição de restrição referencial>
Função
Especifique uma restrição referencial.
Formato
<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> ]
...
Regras de sintaxe
...
3) Caso:
...
b) Se a <tabela e colunas referenciadas> não especificar uma <lista de colunas de referência>, o descritor da tabela da tabela referenciada incluirá uma restrição exclusiva que especifica PRIMARY KEY. Permita que colunas referenciadas sejam a coluna ou colunas identificadas pelas colunas exclusivas nessa restrição exclusiva e permita que a coluna referenciada
seja uma dessas colunas. Considera-se que a <tabela e colunas referenciadas> especifica implicitamente uma <lista de colunas de referência> idêntica àquela <lista de colunas exclusivas>.
...
O Transact-SQL suporta e estende o ANSI SQL. No entanto, não está em conformidade com o SQL Standard exatamente. Existe um documento chamado Documento de Suporte de Padrões do SQL Server Transact-SQL ISO / IEC 9075-2 (MS-TSQLISO02, resumindo, veja aqui ) descrevendo o nível de suporte fornecido pelo Transact-SQL. O documento lista extensões e variações para o padrão. Por exemplo, documenta que a MATCH
cláusula não é suportada na definição de restrição referencial. Mas não há variações documentadas relevantes para a peça citada do padrão. Então, minha opinião é que o comportamento observado é documentado o suficiente.
E, com o uso da REFERENCES T (<reference column list>)
sintaxe, parece que o SqlServer seleciona o primeiro índice não clusterizado adequado entre os índices da tabela que está sendo referenciada (aquele com o menos index_id
aparentemente, não aquele com o menor tamanho físico conforme assumido nos comentários da pergunta) ou o índice agrupado, se processos e não há índices não clusterizados adequados. Esse comportamento parece ser consistente desde o SqlServer 2008 (versão 10.0). Esta é apenas uma observação, é claro, não há garantias neste caso.