No se puede crear un índice filtrado en una columna calculada


18

En una pregunta anterior mía, ¿es una buena idea deshabilitar la escalada de bloqueo al agregar nuevas columnas calculadas a una tabla? , Estoy creando una columna calculada:

ALTER TABLE dbo.tblBGiftVoucherItem
ADD isUsGift AS CAST
(
    ISNULL(
        CASE WHEN sintMarketID = 2 
            AND strType = 'CARD'
            AND strTier1 LIKE 'GG%' 
        THEN 1 
        ELSE 0 
        END
    , 0) 
    AS BIT
) PERSISTED;

La columna calculada es PERSISTED, y de acuerdo con computed_column_definition (Transact-SQL) :

Persistió

Especifica que el Motor de base de datos almacenará físicamente los valores calculados en la tabla y actualizará los valores cuando se actualicen cualesquiera otras columnas de las que depende la columna calculada. Marcar una columna calculada como PERSISTED permite crear un índice en una columna calculada que es determinista, pero no precisa. Para obtener más información, vea Índices en columnas calculadas. Cualquier columna calculada utilizada como columna de partición de una tabla particionada debe marcarse explícitamente como PERSISTED. computed_column_expression debe ser determinista cuando se especifica PERSISTED.

Pero cuando intento crear un índice en mi columna me sale el siguiente error:

CREATE INDEX FIX_tblBGiftVoucherItem_incl
ON dbo.tblBGiftVoucherItem (strItemNo) 
INCLUDE (strTier3)
WHERE isUsGift = 1;

El índice filtrado 'FIX_tblBGiftVoucherItem_incl' no se puede crear en la tabla 'dbo.tblBGiftVoucherItem' porque la columna 'isUsGift' en la expresión del filtro es una columna calculada. Vuelva a escribir la expresión del filtro para que no incluya esta columna.

¿Cómo puedo crear un índice filtrado en una columna calculada?

o

¿Hay una solución alternativa?


3
Sin embargo, podría crear un índice filtrado WHERE (sintMarketID = 2 AND strType = 'CARD' AND strTier1 LIKE 'GG%').
ypercubeᵀᴹ

Respuestas:


21

Desafortunadamente, a partir de SQL Server 2014, no existe la posibilidad de crear un lugar Filtered Indexdonde el filtro está en una columna calculada (independientemente de si es persistente o no).

Ha habido un elemento de conexión abierto desde 2009, así que adelante y vote por él. Quizás Microsoft arregle esto algún día.

Aaron Bertrand tiene un artículo que cubre varios otros problemas con los índices filtrados .


21

Aunque no puede crear un índice filtrado en una columna persistente, existe una solución bastante simple que puede usar.

Como prueba, he creado una tabla simple con una IDENTITYcolumna y una columna calculada persistente basada en la columna de identidad:

USE tempdb;

CREATE TABLE dbo.PersistedViewTest
(
    PersistedViewTest_ID INT NOT NULL
        CONSTRAINT PK_PersistedViewTest
        PRIMARY KEY CLUSTERED
        IDENTITY(1,1)
    , SomeData VARCHAR(2000) NOT NULL
    , TestComputedColumn AS (PersistedViewTest_ID - 1) PERSISTED
);
GO

Luego, creé una vista vinculada al esquema basada en la tabla con un filtro en la columna calculada:

CREATE VIEW dbo.PersistedViewTest_View
WITH SCHEMABINDING
AS
SELECT PersistedViewTest_ID
    , SomeData 
    , TestComputedColumn
FROM dbo.PersistedViewTest
WHERE TestComputedColumn < CONVERT(INT, 27);

A continuación, creé un índice agrupado en la vista vinculada al esquema, que tiene el efecto de persistir los valores almacenados en la vista, incluido el valor de la columna calculada:

CREATE UNIQUE CLUSTERED INDEX IX_PersistedViewTest
ON dbo.PersistedViewTest_View(PersistedViewTest_ID);
GO

Inserte algunos datos de prueba en la tabla:

INSERT INTO dbo.PersistedViewTest (SomeData)
SELECT o.name + o1.name + o2.name
FROM sys.objects o
    CROSS JOIN sys.objects o1
    CROSS JOIN sys.objects o2;

Cree un elemento de estadísticas y un índice en la vista:

CREATE STATISTICS ST_PersistedViewTest_View
ON dbo.PersistedViewTest_View(TestComputedColumn)
WITH FULLSCAN;

CREATE INDEX IX_PersistedViewTest_View_TestComputedColumn
ON dbo.PersistedViewTest_View(TestComputedColumn);

Realizar SELECTdeclaraciones en la tabla con la columna persistente ahora puede usar automáticamente la vista persistente, si el optimizador de consultas determina que tiene sentido hacerlo:

SELECT pv.PersistedViewTest_ID
    , pv.TestComputedColumn
FROM dbo.PersistedViewTest pv
WHERE pv.TestComputedColumn = CONVERT(INT, 26)

El plan de ejecución real para la consulta anterior muestra que el optimizador de consultas eligió usar la vista persistente para devolver los resultados:

