¿Hay alguna solución para cuando quieres poner un OR dentro de un índice filtrado?

8

¿Hay alguna solución para cuando quieres poner un OR dentro de un índice filtrado?

create index FIDX_tblbOrders_sdtmOrdCreated_INCL 
on dbo.tblBOrder(sdtmOrdCreated)
INCLUDE (sintMarketID,
         strCurrencyCode,
         sintOrderStatusID
         )
WHERE ((sintMarketId=1)
AND ( (sintOrderStatusId < 9) OR (sintOrderStatusId > 14)))

Estoy tratando de crear el índice anterior, porque NO estoy interesado en ninguna situación en la que sintOrderStatusId IN (9-14)

Por supuesto, puedo crear una vista o vista indizada, pero estaba tratando de evitarlo.

simplemente agregando más información: sintOrderStatusId es un smallint NOT NULL y los valores posibles oscilan entre 1 y 30. los 9 a 14 deben evitarse, por lo tanto, el índice filtrado.

Marcello Miorelli
fuente

Respuestas:

12

Desafortunadamente, parece que no hay forma de crear un filtro negativo para un índice, sin recurrir a la creación de una vista materializada. Si fuera posible crear un filtro negativo como el que le gustaría, sería muy difícil para el optimizador de consultas "elegir" el índice para su uso, aumentando drásticamente el tiempo requerido para encontrar un buen plan.

Dependiendo de los patrones de consulta para esta tabla, simplemente podría crear dos índices; uno por menos de 9 y uno para mayor que 14. Cualquiera de estos índices puede ser elegido por el optimizador de consultas para simples WHEREcláusulas tales comoWHERE StatusID = 6

CREATE TABLE dbo.TestNegativeFilter
(
    TestNegativeFilter INT NOT NULL
        CONSTRAINT PK_TestNegativeFilter
        PRIMARY KEY CLUSTERED
        IDENTITY(1,1)
    , StatusID INT NOT NULL
);
GO

CREATE INDEX IX_TestNagativeFilter_LessThan9
ON dbo.TestNegativeFilter(StatusID)
WHERE (StatusID < 9);

CREATE INDEX IX_TestNagativeFilter_GreaterThan14
ON dbo.TestNegativeFilter(StatusID)
WHERE (StatusID > 14);

Otra forma de lograr esto podría ser:

CREATE INDEX IX_TestNegativeFilter_9_to_14
ON dbo.TestNegativeFilter(StatusID)
WHERE (StatusID IN (9, 10, 11, 12, 13, 14));

SELECT *
FROM dbo.TestNegativeFilter tnf
EXCEPT
SELECT *
FROM dbo.TestNegativeFilter tnf
WHERE tnf.StatusID IN (9, 10, 11, 12, 13, 14);

Esto usa el índice filtrado del 9 al 14 para excluir filas.

En mi plataforma de prueba, un índice de cobertura simple devuelve filas, con mucho, la más rápida:

CREATE NONCLUSTERED INDEX IX_TestNegativeFilter_StatusID
ON dbo.TestNegativeFilter(StatusID)
INCLUDE (TestNegativeFilter);

SELECT *
FROM dbo.TestNegativeFilter tnf
WHERE tnf.StatusID NOT IN (9, 10, 11, 12, 13, 14);

Alternativamente, usando una variación en el enfoque utilizado en su propia respuesta :

CREATE INDEX [IX dbo.TestNegativeFilter StatusID not 9-14]
ON dbo.TestNegativeFilter (StatusID)
WHERE StatusID <> 9
AND StatusID <> 10
AND StatusID <> 11
AND StatusID <> 12
AND StatusID <> 13
AND StatusID <> 14;

A pesar de que el filtro se escribe como conjunciones, admite consultas escritas de cualquiera de las siguientes maneras (la primera es un poco más eficiente):

  • StatusID NOT IN (9, 10, 11, 12, 13, 14)
  • StatusID < 9 OR StatusID > 14
  • StatusID NOT BETWEEN 9 AND 14
Max Vernon
fuente
1

no es genial, pero parece estar funcionando:

create index FIDX_tblbOrders_sdtmOrdCreated_INCL 
on dbo.tblBOrder(sdtmOrdCreated)
INCLUDE (sintMarketID,
         strCurrencyCode,
         sintOrderStatusID
         )
WHERE ((sintMarketId=1)
AND ( sintOrderStatusId IN (0,1,2,3,4,5,6,7,8,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30)))

Cuando trato de hacerlo de una mejor manera, no me gusta:

ingrese la descripción de la imagen aquí

Marcello Miorelli
fuente
1
Según la documentación de MSND: "Los índices filtrados se definen en una tabla y solo admiten operadores de comparación simples. Si necesita una expresión de filtro que haga referencia a varias tablas o tenga una lógica compleja, debe crear una vista". msdn.microsoft.com/en-us/library/cc280372.aspx
Paweł Tajs