这很棘手。我正在编写第一个需要动态SQL的SQL Server存储过程,而且我遇到了一些问题。
以下是场景:我试图允许用户在我的应用程序中构建方程式,然后根据存储在其中的数据在SQL Server中执行它们。
示例:
x * (y + 10)
我有一系列项目,每个项目都可以为任何一周的任何细分输入一个值。该表的简化版本如下所示:
Item | WeekEndingDate | Subdivision | Value
-------------------------------------------
1 | 13-Aug-15 | 4 | 100
就是这样,因为价值需要每周输入的频率高于每一天。
我还有一个细分表,可以打破本周的每个部分。
简化它看起来像这样:
Subdivision | Name
----------------------------
1 | Monday Morning
还有第三个表格(我不认为我需要进入),其中包含用户创建的每个等式的不同步骤。
我尝试做的是让用户提供" WeekEndingDate"然后在每个"值"上执行给定的用户定义等式。对于每个"细分"那一周。
以下是我的尝试:
我将计算放入T-SQL UDF的形式,其中我使用游标循环遍历等式的步骤并构建一个动态SQL字符串,然后我可以为结果执行并返回结果功能
然后我尝试在如下所示的查询中使用它:
SELECT dbo.DoCalculations(@Item, @WeekEnding, Subdivision), Subdivision, etc...
FROM Subdivisions
问题在于,正如我所知,我无法在UDF中执行动态SQL。这使我无法在dbo.DoCalculations
中进行计算并破坏整个事物。
有没有其他方法可以获得理想的结果?
以下是我正在做的更多数据和样本。
计算表如下所示:
ID | Sequence | UseVariable | ItemVariable | Constant | Operator | ParenLevel
------------------------------------------------------------------------------
1 | 1 | True | 1 | NULL | 1 | 0
解释:
这是我的计算功能:
FUNCTION [dbo].[DoCalculations]
(
-- Add the parameters for the function here
@ItemID nvarchar(MAX),
@Subdivision nvarchar(MAX),
@WeekEnding date
)
RETURNS decimal(18, 5)
AS
BEGIN
--Return variable
DECLARE @R decimal(18, 5)
--Variables for cursor use
DECLARE @UseVariable bit
DECLARE @ItemVar nvarchar(MAX)
DECLARE @Constant decimal(18, 5)
DECLARE @Operator tinyint
DECLARE @ParenLevel tinyint
--Working variables
DECLARE @CalcSQL varchar(MAX) = 'SELECT @R = (' --Note I'm leaving one open paren and that I am selecting the result into '@R'
DECLARE @CurrentParenLevel tinyint
DECLARE @StepTerm nvarchar(MAX)
--Create the cursor to loop through the calculation steps
DECLARE CalcCursor CURSOR FAST_FORWARD FOR
SELECT UseVariable, ItemVariable, Constant, Operator, ParenLevel
FROM CalculationSteps
WHERE CalculationSteps.Item = @ItemID
ORDER BY Sequence
--Start looping
OPEN CalcCursor
FETCH NEXT FROM CalcCursor INTO @UseVariable, @ItemVar, @Constant, @Operator, @ParenLevel
WHILE @@FETCH_STATUS = 0
BEGIN
--Check if wee need to add opening parens to the equation
IF @ParenLevel > @CurrentParenLevel
BEGIN
WHILE (@CurrentParenLevel <> @ParenLevel)
BEGIN
SET @CalcSQL = @CalcSQL + '('
SET @CurrentParenLevel = @CurrentParenLevel + 1
END
END
--Check if this step is using a variable or a constant
IF @UseVariable = 'True'
BEGIN
--If it's using a variable, create the sub-query string to get its value
SET @StepTerm = '(SELECT ReportValue FROM Reports WHERE Slot = @Slot AND WeekEnding = @WeekEnding AND Stat = ' + @ItemVar + ')'
END
ELSE
BEGIN
--If its's using a constant, append its value
SET @StepTerm = '(' + @Constant + ')'
END
--Add the step to the equation
SET @CalcSQL = @CalcSQL + @StepTerm
--Check if wee need to add closing parens to the equation
IF @ParenLevel < @CurrentParenLevel
BEGIN
WHILE (@CurrentParenLevel <> @ParenLevel)
BEGIN
SET @CalcSQL = @CalcSQL + ')'
SET @CurrentParenLevel = @CurrentParenLevel - 1
END
END
--Add the operator between this step and the next, if any
SET @CalcSQL = @CalcSQL + (CASE @Operator WHEN 0 THEN '' WHEN 1 THEN '+' WHEN 2 THEN '-' WHEN 3 THEN '*' WHEN 4 THEN '/' END)
--Go to the next step
FETCH NEXT FROM CalcCursor INTO @UseVariable, @ItemVar, @Constant, @Operator, @ParenLevel
END
CLOSE CalcCursor
DEALLOCATE CalcCursor
--Close any open parens in the equation
WHILE (@CurrentParenLevel > 0)
BEGIN
SET @CalcSQL = @CalcSQL + ')'
SET @CurrentParenLevel = @CurrentParenLevel - 1
END
--Close the original open paren to enclose the whole equation
SET @CalcSQL = @CalcSQL + ')'
--Execute the equation which should set the result to '@R'
Exec @CalcSQL
--Return '@R'
RETURN @R
答案 0 :(得分:1)
你不需要动态SQL来构建公式我相信你只需要它来执行它。由于您无法在函数中使用EXEC或sp_executesql,因此该部分需要保持独立。这是一个粗略的例子,但您可以使用表值参数来完成此操作。如果我们对结构有了更多的了解,也许还有一些额外的数据样本,你可以一起避免动态SQL;
CREATE TYPE CalcVariables as TABLE
(
VariableIndex int IDENTITY(1,1),
VariableName varchar(255),
VariableValue varchar(255)
)
CREATE FUNCTION dbo.Dyn_Calc
(
@CalcFormula varchar(255),
@CaclVars CalcVariables ReadOnly
)
RETURNS varchar(255)
BEGIN
DECLARE @Calculation varchar(255) = @CalcFormula
DECLARE @Index int
DECLARE @iName varchar(255)
DECLARE @iValue varchar(255)
SET @Index = (SELECT MAX(VariableIndex) FROM @CaclVars)
WHILE @Index > 0
BEGIN
SET @iName = (SELECT VariableName FROM @CaclVars WHERE VariableIndex = @Index)
SET @iValue = (SELECT VariableValue FROM @CaclVars WHERE VariableIndex = @Index)
SET @Calculation = REPLACE(@Calculation,@iName,@iValue)
SET @Index = @Index -1
END
RETURN @Calculation
END
DECLARE @CalcFormula varchar(255),
@CaclVars CalcVariables,
@SQL nvarchar(3000)
SET @CalcFormula = '[x] * ([y] + 10)'
INSERT INTO @CaclVars
VALUES ('[x]','10'),
('[y]','5')
SET @SQL = 'SELECT ' + (SELECT dbo.Dyn_Calc (@CalcFormula, @CaclVars))
SELECT @SQL
EXEC(@SQL)