Qualquer maneira de contornar o índice único 16 no máximo

8

De acordo com a CREATE INDEXdocumentação:

Até 16 colunas podem ser combinadas em uma única chave de índice composta.

Temos uma tabela com ~ 18 colunas que precisam formar uma combinação única. Esta tabela não é sensível ao desempenho - raramente atualizamos valores / inserimos registros. Só precisamos garantir que evitamos duplicar nossos registros ... e pensamos que poderíamos impor uma restrição de exclusividade simples.

Alguma ideia? Estou aberto a evitar inteiramente o índice / restrição exclusivo, se houver uma maneira melhor.

Nick B
fonte
4
Essa é uma mesa.
@ Joe: não é incomum em algumas circunstâncias, quando você combina subtipos semelhantes em um. No meu caso, é necessária uma chave de 15 colunas em vez de mais de 50 tabelas diferentes. Uma decisão de implementação ...
gbn
Embora o que você está pedindo seja possível, não tenho tanta certeza de que seja sábio. Você não está seguindo o caminho batido. Como tal, você terá surpresas. É mais provável que você aprenda por seus próprios erros do que pelos outros. A longo prazo, pode ser mais fácil tentar uma abordagem mais convencional. Se você postar mais detalhes, poderíamos ajudar com a implementação.
AK
Sei que já faz um tempo, mas o que o impediu de simplesmente usar uma coluna de identidade GUID?
Robert Harvey

Respostas:

14

Adicione uma coluna computada persistente que combine as 18 teclas e crie um índice exclusivo na coluna computada:

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

Consulte Criando índices em colunas computadas .

Outra abordagem é criar uma exibição indexada:

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

Consulte Criando vistas indexadas .

Ambas as abordagens permitem um agregado de chave parcial: agregado c1 + c2 + c3 como k1, c4 + c5 + c6 como k2 etc., em seguida, indexa / cria uma exibição indexada em (k1, k2, ...). Tia pode ser benéfico para varreduras de intervalo (o índice pode ser usado para pesquisa em c1 + c2 + c3.

Obviamente, todas as +operações no meu exemplo são agregação de cadeias, o operador real a ser usado depende dos tipos de todas essas colunas (por exemplo, você pode precisar usar projeções explícitas).

PS. Como restrições exclusivas são impostas por um índice exclusivo, qualquer restrição em índices exclusivos também se aplica a restrições exclusivas:

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.

No entanto, a criação da restrição em uma coluna computada persistente funciona:

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  

Obviamente, a coluna persistente consome o espaço no disco, portanto a abordagem pode ser ruim para uma tabela muito grande. A abordagem de exibição indexada não apresenta esse problema, apenas consome o espaço para o índice , não o espaço para a coluna e o índice calculados .

Remus Rusanu
fonte
11
Preste atenção no limite da chave do índice de 900 bytes, é claro ...
gbn 11/11
11
@gbn Sim, e foi por isso que acabei usando a função HashBytes, conforme sugerido por RBarryYoung. No entanto, aceitei esta resposta porque ela fornecia mais explicações e exploração de diferentes métodos. (ou seja, eu aprendi muito aqui)
Nick B
13

Eu acho que você faria muito melhor colocar sua verificação de índice exclusiva em uma coluna computada que é gerada usando HASHBYTES('MD5', ...)a combinação de suas 18 colunas.

RBarryYoung
fonte
2

Encontrei esse problema e meu DBA sênior sugeriu o uso de uma função de verificação de exclusividade. Minhas inserções são relativamente pequenas e pouco frequentes (~ 1.000 linhas, inseridas no início de cada mês) e minha única preocupação é reforçar a exclusividade.

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, ainda não tenho o representante para comentar, mas tive problemas com a solução HASHBYTES porque um dos meus tipos de dados era uma data e hora e cometi o erro de novato (?) De não fornecer o argumento de estilo opcional ao meu Função CONVERT ao converter para varchar. Sem o estilo, você obtém o seguinte erro ao tentar adicionar as PERSISTED UNIQUE NONCLUSTEREDrestrições:

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

Você pode combinar alguns dos valores para criar um novo valor exclusivo e armazená-lo, além dos dados atuais.

Crie uma função definida pelo usuário para criar os novos valores e um acionador para preencher o campo quando os dados forem adicionados, para que você não tenha muito mais sobrecarga na manutenção do campo.

A combinação de dois ou três de seus campos colocaria você abaixo do limite de 16.

Tony
fonte
-1 Não concordo com a idéia de desnormalizar a tabela com o objetivo de diminuir o número de colunas.
Matt M
@ Matt M - Estou interessado em saber por que você votou negativamente na minha resposta quando ela não é muito diferente da primeira sugestão na resposta aceita para esta pergunta? Também gostaria de saber por que você não concorda, qual seria sua solução?
Tony
Na verdade, sua sugestão é, de fato, diferente da solução aceita. Você está defendendo a combinação de colunas, enquanto a solução aceita é a criação de uma nova coluna que contenha os valores combinados. Sua solução pode apresentar problemas de desempenho por meio de consultas excessivamente complexas para dividir dados úteis de suas colunas combinadas. Pessoalmente, eu defenderia a solução apresentada por RBarryYoung que utiliza uma coluna computada combinada HashBytes PERSISTED colocada em um índice exclusivo. Por outro lado, votei na sua solução.
Matt M
@ Matt M - Obrigado pela sua explicação, mas eu disse "... crie um novo valor exclusivo e armazene-o além dos dados atuais". Eu pretendia que a nova coluna de chave fosse um novo campo complementando os dados existentes e não substituindo-os. Concordo que o uso de um campo calculado persistente é melhor do que minha sugestão de uma UDF, mas, em espírito, minha solução foi a mesma.
Tony
Parece que eu li mal sua solução e peço desculpas por isso. Dito isto, combinar algumas das colunas não é uma solução tão boa, na minha opinião, quanto a solução HashBytes fornecida. Retirei meu -1. Mais uma vez, peço desculpas pela minha compreensão de leitura.
Matt M
0

Você pode usar um gatilho para insert/ update. Faça um agrupamento selecionado por suas colunas com uma cláusula de having count(*) > 1. Se isso voltar não vazio, reverta.

Ben Thul
fonte
0

Aqui está o que eu faria. Eu criaria um gatilho AFTER para INSERT, UPDATE que executa uma ROW_NUMBER ()função e particiona todas as 18 colunas exclusivas. Se o número máximo de linhas for maior que um, faça a ROLLBACK.

Thomas Stringer
fonte