結果としてテーブルを返さなければならないT-SQLコードの一部を実装する必要があるとしましょう。テーブル値関数、または行のセットを返すストアドプロシージャを実装できます。何を使うべきですか?
要するに、私が知りたいのは:
関数とストアドプロシージャの主な違いはどれですか?どちらか一方を使用する場合、どのような考慮事項を考慮する必要がありますか?
結果としてテーブルを返さなければならないT-SQLコードの一部を実装する必要があるとしましょう。テーブル値関数、または行のセットを返すストアドプロシージャを実装できます。何を使うべきですか?
要するに、私が知りたいのは:
関数とストアドプロシージャの主な違いはどれですか?どちらか一方を使用する場合、どのような考慮事項を考慮する必要がありますか?
このコード片の結果を他のテーブルと組み合わせる可能性が高い場合、テーブル値関数を使用すると、単一の SELECT ステートメントで結果を構成できることは明らかです。
一般に、階層があります (View < TV Function < Stored Proc)。それぞれでより多くのことができますが、出力を構成する能力と、オプティマイザーが実際に関与する能力は、機能が増加するにつれて減少します。
したがって、目的の結果を最小限に表現できるものを使用してください。
関数は決定論的でなければならず、データベースを変更するために使用することはできませんが、ストアド プロシージャを使用すると、挿入や更新などを行うことができます。
関数は、大規模で複雑なクエリのスケーラビリティに大きな問題を引き起こすため、関数の使用を制限する必要があります。これらは、クエリ オプティマイザーの一種の「ブラック ボックス」になり、関数を使用する場合と単にコードをクエリに挿入する場合とでは、パフォーマンスに大きな違いが見られます。
ただし、非常に特殊なケースでのテーブル値の戻り値には間違いなく役立ちます。
コンマ区切りのリストを解析する必要がある場合は、配列をプロシージャに渡すことをシミュレートするために、関数を使用してリストをテーブルに変換できます。これは、Sql Server 2005 では一般的な方法です。テーブルをストアド プロシージャにまだ渡すことができないためです (2008 では可能です)。
ストアド プロシージャが次の基準を満たしている場合は、テーブル値関数として書き直すのに適しています。
ロジックは 1 つの SELECT ステートメントで表現できますが、パラメーターが必要なため、ビューではなくストアド プロシージャになります。
ストアド プロシージャは、テーブル変数を除き、更新操作を実行しません。
動的な EXECUTE ステートメントは必要ありません。
ストアド プロシージャは、1 つの結果セットを返します。
ストアド プロシージャの主な目的は、一時テーブルにロードされる中間結果を作成し、SELECT ステートメントでクエリを実行することです。
ストアド プロシージャと関数の興味深い違いをいくつか書きます。
関数で非決定論的な関数を使用することはできませんが、ストアド プロシージャで非決定論的な関数を使用することはできます。ここで質問が出てきます。非決定論的関数とは何ですか..Ansは:-
非決定論的関数とは、getdate() のように、異なる時間に同じ入力値に対して異なる出力を返す関数です。実行されるたびに、常に異なる値を返します。
例外:-
SQL Server 2000 より前のバージョンでは、ユーザー定義関数で getdate() 関数を使用できませんでしたが、バージョン 2005 以降では、ユーザー定義関数内で getdate() 関数を使用できます。
Newid() は非決定論的関数の別の例ですが、ユーザー定義関数では使用できませんが、ストアド プロシージャでは使用できます。
ストアド プロシージャ内で DML (挿入、更新、削除) ステートメントを使用できますが、物理テーブルまたは永続テーブルの関数では DML ステートメントを使用できません。関数で DML 操作を実行する場合は、永続テーブルではなくテーブル変数に対して実行できます。
関数内でエラー処理を使用することはできませんが、ストアド プロシージャでエラー処理を行うことができます。
テーブル値関数とストアド プロシージャの両方で同じコード (長い 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
この手法をいくつかの関数で使用しましたが、このタイプの「オプション」パラメーターの長いリストを使用する場合もあります。
関数がある場合は、SQL ステートメントの一部として使用できます。たとえば、
SELECT function_name(field1) FROM table
ストアド プロシージャでは、この方法は機能しません。
前述のように、関数はより読みやすく、構成可能で、自己文書化されていますが、一般的にはパフォーマンスが低く、次のような結合で関数に夢中になると、パフォーマンスが大幅に低下する可能性があります。
SELECT *
FROM dbo.tvfVeryLargeResultset1(@myVar1) tvf1
INNER JOIN dbo.tvfVeryLargeResultset1(@myVar2) tvf2
ON (tvf1.JoinId = tvf2.JoinId)
多くの場合、tvf が (許容できないパフォーマンス コストで) 排除できるコードの冗長性を受け入れる必要があります。
まだ言及されていないもう 1 つのポイントは、複数ステートメントの tvf 内でデータベースの状態を変更する一時テーブルを使用できないことです。 一時テーブルと機能的に最も同等のメカニズムは、メモリ テーブル変数内で状態が変化しないことです。大規模なデータセットの場合、一時テーブルはテーブル変数よりもパフォーマンスが高い可能性があります。(他の代替手段には、動的テーブルと一般的なテーブル値式が含まれますが、ある程度複雑になると、これらは良い選択肢ではなくなります。)
私が返すすべてが影響のない単一のテーブルである場合、私は個人的にテーブル値関数を使用します。基本的に、パラメータ化されたビューのように扱います。
複数のレコードセットを返す必要がある場合、またはテーブルで更新される値がある場合は、ストアド プロシージャを使用します。
私の2セント
私は両方のパフォーマンスをテストします。sp アプローチまたは派生テーブルは、関数よりも大幅に高速である可能性が高く、その場合はそのアプローチを使用する必要があります。一般に、関数はパフォーマンスを浪費する可能性があるため、関数は避けます。
それは場合によります:)テーブル値の結果を別のプロシージャで使用する場合は、TableValued関数を使用することをお勧めします。結果がクライアントのものである場合、通常はストアド プロシージャを使用することをお勧めします。