【问题标题】:Dynamic SQL Calculations in Stored Procedure存储过程中的动态 SQL 计算
【发布时间】:2015-08-13 18:57:04
【问题描述】:

这是一个棘手的问题。我正在编写我的第一个需要动态 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

解释一下:

  • 'Sequence' 显示方程式中步骤的顺序。
  • 'UseVariable' 告诉我们这一步计算是否使用 将项目表值作为变量,或者是否使用常量值。
  • 取决于“UseVariable”的值,“ItemVariable”或 'Constant' 将有一个值,另一个将为 null。
  • 'ItemVariable' 是对项目列表的外键引用,然后链接到该项目的值。
  • 'Operator' 存储一个 tinyint,其中从 1 到 4 的每个值代表一个 数字运算符(+、-、*、/)。
  • 'ParenLevel' 用于将方程步骤包含在 '(' 和 ')' 中 将其与上一步的“ParenLevel”进行比较 等式。

这是我的计算函数:

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

【问题讨论】:

  • 你不能把计算放到一个存储过程中吗?让该过程调用函数以创建动态 SQL 语句(但不执行它),然后在存储过程的上下文中执行它

标签: sql sql-server stored-procedures dynamic-sql


【解决方案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)

【讨论】:

  • 我不确定这是否适合我的需要。我在原始帖子中添加了更多数据,因此希望您能够更好地了解我在做什么。
猜你喜欢
  • 2015-04-29
  • 1970-01-01
  • 1970-01-01
  • 2011-02-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-07-29
相关资源
最近更新 更多