Índice de columna calculado no utilizado


14

Quiero tener una búsqueda rápida basada en si dos columnas son iguales. Intenté usar una columna calculada con un índice, pero SQL Server no parece usarla. Si solo uso una columna de bits rellenada estáticamente con un índice, obtengo la búsqueda de índice esperada.

Parece que hay algunas otras preguntas como esta, pero ninguna se centró en por qué no se usaría un índice.

Tabla de prueba:

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)

Y la consulta:

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

Y los planes de ejecución resultantes: Plan de ejecución

Respuestas:


10

Probar con en COALESCElugar deISNULL . ConISNULL , SQL Server no parece capaz de empujar un predicado contra el índice más estrecho y, por lo tanto, tiene que escanear el clúster para encontrar la información.

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

Dicho esto, si se queda con una columna estática, un índice filtrado puede tener más sentido y tendrá costos de E / S más bajos (todo depende de cuántas filas coincidan típicamente con el predicado de filtro), por ejemplo:

CREATE INDEX ix_DiffStaticFiltered 
  ON dbo.Diffs(DiffStatic)
  WHERE DiffStatic = 1;

Muy interesante, no habría pensado en esto. Sin embargo, parece que puedes deshacerte de él COALESCEen este punto; Creo que la CASEdeclaración ya estaba garantizada para regresar 0o 1, pero ISNULLestaba presente solo para que SQL Server produjera un no anulable BITpara la columna calculada. Sin embargo, COALESCEseguirá produciendo una columna anulable. Entonces, el único impacto de este cambio, con o sin el COALESCE, es que la columna calculada ahora es anulable pero se puede utilizar la búsqueda de índice.
Geoff Patterson

@ Geoff Sí, eso es cierto. Pero en este caso, ya que sabemos por la definición de columna calculada NULL no es realmente una posible salida, esto sólo realmente importa si estamos utilizando esta tabla como la fuente de un SELECT INTO.
Aaron Bertrand

Esta es una información sorprendente, ¡gracias! Mi objetivo final es que las columnas DataA y DataB se usen como uuids "sucios" para permitir la actualización asincrónica de columnas desnormalizadas en el registro, por lo que no debería haber demasiadas donde el indicador Diff es 1. Si uso el static campo, entonces estaba pensando en agregar un disparador para monitorear los dos uuids y actualizar el campo.
David Faivre

Además, como señaló @GeoffPatterson, ¿no puedo usar el COALESCE? ¿Por qué me lo quedaría?
David Faivre

@David Probablemente puedas soltar el COALESCE. Traté de mantener el aspecto y la intención de su código original, y no probé sin él, por lo que las pruebas serán suyas. (Tampoco puedo explicar por qué tenías ISNULLallí en primer lugar.)
Aaron Bertrand

5

Esta es una limitación específica de la lógica de coincidencia de columnas calculada de SQL Server, cuando ISNULLse utiliza un elemento externo , y el tipo de datos de la columna es bit.

Informe de error

Para evitar el problema, se puede emplear cualquiera de las siguientes soluciones:

  1. No utilice un elemento externo ISNULL(la única forma de hacer una columna calculada NOT NULL).
  2. No use el bit tipo de datos como el tipo final de la columna calculada.
  3. Haga la columna calculada PERSISTEDy active el indicador de traza 174 .

Detalles

El meollo del problema es que sin el indicador de traza 174, todas las referencias de columna calculadas en una consulta (incluso persistentes) siempre se expanden a la definición subyacente muy temprano en la compilación de consultas.

La idea de expansión es que podría permitir simplificaciones y reescrituras que solo pueden funcionar en la definición, no solo en el nombre de la columna. Por ejemplo, puede haber predicados en la consulta que hagan referencia a esa columna calculada que podrían hacer que parte del cálculo sea redundante o de otra manera más restringida.

Una vez que se consideran las primeras simplificaciones y reescrituras, la compilación de consultas intenta hacer coincidir las expresiones en la consulta con las columnas calculadas (todas las columnas calculadas, no solo las que se encontraron originalmente en el texto de la consulta).

Las expresiones de columna calculada sin cambios coinciden con la columna calculada original sin problemas en la mayoría de los casos. Parece haber un error cuando es específico para hacer coincidir una expresión de bittipo, con una más externa ISNULL. La coincidencia no tiene éxito en este caso específico, incluso cuando un examen detallado de las partes internas muestra que debería tener éxito.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.