ストアドプロシージャがあります。
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
明らかにここの構文は機能しませんが、私が望むものを達成するものはありますか?
いいえ、これは不可能です。
_#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の領域に入る必要があります。
厳密に言えば(T-SQLサブルーチン):いいえ。
技術的に言えば(一度定義される数式を抽象化する手段):はい。
実用的に言えば、それは:)に依存します。
T-SQL関数の制限に関して現在妨げとなっている問題を次に示します。
ただし、これはすべてSQLCLRで実行できます(まあ、動的部分ではありませんが、ここでは重点を置いていないようです)。 SQLCLRを使用して、一時テーブルにアクセスできる関数を作成できます。また、関数を集約関数にすることもできます。もちろん、SUM
やAVG
などの単純な計算では、コードの重複を減らすよりもパフォーマンスが低下する可能性がありますが、それはテストの問題です(そのため、 "場合によります")。
この特定のケースでは、行ごとの値が自然にユーザー定義集計に送信されるため、一時テーブルへのアクセスが必要であるように見えません。 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
_ステートメントを実行して数式を切り替えるという提案は、数式が十分に単純な場合(質問に示されているコードに基づいているように見えるため)より効率的です。ただし、これらの数式が非常に複雑になる場合、または一時テーブルへのアクセスが本当に必要な場合は、これを検討するオプションです。