web-dev-qa-db-ja.com

サブクエリを使用した派生テーブルとの内部結合

環境:SQL 2008 R2

サブクエリを使用して派生テーブルを作成し、メインテーブルと結合しました。サブクエリが一度だけ実行されるのか、結果セットの各行に対して実行されるのかを知りたいだけです。次の例を検討してください(参照用の架空のテーブル名)

SELECT E.EID,DT.Salary FROM Employees E
INNER JOIN
(
    SELECT EID, (SR.Rate * AD.DaysAttended) Salary
    FROM SalaryRate SR
    INNER JOIN AttendanceDetails AD on AD.EID=SR.EID
) DT --Derived Table for inner join
ON DT.EID=E.EID

つまり、内部結合に使用されるサブクエリは1回だけ実行されますか?それとも複数回実行されますか?

OUTER APPLYを使用して上記のクエリを書き換えると、確実にサブクエリが各行に対して実行されることがわかります。下記参照。

SELECT E.EID,DT.Salary FROM Employees E
OUTER APPLY
(
    SELECT (SR.Rate * AD.DaysAttended) Salary
    FROM SalaryRate SR
    INNER JOIN AttendanceDetails AD on AD.EID=SR.EID
    WHERE SR.EID=E.EID
) DT --Derived Table for outer apply

したがって、内部結合がサブクエリを1回だけ実行するようにしたいだけです。

11
love kumar

最初に注意することは、クエリが比較可能ではないことです。OUTER APPLYCROSS APPLYに置き換えるか、INNER JOINLEFT JOINに置き換える必要があります。

ただし、それらを比較可能にすると、両方のクエリのクエリプランが同一であることがわかります。サンプルDDLをモックアップしました。

CREATE TABLE #Employees (EID INT NOT NULL);
INSERT #Employees VALUES (0);
CREATE TABLE #SalaryRate (EID INT NOT NULL, Rate MONEY NOT NULL);
CREATE TABLE #AttendanceDetails (EID INT NOT NULL, DaysAttended INT NOT NULL);

以下を実行します。

SELECT E.EID,DT.Salary FROM #Employees E
OUTER APPLY
(
    SELECT (SR.Rate * AD.DaysAttended) Salary
    FROM #SalaryRate SR
    INNER JOIN #AttendanceDetails AD on AD.EID=SR.EID
    WHERE SR.EID=E.EID
) DT; --Derived Table for outer apply

SELECT E.EID,DT.Salary FROM #Employees E
LEFT JOIN
(
    SELECT SR.EID, (SR.Rate * AD.DaysAttended) Salary
    FROM #SalaryRate SR
    INNER JOIN #AttendanceDetails AD on AD.EID=SR.EID
) DT --Derived Table for inner join
ON DT.EID=E.EID;

次の計画を示します。

enter image description here

そして、インナー/クロスへの変更:

SELECT E.EID,DT.Salary FROM #Employees E
CROSS APPLY
(
    SELECT (SR.Rate * AD.DaysAttended) Salary
    FROM #SalaryRate SR
    INNER JOIN #AttendanceDetails AD on AD.EID=SR.EID
    WHERE SR.EID=E.EID
) DT; --Derived Table for outer apply


SELECT E.EID,DT.Salary FROM #Employees E
INNER JOIN
(
    SELECT SR.EID, (SR.Rate * AD.DaysAttended) Salary
    FROM #SalaryRate SR
    INNER JOIN #AttendanceDetails AD on AD.EID=SR.EID
) DT --Derived Table for inner join
ON DT.EID=E.EID;

次の計画を示します。

enter image description here

これらは、外側のテーブルにデータがなく、従業員に1行しかないため、現実的ではない計画です。外部適用の場合、SQL Serverは従業員に行が1つしかないことを判断できます。そのため、外部テーブルに対してネストされたループ結合(行ごとのルックアップ)を行うだけで有益です。従業員に1,000行を配置した後、LEFT JOIN/OUTER APPLYを使用すると、次の計画が得られます。

