計算列の値はいつ決定されますか?
検索で何も見つからないので、これは初心者の質問だと思います。
計算列の定義方法によって異なります。 PERSISTED
計算列が計算され、データとしてテーブル内に格納されます。列をPERSISTED
として定義しない場合、クエリの実行時に計算されます。
すばらしい説明と証明については、 Aaronの回答 を参照してください。
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,
...
そして、私は反対のことが起こっているのを見ます:私は実行が挿入で記録されますが、選択では記録されません。
sys.dm_exec_function_stats
を使用するのに十分な最新バージョンのSQL Serverがありませんか?心配ありません、これも実行プランに取り込まれます。
非永続バージョンの場合、selectでのみ参照される関数を確認できます。
永続化されたバージョンでは、挿入時に発生する計算のみが表示されます。
さて、 Martinはコメントで素晴らしいポイントを取り上げています :これは常に正しいとは限りません。永続的な計算された列をカバーしないインデックスを作成し、そのインデックスを使用するクエリを実行して、ルックアップが既存の永続的なデータからデータを取得するか、実行時にデータを計算するかを確認します(関数を削除して再作成)そしてここにテーブル):
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は、ルックアップを実行するよりも値を再計算する方が安くなると考えていました。これはさまざまな要因により変化する可能性があるため、それに依存しないでください。そして、これは、ユーザー定義関数が使用されているかどうかに関係なく、どちらの方向にも発生する可能性があります。説明するのがずっと簡単になったので、ここでのみ使用しました。