web-dev-qa-db-ja.com

複数ステートメントテーブル値関数とインラインテーブル値関数

いくつかの例を示します。

インラインテーブル値

CREATE FUNCTION MyNS.GetUnshippedOrders()
RETURNS TABLE
AS 
RETURN SELECT a.SaleId, a.CustomerID, b.Qty
    FROM Sales.Sales a INNER JOIN Sales.SaleDetail b
        ON a.SaleId = b.SaleId
        INNER JOIN Production.Product c ON b.ProductID = c.ProductID
    WHERE a.ShipDate IS NULL
GO

複数ステートメントテーブル値

CREATE FUNCTION MyNS.GetLastShipped(@CustomerID INT)
RETURNS @CustomerOrder TABLE
(SaleOrderID    INT         NOT NULL,
CustomerID      INT         NOT NULL,
OrderDate       DATETIME    NOT NULL,
OrderQty        INT         NOT NULL)
AS
BEGIN
    DECLARE @MaxDate DATETIME

    SELECT @MaxDate = MAX(OrderDate)
    FROM Sales.SalesOrderHeader
    WHERE CustomerID = @CustomerID

    INSERT @CustomerOrder
    SELECT a.SalesOrderID, a.CustomerID, a.OrderDate, b.OrderQty
    FROM Sales.SalesOrderHeader a INNER JOIN Sales.SalesOrderHeader b
        ON a.SalesOrderID = b.SalesOrderID
        INNER JOIN Production.Product c ON b.ProductID = c.ProductID
    WHERE a.OrderDate = @MaxDate
        AND a.CustomerID = @CustomerID
    RETURN
END
GO

1つのタイプ(インラインまたはマルチステートメント)を他のタイプよりも使用する利点はありますか?一方が他方より優れている場合、または純粋に構文上の違いがある特定のシナリオがありますか? 2つのクエリ例が異なることをしていることに気付きましたが、そのように書く理由はありますか?

それらについて読んだり、利点/相違点については実際には説明されていません。

182
AndrewC

マットのコメントを調査するにあたり、元の声明を修正しました。彼は正しい。インラインテーブル値関数(ITVF)とマルチステートメントテーブル値関数(MSTVF)のパフォーマンスは、どちらもSELECTステートメントを実行するだけでも違いがあります。 SQL Serverは、問題のテーブルの最新の統計を使用して実行プランを計算するという点で、VIEWのようなITVFを扱います。 MSTVFは、SELECTステートメントの内容全体をテーブル変数に詰めてから、それに結合することと同等です。したがって、コンパイラはMSTVFのテーブルのテーブル統計を使用できません。したがって、すべてのものが平等である(めったに同じではない)場合、ITVFはMSTVFよりも優れたパフォーマンスを発揮します。私のテストでは、完了時間のパフォーマンスの違いは無視できましたが、統計の観点からは顕著でした。

あなたの場合、2つの機能は機能的に同等ではありません。 MSTV関数は、呼び出されるたびに追加のクエリを実行し、最も重要なことには、顧客IDでフィルタリングします。大規模なクエリでは、オプティマイザは他の種類の結合を利用できません。これは、渡されたcustomerIdごとに関数を呼び出す必要があるためです。ただし、次のようにMSTV関数を書き直した場合:

CREATE FUNCTION MyNS.GetLastShipped()
RETURNS @CustomerOrder TABLE
    (
    SaleOrderID    INT         NOT NULL,
    CustomerID      INT         NOT NULL,
    OrderDate       DATETIME    NOT NULL,
    OrderQty        INT         NOT NULL
    )
AS
BEGIN
    INSERT @CustomerOrder
    SELECT a.SalesOrderID, a.CustomerID, a.OrderDate, b.OrderQty
    FROM Sales.SalesOrderHeader a 
        INNER JOIN Sales.SalesOrderHeader b
            ON a.SalesOrderID = b.SalesOrderID
        INNER JOIN Production.Product c 
            ON b.ProductID = c.ProductID
    WHERE a.OrderDate = (
                        Select Max(SH1.OrderDate)
                        FROM Sales.SalesOrderHeader As SH1
                        WHERE SH1.CustomerID = A.CustomerId
                        )
    RETURN
END
GO

クエリでは、オプティマイザーはその関数を1回呼び出してより良い実行プランを作成できますが、それでも同等のパラメーター化されていないITVSまたはVIEWより優れていません。

テーブルの列からのデータ型、nullability、および照合は、複数ステートメントテーブル値関数でこれらのプロパティを宣言し、重要なことに、ITVFからより良い実行計画を取得するため、可能であれば、ITVFはMSTVFよりも優先されるべきです。私の経験では、ITVFがVIEWよりも優れたオプションであるが、走行距離は異なる場合が多いという状況を発見していません。

