回答:
計算列の定義方法によって異なります。PERSISTED
計算列を計算し、テーブル内のデータとして格納されます。列をとして定義しないPERSISTED
場合、クエリの実行時に計算されます。
優れた説明と証拠については、アーロンの回答をご覧ください。
Pinal Daveはこれについても詳しく説明し、彼のシリーズでストレージの証拠を示しています。
これは自分で証明するのは非常に簡単です。スカラーのユーザー定義関数を使用する計算列を持つテーブルを作成し、更新と選択の両方の前後に計画と関数の統計を確認し、実行が記録されるタイミングを確認できます。
この関数があるとしましょう:
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
そしてこの表:
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
sys.dm_exec_function_stats
挿入の前後、および選択後に(SQL Server 2016およびAzure SQL Databaseの新機能)を確認しましょう。
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();
挿入では関数呼び出しは表示されず、選択でのみ表示されます。
ここで、テーブルをドロップして再度実行します。今回は、列をPERSISTED
次のように変更します。
DROP TABLE dbo.Floobs;
GO
DROP FUNCTION dbo.mask;
GO
...
MaskedName AS CONVERT(varchar(32), dbo.mask(Name)) PERSISTED,
...
そして、私は反対の出来事を見ます:挿入では記録されますが、選択では記録されません。
使用するのに十分な最新バージョンのSQL Serverがありsys.dm_exec_function_stats
ませんか?心配する必要はありません。これも実行計画に含まれています。
非永続バージョンの場合、selectでのみ参照される関数を確認できます。
永続化されたバージョンでは、挿入時に発生する計算のみが表示されます。
さて、マーティンはコメントで素晴らしい点を持ち出しました。これは常に真実であるとは限りません。永続化された計算列をカバーしないインデックスを作成し、そのインデックスを使用するクエリを実行し、ルックアップが既存の永続化データからデータを取得するか、実行時にデータを計算するかを確認します(関数を削除して再作成します)ここに表):
CREATE INDEX x ON dbo.Floobs(Name);
GO
INSERT dbo.Floobs(name)
SELECT LEFT(name, 32)
FROM sys.all_columns
WHERE LEN(name) >= 8;
次に、インデックスを使用するクエリを実行します(実際、この特定のケースでは、where句がなくてもデフォルトでインデックスを使用します)。
SELECT * FROM dbo.Floobs WITH (INDEX(x))
WHERE Name LIKE 'S%';
関数の統計に追加の実行が表示されますが、計画は嘘をつきません。
したがって、答えはIT DEPENDSです。この場合、SQL Serverは、ルックアップを実行するよりも値を再計算する方が安価だと考えました。これはさまざまな要因により変化する可能性があるため、それに依存しないでください。これは、ユーザー定義関数が使用されているかどうかに関係なく、どちらの方向でも発生する可能性があります。ここで使用したのは、説明がずっと簡単になったからです。
この質問への答えは本当に「それは依存する」です。SQL Serverが永続化された計算列のインデックスを使用しているにもかかわらず、値が最初から永続化されていないかのように機能を実行している例を見つけました。列のデータ型(nvarchar(37)
)またはテーブルのサイズ(約700万行)に関係している可能性がありますが、SQL Server persisted
はこの特定のインスタンスでキーワードを無視することにしました。
この場合、テーブルの主キーはTransactionIDであり、これも計算列および永続化列です。実行計画はインデックススキャンを生成しており、700万行しかないテーブルでは、この単純なクエリの実行には2〜3分かかります。これは、関数がすべての行で再度実行され、値が保持されないためですインデックス。