ingrese la descripción de la imagen aquí

Es posible que haya notado la conversión explícita en la WHEREcláusula anterior. Este explícito CONVERT(INT, 26)permite que el optimizador de consultas use correctamente el objeto de estadísticas para estimar el número de filas que devolverá la consulta. Si escribimos la consulta conWHERE pv.TestComputedColumn = 26 , el optimizador de consultas puede no estimar adecuadamente el número de filas ya que 26 se considera realmente a TINY INT; esto puede hacer que SQL Server no use la vista persistente. Las conversiones implícitas pueden ser muy dolorosas, y vale la pena usar constantemente los tipos de datos correctos para comparaciones y uniones.

Por supuesto, todas las "trampas" estándar que resultan del uso del enlace de esquema se aplican al escenario anterior; Esto puede evitar el uso de esta solución en todos los escenarios. Por ejemplo, ya no será posible modificar la tabla base sin eliminar primero el enlace del esquema de la vista. Para hacerlo, deberá eliminar el índice agrupado de la vista.

Si no tiene SQL Server Enterprise Edition, el optimizador de consultas no usará automáticamente la vista persistente para consultas que no hacen referencia directa a la vista usando la WITH (NOEXPAND)sugerencia. Para obtener el beneficio de usar la vista persistente en versiones que no sean Enterprise Edition, deberá volver a escribir la consulta anterior en algo como:

SELECT pv.PersistedViewTest_ID
    , pv.TestComputedColumn
FROM dbo.PersistedViewTest_View pv WITH (NOEXPAND)
WHERE pv.TestComputedColumn = CONVERT(INT, 26)

Gracias a Ian Ringrose por señalar la limitación de Enterprise Edition anterior, y a Paul White por el(NOEXPAND) pista.

Esta respuesta de Paul tiene algunos detalles interesantes sobre el optimizador de consultas en relación con las vistas persistentes.


El trabajo alternativo muestra que tanto un índice agrupado como un índice no agrupado se crean en la vista. ¿El índice no agrupado tiene que usarse sobre el índice agrupado por alguna razón? ¿O es el índice no agrupado más eficaz? Si se utilizara el índice agrupado en la consulta, ¿qué mostrarían las estadísticas?
Bob Bryan

Pregunta interesante, @BobBryan: el índice agrupado es necesario para permitir que la vista sea persistente, aunque en realidad no necesita ser un índice único. Podría haber creado el índice agrupado de la vista en alguna otra columna, como el TestComputedColumnlugar. Sin embargo, dado que el índice agrupado contiene todos los datos para la tabla / vista, decidí que sería mejor usar un número monotónicamente creciente como clave de agrupamiento. Tenga en cuenta que en realidad no probé esa suposición, y de hecho puede ser incorrecta para algunas variaciones de la reproducción.
Max Vernon el

Tenga en cuenta que el índice no agrupado no es un índice de cobertura y, como tal, cualquier consulta que filtre, combine o devuelva columnas desde la vista o la tabla subyacente deberá realizar una operación de búsqueda de claves en la tabla base o la vista. Es probable que para un escenario del mundo real, el alcance limitado de mi respuesta podría exponerse con un rendimiento aún mejor en mente.
Max Vernon el

4

De Create Indexy su wherecláusula, esto no es posible:

DÓNDE

Crea un índice filtrado especificando qué filas incluir en el índice. El índice filtrado debe ser un índice no agrupado en una tabla. Crea estadísticas filtradas para las filas de datos en el índice filtrado.

El predicado de filtro utiliza una lógica de comparación simple y no puede hacer referencia a una columna calculada, una columna UDT, una columna de tipo de datos espaciales o una columna de tipo de datos de jerarquía ID. Las comparaciones que usan literales NULL no están permitidas con los operadores de comparación. Utilice los operadores IS NULL y IS NOT NULL en su lugar.

Fuente: MSDN


3
  • Necesita una columna que no esté calculada para colocar el índice filtrado.
  • Necesita calcular el valor para ir en esa columna.

Antes de calcular las columnas, utilizamos disparadores para calcular el valor de las columnas cada vez que se cambiaba o insertaba la fila.

(Un disparador también podría usarse para insertar / eliminar el PK del elemento de una segunda tabla que luego se usó en consultas).


3

Este es un intento de mejorar el trabajo de Max Vernon . En su solución, sugiere usar 2 índices en la vista y un objeto de estadísticas.

El primer índice está agrupado, lo que en realidad es necesario ya que, a diferencia de un índice no agrupado en una tabla, se generará un error si se intenta la creación de un índice no agrupado en la vista sin tener primero un índice agrupado.

El segundo índice es un índice no agrupado, que se utiliza como índice detrás de la consulta. En la sección de comentarios de su respuesta, le pregunté qué pasaría si se usara un índice agrupado en lugar de un índice no agrupado.

El siguiente análisis intenta responder a esta pregunta.

Estoy usando exactamente el mismo código, excepto que no estoy creando un índice no agrupado en la vista.

