3

外部適用を使用するいくつかのストアド プロシージャがあります。外側の適用内のクエリは常に同じであるため、共通のテーブル値関数を作成すると、コードの再利用の明らかな利点が得られますが、どちらの方法でもパフォーマンスに影響があるかどうか疑問に思っています. 関数を呼び出した場合にヒットすることはありますか?

例えば:

SELECT
    m.[ID],
    m.[MyField],
    o.[OtherField]
FROM
    [MyTable] m
OUTER Apply
(
    fMyFunction(m.[ID])
)

VS

SELECT
    mt.[ID],
    mt.[MyField],
    o.[OtherField]
FROM
    [MyTable] mt
OUTER Apply
(
    SELECT TOP 1
        ot.[OtherField]
    FROM
        [OtherTable] ot 
    WHERE
        ot.[ID] = m.[ID]
) o
4

2 に答える 2

4

関数の種類によって異なります。

  1. 関数がインライン テーブル値関数である場合、この関数は "パラメーター化された" ビューと見なされ、SQL Serverいくつかの最適化作業を行うことができます。

  2. 関数が複数ステップのテーブル値関数である場合SQL Server、ステートメントを最適化するのは難しく、出力はSET STATISTICS IO誤解を招くものになります。

次のテストでは、 を使用しましたAdventureWorks2008(このデータベースは CodePlex からダウンロードできます)。このサンプル データベースには、次のinline table-valued function名前の が含まれている場合があり[Sales].[ufnGetCheapestProduct]ます。

ALTER FUNCTION [Sales].[ufnGetCheapestProduct](@ProductID INT)
RETURNS TABLE
AS
RETURN
    SELECT   dt.ProductID
            ,dt.UnitPrice
    FROM
    (
        SELECT   d.SalesOrderDetailID
                ,d.UnitPrice
                ,d.ProductID  
                ,ROW_NUMBER() OVER(PARTITION BY d.ProductID ORDER BY d.UnitPrice ASC, d.SalesOrderDetailID) RowNumber
        FROM    Sales.SalesOrderDetail d
        WHERE   d.ProductID = @ProductID
    ) dt
    WHERE   dt.RowNumber = 1

という名前の新しい関数を作成しました[Sales].[ufnGetCheapestProductMultiStep]。この関数は次のmulti-step table-valued functionとおりです。

CREATE FUNCTION [Sales].[ufnGetCheapestProductMultiStep](@ProductID INT)
RETURNS @Results TABLE (ProductID INT PRIMARY KEY, UnitPrice MONEY NOT NULL)
AS
BEGIN
    INSERT  @Results(ProductID, UnitPrice)
    SELECT   dt.ProductID
            ,dt.UnitPrice
    FROM
    (
        SELECT   d.SalesOrderDetailID
                ,d.UnitPrice
                ,d.ProductID  
                ,ROW_NUMBER() OVER(PARTITION BY d.ProductID ORDER BY d.UnitPrice ASC, d.SalesOrderDetailID) RowNumber
        FROM    Sales.SalesOrderDetail d
        WHERE   d.ProductID = @ProductID
    ) dt
    WHERE   dt.RowNumber = 1;

    RETURN;
END

これで、次のテストを実行できます。

--Test 1
SELECT  p.ProductID, p.Name, oa1.*
FROM    Production.Product p
OUTER APPLY 
(
    SELECT   dt.ProductID
            ,dt.UnitPrice
    FROM
    (
        SELECT   d.SalesOrderDetailID
                ,d.UnitPrice
                ,d.ProductID  
                ,ROW_NUMBER() OVER(PARTITION BY d.ProductID ORDER BY d.UnitPrice ASC, d.SalesOrderDetailID) RowNumber
        FROM    Sales.SalesOrderDetail d
        WHERE   d.ProductID = p.ProductID
    ) dt
    WHERE   dt.RowNumber = 1
) oa1

--Test 2
SELECT  p.ProductID, p.Name, oa2.*
FROM    Production.Product p
OUTER APPLY [Sales].[ufnGetCheapestProduct](p.ProductID) oa2

--Test 3
SELECT  p.ProductID, p.Name, oa3.*
FROM    Production.Product p
OUTER APPLY [Sales].[ufnGetCheapestProductMultiStep](p.ProductID) oa3

そして、これは からの出力ですSQL Profiler: ここに画像の説明を入力

結論OUTER APPLY: クエリまたはインライン テーブル値関数を使用すると、同じパフォーマンス (論理読み取り) が得られることがわかります。さらに、複数ステップのテーブル値関数は (通常) より高価です。

:結果が間違っている可能性があるため、スカラーおよび多段階のテーブル値関数のSET STATISTICS IO測定に使用することはお勧めしません。IOたとえば、これらのテストの出力は次のSET STATISTICS IO ONようになります。

--Test 1
Table 'SalesOrderDetail'. Scan count 504, logical reads 1513, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Product'. Scan count 1, logical reads 5, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

--Test 2
Table 'SalesOrderDetail'. Scan count 504, logical reads 1513, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Product'. Scan count 1, logical reads 5, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

--Test 3
Table '#064EAD61'. Scan count 504, logical reads 1008 /*WRONG*/, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Product'. Scan count 1, logical reads 5, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
于 2012-05-15T20:06:21.410 に答える
2

Outer Applyここで考慮されるべきではありません...

理由

MyTableからすべてのレコードを取得するという事実にもかかわらず、の各レコードを繰り返し処理MyTableし、テーブル内の対応するレコードを検索します。Outer Applyしたがって、代わりにJoin(Left / Internal)に置き換える必要があります。これにより、特にフェッチするレコードが多数ある場合にクエリが高速化されます。

適用と参加の違いを確認してください

于 2012-05-15T16:16:30.983 に答える