存储过程中的动态SQL计算

时间:2015-08-13 18:57:04

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

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

解释:

  • '序列'显示等式中步骤的顺序。
  • ' UseVariable'告诉我们这个计算步骤是否使用 项目表值作为变量,或者是否使用常量值。
  • 取决于' UseVariable'的价值。要么' ItemVariable'要么 '恒'将有一个值,另一个将为null。
  • ' ItemVariable'是对项目列表的外键引用,然后链接到该项目的值。
  • '操作'存储一个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

1 个答案:

答案 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)