計算列はいつ計算されますか?


29

計算列の値はいつ決定されますか?

  • 値が取得されるとき?
  • 値が変更されたとき
  • いつか?

私は検索で何も見つけられないので、これは初心者の質問だと思います。

回答:


19

計算列の定義方法によって異なります。PERSISTED計算列を計算し、テーブル内のデータとして格納されます。列をとして定義しないPERSISTED場合、クエリの実行時に計算されます。

優れた説明と証拠については、アーロンの回答をご覧ください。

Pinal Daveはこれについても詳しく説明し、彼のシリーズでストレージの証拠を示しています。

SQL SERVER –計算列– PERSISTEDおよびストレージ


6
永続化されているが、クエリプランがその列をカバーしないインデックスを使用している場合はどうでしょうか。ルックアップを取得するのか、それともその場で計算するだけでテストできないのかはわかりません。
マーティンスミス

1
私のテストでは、SQL Serverはルックアップよりも再計算を選択しました。
アーロンバートランド

34

これは自分で証明するのは非常に簡単です。スカラーのユーザー定義関数を使用する計算列を持つテーブルを作成し、更新と選択の両方の前後に計画と関数の統計を確認し、実行が記録されるタイミングを確認できます。

この関数があるとしましょう:

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は、ルックアップを実行するよりも値を再計算する方が安価だと考えました。これはさまざまな要因により変化する可能性があるため、それに依存しないでください。これは、ユーザー定義関数が使用されているかどうかに関係なく、どちらの方向でも発生する可能性があります。ここで使用したのは、説明がずっと簡単になったからです。


結果を計算する際のエンジンの動作に疑問を抱くことはありませんでした。
アーサーD

8
@ArthurDこれは、各選択肢の推定コストに基づいて(ほとんど)オプティマイザーの決定です。別の質問への私の答えはこちらをご覧ください。
ポールホワイトはGoFundMonicaを言う

-1

この質問への答えは本当に「それは依存する」です。SQL Serverが永続化された計算列のインデックスを使用しているにもかかわらず、値が最初から永続化されていないかのように機能を実行している例を見つけました。列のデータ型(nvarchar(37))またはテーブルのサイズ(約700万行)に関係している可能性がありますが、SQL Server persistedはこの特定のインスタンスでキーワードを無視することにしました。

この場合、テーブルの主キーはTransactionIDであり、これも計算列および永続化列です。実行計画はインデックススキャンを生成しており、700万行しかないテーブルでは、この単純なクエリの実行には2〜3分かかります。これは、関数がすべての行で再度実行され、値が保持されないためですインデックス。

永続化された列を持つテーブルの作成 機能が実行されていることを示す実行計画

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.