Índice de colunas computadas não usado

14

Eu quero ter uma pesquisa rápida com base em se duas colunas são iguais. Tentei usar uma coluna computada com um índice, mas o SQL Server não parece usá-lo. Se eu apenas usar uma coluna de bits preenchida estaticamente com um índice, recebo a busca de índice esperada.

Parece que existem outras perguntas como essa, mas nenhuma se concentrou no motivo pelo qual um índice não seria usado.

Tabela de teste:

CREATE TABLE dbo.Diffs
    (
    Id int NOT NULL IDENTITY (1, 1),
    DataA int NULL,
    DataB int NULL,
    DiffPersisted  AS isnull(convert(bit, case when [DataA] is null and [DataB] is not null then 1 when [DataA] <> [DataB] then 1 else 0 end), 0) PERSISTED ,
    DiffComp  AS isnull(convert(bit, case when [DataA] is null and [DataB] is not null then 1 when [DataA] <> [DataB] then 1 else 0 end), 0),
    DiffStatic bit not null,
    Primary Key (Id)
    )

create index ix_DiffPersisted on Diffs (DiffPersisted)
create index ix_DiffComp on Diffs (DiffComp)
create index ix_DiffStatic on Diffs (DiffStatic)

E a consulta:

select Id from Diffs where DiffPersisted = 1
select Id from Diffs where DiffComp = 1
select Id from Diffs where DiffStatic = 1

E os planos de execução resultantes: Plano de execução

David Faivre
fonte

Respostas:

10

Tente com em COALESCEvez de ISNULL. Com ISNULL, o SQL Server não parece capaz de enviar um predicado ao índice mais estreito e, portanto, precisa verificar o cluster para encontrar as informações.

CREATE TABLE dbo.Diffs
    (
    Id int NOT NULL IDENTITY (1, 1),
    DataA int NULL,
    DataB int NULL,
    DiffPersisted  AS COALESCE(convert(bit, case when [DataA] is null 
      and [DataB] is not null then 1 when [DataA] <> [DataB] 
      then 1 else 0 end), 0) PERSISTED ,
    DiffComp  AS COALESCE(convert(bit, case when [DataA] is null 
      and [DataB] is not null then 1 when [DataA] <> [DataB] 
      then 1 else 0 end), 0),
    DiffStatic bit not null,
    Primary Key (Id)
    );

Dito isto, se você ficar com uma coluna estática, um índice filtrado poderá fazer mais sentido e terá custos de E / S mais baixos (tudo dependendo de quantas linhas normalmente correspondem ao predicado do filtro), por exemplo:

CREATE INDEX ix_DiffStaticFiltered 
  ON dbo.Diffs(DiffStatic)
  WHERE DiffStatic = 1;
Aaron Bertrand
fonte
Muito interessante, não teria pensado nisso. Parece que você pode se livrar do COALESCEneste momento; Acredito que a CASEinstrução já estava garantida para retornar 0ou 1, mas ISNULLestava presente apenas para que o SQL Server produzisse um valor nulo BITpara a coluna computada. No entanto, COALESCEainda produzirá uma coluna anulável. Portanto, o único impacto dessa alteração, com ou sem o COALESCE, é que a coluna computada agora é anulável, mas a busca do índice pode ser usada.
Geoff Patterson
@ Geoff Sim, isso é verdade. Mas neste caso, como sabemos pela definição da coluna computada NULL realmente não é uma saída possível, isso realmente importa se estamos usando esta tabela como a fonte de um SELECT INTO.
Aaron Bertrand
Esta é uma informação incrível - obrigado! Meu objetivo final é que as colunas DataA e DataB sejam usadas como uuids "sujos" para permitir a atualização assíncrona de colunas desnormalizadas no registro, portanto não deve haver muitas onde o sinalizador Diff é 1. Se eu usar o static campo, então eu estava pensando em adicionar um gatilho para monitorar os dois uuids e atualizar o campo.
David Faivre
Além disso, como apontou @GeoffPatterson, não posso usar o COALESCE? Por que eu guardaria isso?
David Faivre
@ David Você provavelmente pode soltar o COALESCE. Tentei manter a aparência e a intenção do seu código original e não testei sem ele, para que o teste seja feito em você. (Também não sei explicar por que você o tinha ISNULLlá.)
Aaron Bertrand
5

Essa é uma limitação específica da lógica de correspondência de coluna computada do SQL Server, quando uma mais externa ISNULLé usada e o tipo de dados da coluna é bit.

Relatório de erro

Para evitar o problema, qualquer uma das seguintes soluções alternativas pode ser empregada:

  1. Não use uma extremidade ISNULL(a única maneira de criar uma coluna computada NOT NULL).
  2. Não use o bittipo de dados como o tipo final da coluna computada.
  3. Crie a coluna computada PERSISTEDe ative o sinalizador de rastreamento 174 .

Detalhes

O cerne da questão é que, sem o sinalizador de rastreamento 174, todas as referências de coluna computadas em uma consulta (mesmo persistentes) são sempre expandidas para a definição subjacente muito cedo na compilação de consultas.

A idéia de expansão é que ela poderia permitir simplificações e reescritas que só podem funcionar na definição, não apenas no nome da coluna. Por exemplo, pode haver predicados na consulta que faz referência a essa coluna computada que pode tornar parte do cálculo redundante ou de outra forma mais restrita.

Uma vez que as simplificações e reescritas iniciais são consideradas, a compilação de consultas tenta corresponder expressões na consulta a colunas computadas (todas as colunas computadas, não apenas aquelas originalmente encontradas no texto da consulta).

As expressões da coluna computada inalterada correspondem à coluna computada original sem problemas na maioria dos casos. Parece haver um erro específico para combinar uma expressão do bittipo com uma mais externa ISNULL. A correspondência não é bem-sucedida nesse caso específico, mesmo quando um exame detalhado das partes internas mostra que ela deve ser bem-sucedida.

Paul White 9
fonte