結果としてテーブルを返す必要があるT-SQLコードを実装する必要があるとしましょう。テーブル値関数、または行のセットを返すストアドプロシージャを実装できます。何を使うべきですか?
要するに、私が知りたいのは:
関数とストアドプロシージャの主な違いはどれですかどちらを使用するかを考慮する必要があるのはどのような考慮事項ですか?
このコードの結果を他のテーブルと組み合わせたい場合は、明らかにテーブル値関数を使用すると、単一のSELECTステートメントで結果を作成できます。
通常、階層があります(表示<TV関数<ストアドプロシージャ)。それぞれでさらに多くのことを行うことができますが、出力を構成する機能、およびオプティマイザーが実際に関与する機能は、機能が増加するにつれて低下します。
したがって、必要な結果を表現できる最小限のものを使用してください。
関数は確定的である必要があり、データベースの変更に使用することはできませんが、ストアドプロシージャでは挿入や更新などを行うことができます。
関数は、大きく複雑なクエリに対しては大きなスケーラビリティの問題を引き起こすため、使用を制限する必要があります。これらはクエリオプティマイザーにとって一種の「ブラックボックス」になり、関数の使用とクエリへのコードの挿入の間でパフォーマンスに大きな違いが見られます。
しかし、それらは非常に特定のケースでのテーブル値のリターンには間違いなく役立ちます。
コンマ区切りのリストを解析する必要がある場合、プロシージャへの配列の受け渡しをシミュレートするために、関数はリストをテーブルに変換できます。ストアドプロシージャにテーブルをまだ渡すことができないので、これはSql Server 2005の一般的な方法です(2008でできます)。
ドキュメントから :
ストアドプロシージャが次の条件を満たしている場合、テーブル値関数として書き換えるのに適しています。
ロジックは単一のSELECTステートメントで表現できますが、パラメーターが必要なため、ビューではなくストアドプロシージャです。
ストアドプロシージャは、テーブル変数を除き、更新操作を実行しません。
動的EXECUTEステートメントは必要ありません。
ストアドプロシージャは1つの結果セットを返します。
ストアドプロシージャの主な目的は、一時テーブルにロードされる中間結果を作成することです。一時テーブルは、SELECTステートメントでクエリされます。
ストアドプロシージャと関数の興味深い違いをいくつか書きます。
関数では非決定的関数を使用できませんが、ストアドプロシージャでは非決定的関数を使用できます。ここで、非決定的関数とは何ですか?
非決定的関数とは、getdate()のように、同じ入力値に対して異なる時間に異なる出力を返す関数です。実行されるたびに常に異なる値を返します。
例外:-
Sql 2000より前のsqlサーバーの以前のバージョンでは、ユーザー定義関数でgetdate()関数を使用できませんが、バージョン2005以降では、ユーザー定義関数内でgetdate()関数を使用できます。
Newid()は非決定的関数の別の例ですが、ユーザー定義関数では使用できませんが、ストアドプロシージャでは使用できます。
ストアドプロシージャ内でDML(挿入、更新、削除)ステートメントを使用できますが、物理テーブルまたは永続テーブルの関数でDMLステートメントを使用することはできません。関数でDML操作を行いたい場合は、永続テーブルではなくテーブル変数に対して実行できます。
関数内でエラー処理を使用することはできませんが、ストアドプロシージャでエラー処理を行うことはできます。
プロシージャは0またはnの値を返すことができますが、関数は必須の1つの値を返すことができます。
プロシージャは入力/出力パラメータを持つことができますが、関数は入力パラメータのみを持つことができます。
プロシージャではselectおよびDMLステートメントを使用できますが、関数ではselectステートメントのみを使用できます。
プロシージャから関数を呼び出すことはできますが、プロシージャから関数を呼び出すことはできません。
例外はプロシージャのtry-catchブロックで処理できますが、try-catchブロックは関数では使用できません。
トランザクション管理を手続きで行うことができますが、機能することはできません。
プロシージャはselect文で使用できませんが、関数はselect文に埋め込むことができます。
UDF(ユーザー定義関数)は、WHERE
/HAVING
/SELECT
セクションのどこでもSQLステートメントで使用できますが、ストアドプロシージャは使用できません。
テーブルを返すUDFは、別の行セットとして扱うことができます。これは、他のテーブルのJOIN
sで使用できます。
インラインUDFは、パラメーターを受け取り、JOIN
sおよびその他の行セット操作で使用できるビューとして使用できます。
関数がある場合は、たとえば、SQLステートメントの一部として使用できます。
SELECT function_name(field1) FROM table
ストアドプロシージャに対しては、この方法では機能しません。
テーブル値関数とストアドプロシージャの両方で実行されるコードの同じビット(長いSELECTステートメント)と、単純なEXEC/SELECTを使用して、いくつかのテストを実行しました。
私の意見では、結果セットを返すためにストアドプロシージャではなくテーブル値関数を常に使用します。これにより、後で結合するクエリでロジックがはるかに簡単かつ読みやすくなり、同じロジックを再利用できます。パフォーマンスへの過度の影響を避けるために、「オプション」パラメーターを使用して(つまり、NULLを渡すことができます)、関数が結果セットをより速く返せるようにします。
CREATE FUNCTION dbo.getSitePermissions(@RegionID int, @optPersonID int, optSiteID int)
AS
RETURN
SELECT DISTINCT SiteID, PersonID
FROM dbo.SiteViewPermissions
WHERE (@optPersonID IS NULL OR @optPersonID = PersonID)
AND (@optSiteID IS NULL OR @optSiteID = SiteID)
AND @RegionID = RegionID
これにより、この関数をさまざまな状況で使用でき、パフォーマンスに大きな影響を与えません。これは後でフィルタリングするよりも効率的だと思います。
SELECT * FROM dbo.getSitePermissions(@RegionID) WHERE SiteID = 1
私はこの手法をいくつかの関数で使用しましたが、時にはこのタイプの「オプション」パラメーターの長いリストを使用していました。
上記のように、関数は読みやすく/構成可能/自己文書化されますが、一般的にはパフォーマンスが低下します。
SELECT *
FROM dbo.tvfVeryLargeResultset1(@myVar1) tvf1
INNER JOIN dbo.tvfVeryLargeResultset1(@myVar2) tvf2
ON (tvf1.JoinId = tvf2.JoinId)
多くの場合、tvfで排除できるコードの冗長性を受け入れる必要があります(許容できないパフォーマンスコストで)。
まだ言及していないもう1つの点は、マルチステートメントtvf内ではデータベースの状態を変更する一時テーブルを使用できないことです。 一時テーブルと最も機能的に同等のメカニズムは、メモリテーブル変数で状態が変化しないことです。大規模なデータセットの場合、一時テーブルはテーブル変数よりもパフォーマンスが高い可能性があります。 (他の選択肢には動的テーブルと共通テーブル値式が含まれますが、ある程度の複雑さでは、これらは良いオプションIMOではなくなります。)
私が返すのは、影響のない単一のテーブルのみである場合、個人的にテーブル値関数を使用します。基本的に、それらをパラメーター化されたビューのように扱います。
複数のレコードセットを返す必要がある場合、またはテーブルで値が更新される場合は、ストアドプロシージャを使用します。
私の2セント
両方のパフォーマンステストを行います。 spアプローチまたは派生テーブルは関数よりも大幅に高速になる可能性が高いため、その場合はそのアプローチを使用する必要があります。一般に、パフォーマンスが高くなる可能性があるため、関数は避けます。
依存します:)テーブル値の結果を別のプロシージャで使用する場合は、TableValued関数を使用する方が適切です。結果がクライアント向けである場合、通常はストアドプロシージャの方が適しています。