Tampoco estoy creando un objeto de estadísticas. Si sigue y utiliza SQL Server Management Studio (SSMS) para ingresar el código a continuación, debe tener en cuenta que puede ver algunas líneas onduladas rojas, que parecen errores. Estos (probablemente) no son errores, pero involucran un problema con intellisense.

Puede desactivar intellisense o simplemente ignorar los errores y ejecutar los comandos. Deben completarse sin errores.

-- Create the test table that uses a computed column.
USE tempdb;
CREATE TABLE dbo.PersistedViewTest
(
    PersistedViewTest_ID INT NOT NULL
    CONSTRAINT PK_PersistedViewTest
    PRIMARY KEY CLUSTERED
    IDENTITY(1,1)
    , SomeData VARCHAR(2000) NOT NULL
    , TestComputedColumn AS (PersistedViewTest_ID - 1) PERSISTED
);
GO

-- Insert some test data into the table.
INSERT INTO dbo.PersistedViewTest (SomeData)
SELECT o.name + o1.name + o2.name
FROM sys.objects o
    CROSS JOIN sys.objects o1
    CROSS JOIN sys.objects o2;
GO

El siguiente plan de ejecución (sin vista de vista / índice) se produce después de ejecutar la siguiente consulta en la tabla:

SELECT pv.PersistedViewTest_ID, pv.TestComputedColumn
FROM dbo.PersistedViewTest pv
WHERE pv.TestComputedColumn = CONVERT(INT, 26)
GO

ingrese la descripción de la imagen aquí

Esto proporciona una línea de base para comparar. Observe que después de completar la consulta, se creó un objeto de estadísticas (_WA_Sys_00000003_1FCDBCEB). El objeto de estadísticas PK_PersistedViewTest se creó cuando se creó el índice de tabla en clúster.

A continuación, se crean la vista filtrada y el índice agrupado en esa vista:

-- Create filtered view on the computed column.
CREATE VIEW dbo.PersistedViewTest_View
WITH SCHEMABINDING
AS
SELECT PersistedViewTest_ID, SomeData, TestComputedColumn
FROM dbo.PersistedViewTest
WHERE TestComputedColumn < CONVERT(INT, 27);
GO

-- Create unique clustered index to persist the values, including the computed column.
CREATE UNIQUE CLUSTERED INDEX IX_PersistedViewTest
ON dbo.PersistedViewTest_View(PersistedViewTest_ID);
GO

Ahora, intentemos ejecutar la consulta nuevamente, pero esta vez en la vista:

SELECT pv.PersistedViewTest_ID, pv.TestComputedColumn
FROM dbo.PersistedViewTest_View pv
WHERE pv.TestComputedColumn = CONVERT(INT, 26)
GO

El nuevo plan de ejecución es ahora:

ingrese la descripción de la imagen aquí

Si se cree en el nuevo plan, después de agregar la vista y el índice agrupado en esa vista, las estadísticas parecen indicar que el tiempo requerido para ejecutar la consulta ahora se ha duplicado. Además, observe que no se creó un nuevo objeto de estadísticas para admitir el nuevo índice después de que se ejecutó la consulta, que es diferente de la consulta en la tabla.

El plan de consulta aún sugiere que la creación de un índice no agrupado sería bastante útil para mejorar el rendimiento de la consulta. Entonces, ¿eso significa que se debe agregar un índice no agrupado a la vista antes de que se pueda obtener la mejora de rendimiento deseada? Hay una última cosa para probar. Modifique la consulta para usar la opción "WITH NOEXPAND":

SELECT pv.PersistedViewTest_ID, pv.TestComputedColumn
FROM dbo.PersistedViewTest_View pv WITH (NOEXPAND)
WHERE pv.TestComputedColumn = CONVERT(INT, 26)
GO

Esto da como resultado el siguiente plan de consulta:

ingrese la descripción de la imagen aquí

Este plan de ejecución se parece bastante al que se produjo con el índice no agrupado que figura en la respuesta de Max Vernon. Pero, este se hace con un índice menos (no agrupado) y un objeto de estadísticas menos.

Resulta que la opción NOEXPAND debe usarse con las versiones express y estándar de SQL Server para hacer un uso adecuado de una vista indizada. Paul White tiene un excelente artículo que expone los beneficios de usar la opción NOEXPAND. También recomienda que esta opción se use con la edición empresarial para garantizar que el optimizador utilice la garantía de unicidad proporcionada por los índices de vista.

El análisis anterior se realizó con la edición express de SQL Sever 2014. También lo probé con la edición de desarrollador de SQL Server 2016. La opción NOEXPAND no parece ser necesaria con la edición de desarrollo para lograr las mejoras de rendimiento, pero todavía se recomienda .

Hace menos de 5 meses, Microsoft hizo las ediciones para desarrolladores gratuitas . La licencia restringe el uso solo al desarrollo, lo que significa que la base de datos no se puede usar en un entorno de producción. Entonces, si ha estado buscando probar tablas optimizadas de memoria, cifrado, R, etc., entonces ya no tiene la excusa sin licencia. Lo instalé con éxito en mi computadora hace unos días junto con SQL Server 2014 Express sin problemas.

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.