enter image description here

ここで、結合がハッシュマッチ結合になっていることがわかります。つまり、(最も簡単に言えば)SQL Serverは、最初に外部クエリを実行し、結果をハッシュしてから従業員から参照することが最善の計画であると判断しました。ただし、これはサブクエリ全体が実行され、結果が保存されることを意味するものではなく、簡単にするためにこれを考慮することができますが、外部クエリからの述語は、たとえばサブクエリが実行されて内部に保存された場合でも、引き続き使用できます、次のクエリでは大きなオーバーヘッドが発生します。

SELECT E.EID,DT.Salary FROM #Employees E
LEFT JOIN
(
    SELECT SR.EID, (SR.Rate * AD.DaysAttended) Salary
    FROM #SalaryRate SR
    INNER JOIN #AttendanceDetails AD on AD.EID=SR.EID
) DT --Derived Table for inner join
ON DT.EID=E.EID
WHERE E.EID = 1;

実際に1人の従業員を検索するだけで、すべての従業員のレートを取得し、結果を保存する場合、何がポイントになりますか?実行計画を調べると、EID = 1述語が#AttendanceDetailsのテーブルスキャンに渡されていることがわかります。

enter image description here

したがって、次のポイントに対する答えは次のとおりです。

  • OUTER APPLYを使用して上記のクエリを書き直せば、確実にサブクエリが各行に対して実行されることがわかります。
  • 内部結合はサブクエリを1回だけ実行します。

依存する。 SQL Serverは、APPLYを使用すると、可能な場合はクエリをJOINとして書き換えようとします。これにより、最適なプランが得られるため、OUTER APPLYを使用しても、クエリが行ごとに1回実行されることは保証されません。同様に、LEFT JOINを使用しても、クエリが1回だけ実行されることは保証されません。

SQLは宣言型の言語であり、実行方法ではなく実行したいことを伝えるため、特定のコマンドに依存して特定の動作を引き出すべきではありません。代わりに、パフォーマンスの問題が見つかった場合は、実行計画を確認してください、およびIO統計を使用して、それがどのように行われているかを確認し、クエリを改善する方法を特定します。

さらに、SQL Serverはサブクエリをマテリアライズしません。通常、定義はメインクエリに展開されます。

SELECT E.EID,DT.Salary FROM #Employees E
INNER JOIN
(
    SELECT SR.EID, (SR.Rate * AD.DaysAttended) Salary
    FROM #SalaryRate SR
    INNER JOIN #AttendanceDetails AD on AD.EID=SR.EID
) DT --Derived Table for inner join
ON DT.EID=E.EID;

実際に実行されるのは、次のようなものです。

SELECT  e.EID, sr.Rate * ad.DaysAttended AS Salary
FROM    #Employees e
        INNER JOIN #SalaryRate sr
            on e.EID = sr.EID
        INNER JOIN #AttendanceDetails ad
            ON ad.EID = sr.EID;
12
GarethD

INNER JOINを使用すると、サブクエリは1回だけ実行され、そのレコードは複雑な操作でtempdb作業テーブルに内部的に格納され、最初のテーブルに結合されます。

APPLY句を使用すると、1番目のテーブルのすべての行に対してサブクエリが実行されます。

編集:CTEを使用

;with SalaryRateCTE as 
(
    SELECT EID, (SR.Rate * AD.DaysAttended) AS Salary
    FROM SalaryRate SR
    INNER JOIN AttendanceDetails AD on AD.EID=SR.EID
)
SELECT E.EID, DT.Salary 
FROM Employees E
INNER JOIN SalaryRateCTE DT --Derived Table for inner join
ON DT.EID = E.EID
2
Manoj Pandey

サブクエリは1回だけ評価されます。混乱を避けるために、外部クエリと内部クエリは相互に関連していないため、サブクエリを単一のテーブル/ビューと考えることができます。

1
anonxen