Risposte:
Dipende da come si definisce la colonna calcolata. Una PERSISTED
colonna calcolata verrà calcolata e quindi memorizzata come dati all'interno della tabella. Se non si definisce la colonna come PERSISTED
, verrà calcolata quando viene eseguita la query.
Si prega di vedere la risposta di Aaron per una grande spiegazione e prova.
Pinal Dave lo descrive anche in dettaglio e mostra la prova di conservazione nella sua serie:
Questo è molto facile da provare da solo. È possibile creare una tabella con una colonna calcolata che utilizza una funzione scalare definita dall'utente, quindi controllare i piani e le statistiche delle funzioni prima e dopo sia un aggiornamento che una selezione e vedere quando viene registrata un'esecuzione.
Diciamo che abbiamo questa funzione:
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
E questa tabella:
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
Controlliamo sys.dm_exec_function_stats
(nuovo in SQL Server 2016 e nel database SQL di Azure) prima e dopo un inserimento, quindi dopo una selezione:
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();
Non vedo alcuna chiamata di funzione sull'inserto, solo sulla selezione.
Ora lascia cadere le tabelle e fallo di nuovo, questa volta cambiando la colonna in PERSISTED
:
DROP TABLE dbo.Floobs;
GO
DROP FUNCTION dbo.mask;
GO
...
MaskedName AS CONVERT(varchar(32), dbo.mask(Name)) PERSISTED,
...
E vedo accadere il contrario: ottengo un'esecuzione registrata sull'insert, ma non sulla selezione.
Non hai una versione abbastanza moderna di SQL Server da usare sys.dm_exec_function_stats
? Nessun problema , anche questo viene catturato nei piani di esecuzione .
Per la versione non persistente, possiamo vedere la funzione indicata solo nella selezione:
Mentre la versione persistente mostra solo il calcolo in corso su insert:
Ora, Martin sottolinea un ottimo punto in un commento : questo non sarà sempre vero. Creiamo un indice che non copre la colonna calcolata persistente ed eseguiamo una query che utilizza tale indice e vediamo se la ricerca ottiene i dati dai dati persistenti esistenti o calcola i dati in fase di esecuzione (funzione drop and re-create e tabella qui):
CREATE INDEX x ON dbo.Floobs(Name);
GO
INSERT dbo.Floobs(name)
SELECT LEFT(name, 32)
FROM sys.all_columns
WHERE LEN(name) >= 8;
Ora eseguiremo una query che utilizza l'indice (in realtà utilizza l'indice per impostazione predefinita in questo caso specifico, anche senza una clausola where):
SELECT * FROM dbo.Floobs WITH (INDEX(x))
WHERE Name LIKE 'S%';
Vedo ulteriori esecuzioni nelle statistiche delle funzioni e il piano non mente:
Quindi, la risposta è DIPENDE . In questo caso, SQL Server riteneva che sarebbe stato più economico ricalcolare i valori piuttosto che eseguire ricerche. Questo potrebbe cambiare a causa di una varietà di fattori, quindi non fare affidamento su di esso. E ciò può accadere in entrambe le direzioni indipendentemente dal fatto che venga utilizzata o meno una funzione definita dall'utente; L'ho usato solo qui perché lo ha reso molto più facile da illustrare.
La risposta a questa domanda è davvero "dipende". Ho appena incontrato un esempio in cui SQL Server sta utilizzando l'indice sulla colonna calcolata persistente ma sta ancora eseguendo la funzione, come se i valori non fossero mai stati persistiti all'inizio. Potrebbe avere a che fare con il tipo di dati della colonna ( nvarchar(37)
) o forse la dimensione della tabella (circa 7 milioni di righe), ma SQL Server ha deciso di ignorare la persisted
parola chiave, a quanto pare, in questa particolare istanza.
In questo caso, la chiave primaria nella tabella è TransactionID che è anche una colonna calcolata e persistente. Il piano di esecuzione sta generando una scansione dell'indice e in una tabella con solo 7 milioni di righe questa semplice query impiega fino a 2-3 minuti per l'esecuzione perché la funzione viene eseguita nuovamente su ogni riga e i valori non sembrano persistere in l'indice.