以下は私が作成したビューです
CREATE VIEW [ForMasterTable]
WITH SCHEMABINDING AS
SELECT
[MasterID],
[MasterColumnData],
[DetailID],
[DetailColumnData],
[SomeOtherID],
[SomeOtherColumnData]
FROM [MasterTable]
LEFT OUTER JOIN [DetailTable] ON [MasterTable].[DetailID]= [DetailTable].[DetailID]
LEFT OUTER JOIN [SomeOtherTable] ON [MasterTable].[SomeOtherID]= [SomeOtherTable].[SomeOtherID]
このビューにブール列を追加したいのですが、その値はサブクエリの条件の結果に依存します
CASE
WHEN EXISTS (
SELECT ErrorID
FROM [ErrorTable]
WHERE DetailID IN
(SELECT [DetailID]
FROM [DetailTable]
WHERE MasterID = [MasterTable].[MasterID])
)
THEN
1
ELSE
0
END
私の見解では、このようにするにはどうすればよいですか?また、ネストされたクエリは、行数が増えるにつれてパフォーマンスを指数関数的に低下させると想定しています。これで何か助けていただければ幸いです。
最も重要なことは、パフォーマンスが低下することを想定する必要がないことです。 SQLサーバーは、いくつかのサブクエリを書き換えて、行ごとの操作が発生すると想定しているような操作を回避できます。サブクエリによってビューがかなり遅くなる可能性は確かにありますが、これを自分でテストすることができます。実際、テーブルの構造、インデックス、統計、テーブルのサイズなどがわからないため、これよりもはるかに効果的にテストできます。
ビット列がある場合とない場合のビューを使用するクエリを実行します。新しい列を使用したクエリは指数的に遅くなりますか?実際の実行計画を取得して分析します。 SET STATISTICS IO、TIME ONの後でクエリを実行し、IO、CPU時間、および経過時間が異なるクエリでどのように変化するかを確認します。
サブクエリなしでそのクエリを書き換える1つの方法を紹介します。 [MasterID]
は[MasterTable]
に対して一意であると想定しています。
サブクエリのロジックを見ていきましょう。それを表現する1つの方法は、[MasterTable].[MasterID]
から始めて、関連するすべての[DetailID]
値を[DetailTable]
から取得することです。 [ErrorTable]
に少なくとも1つの行があり、一致するDetailID
がある場合、ビット列の値は1になります。それ以外の場合、値は0になります。
これを派生テーブルに書き換えることができます。派生テーブルに、[MasterID]
に対応するエントリがあるすべての[ErrorTable]
がある場合、LEFT OUTER JOIN
を実行できます。結合列がNULL
ではない場合、一致があったため、ビット列の値は1です。それ以外の場合、値は0です。
ここに1つの実装があります:
SELECT
[MasterID],
[MasterColumnData],
[DetailID],
[DetailColumnData],
[SomeOtherID],
[SomeOtherColumnData],
CASE WHEN bool.[MasterID] IS NOT NULL THEN 1 ELSE 0 END
FROM [MasterTable]
LEFT OUTER JOIN [DetailTable] ON [MasterTable].[DetailID]= [DetailTable].[DetailID]
LEFT OUTER JOIN [SomeOtherTable] ON [MasterTable].[SomeOtherID]= [SomeOtherTable].[SomeOtherID]
LEFT OUTER JOIN
(
SELECT DISTINCT dt.[MasterID]
FROM [DetailTable] dt
WHERE EXISTS
(
SELECT 1
FROM [ErrorTable] et
WHERE dt.DetailID = et.DetailID
)
) bool ON [MasterTable].[MasterID] = bool.[MasterID];
次のVIEW
はMS SQL Server 2016
で機能し、「元の仕様にできるだけ近づきます」(SCHEMABINDING
は無視しているので、これは質問の中核):
CREATE VIEW [ForMasterTable] AS
SELECT
[MasterID],
[MasterColumnData],
[MasterTable].[DetailID],
[DetailColumnData],
[MasterTable].[SomeOtherID],
[SomeOtherColumnData],
CAST (CASE
WHEN EXISTS (
SELECT ErrorID
FROM [ErrorTable]
WHERE DetailID IN
(SELECT [DetailID]
FROM [DetailTable]
WHERE MasterID = [MasterTable].[MasterID])
)
THEN 1
ELSE 0
END AS BIT) AS ErrorExists
FROM
[MasterTable]
LEFT JOIN [DetailTable] ON [MasterTable].[DetailID]= [DetailTable].[DetailID]
LEFT JOIN [SomeOtherTable] ON [MasterTable].[SomeOtherID]= [SomeOtherTable].[SomeOtherID] ;
私はブール結果を0 = false/1 = trueビットとして表します(CAST
ingによって)。
実行計画が現実的なデータでどのように見えるかに興味があります。 (ほんの数行の)シミュレートされたデータを使用すると、それは完全に合理的に見え、指数関数的に悪化するとは思わない。最悪の場合、直線的に劣化する可能性があります。ほとんどの現代のデータベースは、EXISTSをLEFT(SEMI-)JOINとして「理解」し、[DetaildID] INをJOINとして「理解」するため、SQL Serverは最適に近い計画を生成すると思います。
元のテーブルは、次の定義に類似していると想定されています。
CREATE TABLE [DetailTable]
(
DetailID integer PRIMARY KEY,
DetailColumnData varchar
) ;
CREATE TABLE [SomeOtherTable]
(
SomeOtherID integer PRIMARY KEY,
SomeOtherColumnData varchar
) ;
CREATE TABLE [MasterTable]
(
MasterID integer PRIMARY KEY,
DetailID integer REFERENCES [DetailTable] (DetailID),
SomeOtherID integer REFERENCES [SomeOtherTable] (SomeOtherID),
MasterColumnData varchar
) ;
CREATE TABLE [ErrorTable]
(
ErrorID integer PRIMARY KEY,
DetailID integer REFERENCES DetailTable (DetailID)
) ;