web-dev-qa-db-ja.com

値がサブクエリに依存するビューに追加のブール列を作成するにはどうすればよいですか?

以下は私が作成したビューです

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

私の見解では、このようにするにはどうすればよいですか?また、ネストされたクエリは、行数が増えるにつれてパフォーマンスを指数関数的に低下させると想定しています。これで何か助けていただければ幸いです。

3
lohiarahul

最も重要なことは、パフォーマンスが低下することを想定する必要がないことです。 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];
2
Joe Obbish

次のVIEWMS 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ビットとして表します(CASTingによって)。

実行計画が現実的なデータでどのように見えるかに興味があります。 (ほんの数行の)シミュレートされたデータを使用すると、それは完全に合理的に見え、指数関数的に悪化するとは思わない。最悪の場合、直線的に劣化する可能性があります。ほとんどの現代のデータベースは、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)
) ;
0
joanolo