web-dev-qa-db-ja.com

ストアドプロシージャ内で関数を定義することはできますか?

ストアドプロシージャがあります。

create proc sp_MyProc(@calcType tinyint) as
begin
    -- some stuff collating data into #MyTempTable

    if (@calcType = 1) -- sum
        select A, B, C, CalcField = sum(Amount) 
        from #MyTempTable t 
        join AnotherTable a on t.Field1 = a.Field1;
        group by A, B, C
    else if (@calcType = 2) -- average
        select A, B, C, CalcField = avg(Amount) 
        from #MyTempTable t 
        join AnotherTable a on t.Field1 = a.Field1;
        group by A, B, C
    else if (@calcType = 3) -- some other fancy formula
        select A, B, C, CalcField = sum(case when t.Type = 1 then 1 else 0 end) * t.Factor 
        from #MyTempTable t 
        join AnotherTable a on t.Field1 = a.Field1;
        group by A, B, C
    -- plus a whole bunch of other, similar cases
    else    
        select A, B, C, CalcField = 0.0
        from #MyTempTable t 
        join AnotherTable a on t.Field1 = a.Field1;
        group by A, B, C
end

@calcTypeの値が異なるこれらすべてのケースは、多くのスペースを浪費しているように見え、コピーと貼り付けを余儀なくされ、常に背筋が震えています。

C#のラムダ表記に似た、CalcFieldの関数を宣言して、コードをよりコンパクトで保守可能な方法にする方法はありますか?私はこのようなことをしたいと思います:

declare @func FUNCTION(@t #MyTempTable) as real -- i.e. input is of type #MyTempTable and output is of type real

if (@calcType = 1) -- sum
    set @func = sum(@t.Amount)
else if (@calcType = 2) -- average
    set @func = avg(@t.Amount)
else if (@calcType = 3) -- some other fancy formula
    set @func = sum(case when @t.Type = 1 then 1 else 0 end) * @t.Factor 
-- plus a whole bunch of other, similar cases
else    
    set @func = 0;

select A, B, C, CalcField = @func(t) 
from #MyTempTable t 
join AnotherTable a on t.Field1 = a.Field1;
group by A, B, C

明らかにここの構文は機能しませんが、私が望むものを達成するものはありますか?

7
Shaul Behr

いいえ、これは不可能です。

_#MyTempTable_への参照があるため、永続的なTVFまたはビューはオプションではありません

一時的なビュー に対するアイテムの接続要求を見てきましたが、それらが役立つ場合があることに同意します。これは、 モジュールレベルのテーブル式 を要求するものと重複して閉じられました。

あなたは次のように書き直すことができるかもしれません

_SELECT A,
       B,
       C,
       CASE @calcType
         WHEN 1
           THEN sum(Amount)
         WHEN 2
           THEN avg(Amount)
       END
FROM   #MyTempTable t
       JOIN AnotherTable a
         ON t.Field1 = a.Field1
GROUP  BY A,
          B,
          C
_

または、ニーズがより複雑な場合(たとえば、同じ形状の結果セットと同じソースを使用しているが、グループ化条件が異なる場合)

_WITH Base
     AS (SELECT A,
                B,
                C,
                Factor,
                Type
         FROM   #MyTempTable t
                JOIN AnotherTable a
                  ON t.Field1 = a.Field1) 
SELECT A,
       B,
       C,
       sum(Amount)
FROM   Base
WHERE  @calcType = 1
GROUP  BY A,
          B,
          C
UNION ALL
SELECT A,
       B,
       C,
       CalcField = 0.0 * t.Factor
FROM   Base
WHERE  @calcType NOT IN ( 1, 2, 3 ) 
_

特に最後のものでは、計画を単純化するためにOPTION (RECOMPILE)を検討するかもしれません。 (おそらく、ヒントのない起動述語を持つフィルターがあり、実際には冗長な分岐を実行しませんが、確認する必要があります。起動述語が保持されている場合、このアプローチでは行の推定値が間違っている可能性があります)。

どちらも適していない場合は、動的SQLの領域に入る必要があります。

5
Martin Smith

厳密に言えば(T-SQLサブルーチン):いいえ。

技術的に言えば(一度定義される数式を抽象化する手段):はい。

実用的に言えば、それは:)に依存します。

T-SQL関数の制限に関して現在妨げとなっている問題を次に示します。

  • 動的に宣言することはできません
  • 一時テーブルにアクセスできません
  • それらは集約関数にすることはできません(これは実際に探しているものです)

ただし、これはすべてSQLCLRで実行できます(まあ、動的部分ではありませんが、ここでは重点を置いていないようです)。 SQLCLRを使用して、一時テーブルにアクセスできる関数を作成できます。また、関数を集約関数にすることもできます。もちろん、SUMAVGなどの単純な計算では、コードの重複を減らすよりもパフォーマンスが低下する可能性がありますが、それはテストの問題です(そのため、 "場合によります")。

この特定のケースでは、行ごとの値が自然にユーザー定義集計に送信されるため、一時テーブルへのアクセスが必要であるように見えません。 dbo.DynamicCalcにDynamicCalc(@CalcType TINYINT, @Amount FLOAT)のシグネチャがあると仮定します:

_select A, B, C, CalcField = dbo.DynamicCalc(2, t.Amount) 
from #MyTempTable t 
join AnotherTable a
    on t.Field1 = a.Field1
group by A, B, C;
_

または:

_select A, B, C, CalcField = dbo.DynamicCalc(3, IIF(t.Type = 1, t.Factor, 0)) 
from #MyTempTable t 
join AnotherTable a
    on t.Field1 = a.Field1
group by A, B, C;
_

または、_t.Factor_を_@Amount_パラメータとして単独で渡して、_@Type INT_に追加のパラメータを追加し、_t.Type_を受け取ることで、_@CalcType_の場合にのみ使用されます。 = 3。

繰り返しますが、このアプローチを採用するかどうかは、使用する数式に大きく依存する実用性の問題です。 @Martinが_CASE @calcType_ステートメントを実行して数式を切り替えるという提案は、数式が十分に単純な場合(質問に示されているコードに基づいているように見えるため)より効率的です。ただし、これらの数式が非常に複雑になる場合、または一時テーブルへのアクセスが本当に必要な場合は、これを検討するオプションです。

4
Solomon Rutzky