¿Cuándo se calculan las columnas calculadas?

29

¿Cuándo se determinan los valores para las columnas calculadas?

  • Cuando se recupera el valor?
  • Cuando se cambia el valor?
  • ¿En otro momento?

Supongo que esta es una pregunta novata ya que no encuentro nada en mis búsquedas.

Shelby115
fuente

Respuestas:

19

Depende de cómo defina la columna calculada. Se PERSISTEDcalculará una columna calculada y luego se almacenará como datos dentro de la tabla. Si no define la columna como PERSISTED, se calculará cuando se ejecute su consulta.

Por favor vea la respuesta de Aaron para una gran explicación y prueba.

Pinal Dave también describe esto en detalle y muestra una prueba de almacenamiento en su serie:

SERVIDOR SQL - Columna calculada - PERSISTADO y Almacenamiento

Arthur D
fuente
66
¿Qué pasa si persisten pero el plan de consulta utiliza un índice que no cubre esa columna? No estoy seguro de si obtendría una búsqueda o si solo lo calcularía sobre la marcha y actualmente no puede probarlo.
Martin Smith
1
@ Martin tiene razón, en mi prueba, SQL Server eligió volver a calcular en lugar de una búsqueda.
Aaron Bertrand
34

Esto es muy fácil de probar por su cuenta. Podemos crear una tabla con una columna calculada que use una función escalar definida por el usuario, y luego verificar los planes y las estadísticas de la función antes y después de una actualización y selección, y ver cuándo se registra una ejecución.

Digamos que tenemos esta función:

CREATE FUNCTION dbo.mask(@x varchar(32))
RETURNS varchar(32) WITH SCHEMABINDING
AS
BEGIN
  RETURN (SELECT 'XX' + SUBSTRING(@x, 3, LEN(@x)-4) + 'XXXX');
END
GO

Y esta tabla:

CREATE TABLE dbo.Floobs
(
  FloobID int IDENTITY(1,1),
  Name varchar(32),
  MaskedName AS CONVERT(varchar(32), dbo.mask(Name)),
  CONSTRAINT pk_Floobs PRIMARY KEY(FloobID),
  CONSTRAINT ck_Name CHECK (LEN(Name)>=8)
);
GO

Verifiquemos sys.dm_exec_function_stats(nuevo en SQL Server 2016 y Azure SQL Database) antes y después de una inserción, y luego después de una selección:

SELECT o.name, s.execution_count
FROM sys.dm_exec_function_stats AS s
INNER JOIN sys.objects AS o
ON o.[object_id] = s.[object_id]
WHERE s.database_id = DB_ID();

INSERT dbo.Floobs(Name) VALUES('FrankieC');

SELECT o.name, s.execution_count
FROM sys.dm_exec_function_stats AS s
INNER JOIN sys.objects AS o
ON o.[object_id] = s.[object_id]
WHERE s.database_id = DB_ID();

SELECT * FROM dbo.Floobs;

SELECT o.name, s.execution_count
FROM sys.dm_exec_function_stats AS s
INNER JOIN sys.objects AS o
ON o.[object_id] = s.[object_id]
WHERE s.database_id = DB_ID();

No veo ninguna llamada a la función en la inserción, solo en la selección.

Ahora, suelte las tablas y vuelva a hacerlo, esta vez cambiando la columna a PERSISTED:

DROP TABLE dbo.Floobs;
GO
DROP FUNCTION dbo.mask;
GO

...
  MaskedName AS CONVERT(varchar(32), dbo.mask(Name)) PERSISTED,
...

Y veo que sucede lo contrario: obtengo una ejecución registrada en la inserción, pero no en la selección.

¿No tiene una versión suficientemente moderna de SQL Server para usar sys.dm_exec_function_stats? No se preocupe, esto también está capturado en los planes de ejecución .

Para la versión no persistente, podemos ver la función referenciada solo en select:

ingrese la descripción de la imagen aquí

ingrese la descripción de la imagen aquí

Si bien la versión persistente solo muestra el cálculo que ocurre en la inserción:

ingrese la descripción de la imagen aquí

ingrese la descripción de la imagen aquí

Ahora, Martin menciona un gran punto en un comentario : esto no siempre será cierto. Creemos un índice que no cubra la columna calculada persistente, y ejecutemos una consulta que use ese índice, y veamos si la búsqueda obtiene los datos de los datos persistentes existentes, o calcula los datos en tiempo de ejecución (función drop and re-create) y tabla aquí):

CREATE INDEX x ON dbo.Floobs(Name);
GO

INSERT dbo.Floobs(name) 
  SELECT LEFT(name, 32) 
  FROM sys.all_columns 
  WHERE LEN(name) >= 8;

Ahora, ejecutaremos una consulta que use el índice (de todos modos, en realidad, usa el índice en este caso específico, incluso sin una cláusula where):

SELECT * FROM dbo.Floobs WITH (INDEX(x))
  WHERE Name LIKE 'S%';

Veo ejecuciones adicionales en las estadísticas de funciones, y el plan no miente:

ingrese la descripción de la imagen aquí

Entonces, la respuesta es DEPENDE . En este caso, SQL Server pensó que sería más barato volver a calcular los valores que realizar búsquedas. Esto podría cambiar debido a una variedad de factores, así que no confíes en él. Y esto puede suceder en cualquier dirección, se use o no una función definida por el usuario; Solo lo usé aquí porque lo hizo mucho más fácil de ilustrar.

Aaron Bertrand
fuente
Muy apreciado, nunca cuestioné el comportamiento del motor en los resultados informáticos.
Arthur D
8
@ArthurD Es una decisión optimizadora basada (principalmente) en los costos estimados de cada alternativa, vea mi respuesta a otra pregunta aquí.
Paul White dice GoFundMonica
-1

La respuesta a esta pregunta realmente es "depende". Acabo de encontrar un ejemplo en el que SQL Server está utilizando el índice en la columna calculada persistente, pero aún realiza la función, como si los valores nunca hubieran sido persistentes. Es posible que tenga que ver con el tipo de datos de la columna ( nvarchar(37)) o posiblemente con el tamaño de la tabla (aproximadamente 7 millones de filas), pero SQL Server decidió ignorar elpersisted palabra clave, al parecer, en este caso particular.

En este caso, la clave principal de la tabla es TransactionID, que también es una columna calculada y persistente. El plan de ejecución está generando un escaneo de índice y en una tabla con solo 7 millones de filas, esta consulta simple tarda más de 2-3 minutos en ejecutarse porque la función se ejecuta nuevamente en cada fila y los valores no parecen persistir en El índice.

crear tabla con columna persistente se está ejecutando el plan de ejecución que muestra la función

Brony corpulento
fuente