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


198

念のため、いくつかの例を示します。

インラインテーブルの評価

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つのサンプルクエリが異なることを実行していることに気付きましたが、そのように記述する理由はありますか?

それらについて読んだり、利点/違いを説明したりはしていません。


また、インライン関数の大きな利点の1つは、ROWID(TIMESTAMP)列を選択できる一方で、マルチステートメント関数のリターンテーブルにTIMESTAMPデータを挿入できないことです。
Artru 2013

3
素晴らしいスレッドをありがとう。私はたくさん学びました。ただし、ITVであった関数をMSTVに変更する場合、プロファイラーはITVを変更していると考えます。MSTVの観点から正しい構文を得るために何をしても、通常はBEGINの後の最初のステートメントの前後で、再コンパイルは常に失敗します。これを回避する唯一の方法は、古い関数を削除し、MSTVとして新しい関数を作成することでした。
Fandango68

回答:


141

Mattのコメントを調査するにあたり、私は元のステートメントを修正しました。彼は正解です。インラインテーブル値関数(ITVF)とマルチステートメントテーブル値関数(MSTVF)は、どちらも単純にSELECTステートメントを実行する場合でも、パフォーマンスに違いがあります。SQL ServerはITVFをいくらか扱いますVIEW問題のテーブルの最新の統計を使用して実行計画を計算します。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

ITVFは、可能な場合はMSTVFよりも優先する必要があります。これは、テーブルの列のデータ型、null可能性、および照合順序が複数ステートメントのテーブル値関数で宣言され、重要なことに、ITVFからより優れた実行プランが得られるためです。私の経験では、ITVFがVIEWよりも優れたオプションである状況はあまり見当たりませんが、走行距離はさまざまです。

マットに感謝します。

添加

これが最近発生するのを見たので、インラインテーブル値関数とマルチステートメント関数のパフォーマンスの違いを比較するウェインシェフィールドによる優れた分析を次に示します。

彼のオリジナルのブログ投稿。

SQL Server Centralにコピー


40
これは単に真実ではありません。複数ステートメント関数は、クエリオプティマイザが統計情報を使用するのを妨げるため、非常に頻繁に大きなパフォーマンスヒットになります。マルチステートメント関数の使用が実行プランの選択を非常に悪くするのを見たたびに1ドル持っていた場合(主に返される行数が通常1と推定されるため)、小型車を購入するのに十分です。
マットホイットフィールド、

私が見つけた最も良い説明は最初の答えと関連する投稿です:stackoverflow.com/questions/4109152/…関連するドキュメントをお見逃しなく、すぐに読むことができ、非常に興味深いものです。
JotaBe

1
SQL Server 2017のこの回答に対する更新はありますか?:youtube.com/watch
Ralph

29

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

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

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

複数ステートメントのテーブル値関数のパフォーマンスが低下するのは、それらが多数の行を返し、外部クエリで結合される場合です。パフォーマンスの問題の主な原因は、オプティマイザが単一の行が返されると想定して計画を作成することですが、これは必ずしも最も適切な計画ではありません。

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


2
ストアドプロシージャと同様に複数ステートメントのテーブル値関数を処理できますが、機能的に同一のストアドプロシージャは、大規模なデータセットのテーブル値関数よりもはるかに高速です。マルチステートメントテーブル値関数のストアドプロシージャを使用しています。
Kekoa 2011

6
これらの結果を別のクエリで結合する必要がない限り。
ギジェルモ・グティエレス

なぜ両方使用しないのですか?複数ステートメントのテーブル値関数の結果を返すストアドプロシージャ。両方の長所。
ロビノ2016年

13

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


3

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

私は可能な限り最も単純な(インライン)を使用し、必要な場合(明らかに)、または個人的な好み/読みやすさが追加のタイピングを必要とする場合はマルチステートメントを使用することをお勧めします。


答えてくれてありがとう。つまり、基本的に、マルチステートメントは、読みやすさのために、関数がインライン関数で実行できるよりも複雑な場合にのみ実際に使用されますか?マルチステートメントにパフォーマンス上のメリットはありますか?
AndrewC 2010年

わかりませんが、そうは思いません。おそらく、SQLサーバーに、手動で(変数、一時テーブルなどを使用して)作成しようとする可能性のある最適化を把握させることをお勧めします。確かに、特定のケースでこれを証明/反証するためにいくつかのパフォーマンステストを行うことができます。
レイ・

改めて感謝いたします。時間があれば、さらに詳しく調べます。:)
AndrewC 2010年


0

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


0

複数行関数を使用するもう1つのケースは、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

多分非常に凝縮された方法で。ITVF(インラインTVF):uがDB担当者の場合、パラメーター化されたビューの一種であり、単一のSELECT stを取る

MTVF(マルチステートメントTVF):開発者、テーブル変数を作成してロードします。


-2

クエリを実行する場合は、次のようなインラインテーブル値関数に参加できます。

SELECT
    a.*,b.*
    FROM AAAA a
        INNER JOIN MyNS.GetUnshippedOrders() b ON a.z=b.z

オーバーヘッドはほとんどなく、問題なく動作します。

同様のクエリでMulti Statement Table Valuedを使用しようとすると、パフォーマンスの問題が発生します。

SELECT
    x.a,x.b,x.c,(SELECT OrderQty FROM MyNS.GetLastShipped(x.CustomerID)) AS Qty
    FROM xxxx   x

返された行ごとに1回関数を実行するため、結果セットが大きくなると、実行速度が遅くなります。


ああ、あなたはインラインのほうがパフォーマンスの点ではるかに優れていると言うでしょう?
AndrewC 2010年

1
いいえ、どちらもテーブルを返します。列にテーブルを配置しようとすると、2番目のSQLが無効になります。
cjk 2010年

1
@ck、私はあなたがコメントしたクエリを更新しました。2番目の関数で使用される関数のパラメーターは、サブクエリとして使用されるため、パフォーマンスが低下します。
和基。
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.