マットに感謝します。

追加

これが最近出てきたので、Wayne Sheffieldがインラインテーブル値関数とマルチステートメント関数のパフォーマンスの違いを比較した優れた分析を紹介します。

彼の元のブログ投稿。

SQL Server Centralにコピー

132
Thomas

内部的に、SQL Serverはインラインテーブル値関数をビューと同様に扱い、複数ステートメントテーブル値関数をストアドプロシージャと同様に扱います。

インラインテーブル値関数が外部クエリの一部として使用される場合、クエリプロセッサはUDF定義を展開し、これらのオブジェクトのインデックスを使用して、基になるオブジェクトにアクセスする実行プランを生成します。

複数ステートメントテーブル値関数の場合、関数自体の実行プランが作成され、実行プランキャッシュに保存されます(関数が最初に実行されると)。複数ステートメントのテーブル値関数がより大きなクエリの一部として使用される場合、オプティマイザーは関数が返すものを知らないため、いくつかの標準的な仮定を行います-実際には、関数は単一の行を返し、関数は、単一行のテーブルに対してテーブルスキャンを使用してアクセスされます。

複数ステートメントのテーブル値関数のパフォーマンスが低いのは、多数の行を返し、外部クエリで結合されている場合です。パフォーマンスの問題は主に、単一の行が返されることを前提としてオプティマイザーがプランを作成するという事実にありますが、これは必ずしも最適なプランではありません。

一般的な経験則として、可能性のあるインラインテーブル値関数は、これらの潜在的なパフォーマンスの問題のため、複数ステートメント関数(UDFを外部クエリの一部として使用する場合)よりも優先して使用する必要があることがわかりました。

27
Paul McLoughlin

別の違いがあります。インラインテーブル値関数は、ビューと同じように挿入、更新、削除できます。同様の制限が適用されます-集計を使用して関数を更新できない、計算列を更新できないなど。

13
Craig Beere

あなたの例は、質問に非常によく答えていると思います。最初の機能は単一の選択として実行でき、インラインスタイルを使用する正当な理由です。 2番目はおそらく単一のステートメントとして実行できます(サブクエリを使用して最大日付を取得します)が、一部のコーダーは読みやすいか、複数のステートメントで実行する方が自然であると感じる場合があります。単純な機能の中には、1つのステートメントで実行できないものがあるため、複数ステートメントバージョンが必要です。

可能な限り最も単純な(インライン)を使用し、必要に応じて(明らかに)複数のステートメントを使用するか、個人的な好み/読みやすさが余分な入力を必要とする場合をお勧めします。

3
Ray

インラインとマルチステートメントのテーブル値関数の比較 を見ると、良い説明とパフォーマンスのベンチマークを見つけることができます

0
hmfarimani

複数行関数を使用する別のケースは、SQLサーバーがwhere句をプッシュダウンするのを回避することです。

たとえば、テーブル名を持つテーブルがあり、一部のテーブル名はC05_2019やC12_2018のようにフォーマットされており、そのようにフォーマットされたすべてのテーブルは同じスキーマを持っています。すべてのデータを1つのテーブルにマージし、05と12をCompNo列に、2018,2019を年列に解析しました。ただし、ACA_StupidTableのような他のテーブルがあり、CompNoとCompYrを抽出できず、試してみると変換エラーが発生します。そのため、私のクエリは2つの部分に分かれていました。「C _______」のような形式のテーブルのみを返す内部クエリで、外部クエリは部分文字列とint変換を行いました。すなわち、CompNoとしてCast(Substring(2、2)as int)結果がフィルタリングされる前にSQLサーバーがCast関数を配置することを決定したことを除いて、すべてが見栄えが良いため、変換エラーをスクランブルしています。基本的に「新しい」テーブルであるため、複数ステートメントのテーブル関数はそれを防ぐことができます。

0
William Egge

私はこれをテストしていませんが、マルチステートメント関数は結果セットをキャッシュします。オプティマイザーが関数をインライン化するには、あまりにも多くの処理が行われる場合があります。たとえば、「会社番号」として渡すものに応じて、異なるデータベースから結果を返す関数があるとします。通常、ユニオンをすべて使用してビューを作成し、会社番号でフィルター処理できますが、SQLサーバーがユニオン全体をプルバックし、1つの選択を呼び出すほどスマートではないことがあります。表関数には、ソースを選択するロジックを含めることができます。

0
William Egge