简体   繁体   English

存储过程中的动态SQL计算

[英]Dynamic SQL Calculations in Stored Procedure

This is tricky one. 这是一个棘手的问题。 I'm writing my first SQL Server stored procedure that will require dynamic SQL and I'm running into a bit of a problem. 我正在编写第一个需要动态SQL的SQL Server存储过程,但遇到了一些问题。

Here is the scenario: I am trying to allow the user to build equations in my application and then execute them in SQL Server based on the data stored therein. 这里是这种情况:我试图允许用户在我的应用程序中建立方程式,然后根据其中存储的数据在SQL Server中执行它们。

Example: 例:

x * (y + 10)

I have a series of items, and each item can have a value entered for it for any subdivision of any week. 我有一系列项目,每个项目中每个星期的任何细分都可以输入一个值。 A simplified version of that table would look like: 该表的简化版本如下所示:

Item | WeekEndingDate | Subdivision | Value
-------------------------------------------
1    | 13-Aug-15      | 4           | 100

It is like this because values need to be entered more often than each day every week. 之所以这样,是因为与每周的每天相比,输入值的频率更高。

I also have a subdivisions table that breaks out each part of the week. 我还有一个细分表,该表细分了一周的每个部分。

Simplified it looks like this: 简化后看起来像这样:

Subdivision | Name
----------------------------
1           | Monday Morning

There is also a third table (which I don't think I need to go into), which holds the different steps for each equation that the user has created. 还有第三张表(我认为不需要进入该表),其中包含用户创建的每个方程式的不同步骤。

What I'm trying to do is have the user supply a "WeekEndingDate", then perform a given user-defined equation on each "Value" for each "Subdivision" of that week. 我想做的是让用户提供一个“ WeekEndingDate”,然后针对该周的每个“细分”在每个“值”上执行给定的用户定义方程式。

Here is what I tried: 这是我尝试过的:

I put the calculations into the form of a T-SQL UDF wherein I use a cursor to loop through the steps of the equation and build a dynamic SQL string, which I could then execute for the result and return the result from the function. 我将计算结果放入T-SQL UDF的形式,其中使用光标在方程式的步骤中循环并构建动态SQL字符串,然后可以对其执行结果并从函数中返回结果。

I then tried to use it in a query like the one below: 然后,我尝试在如下查询中使用它:

SELECT dbo.DoCalculations(@Item, @WeekEnding, Subdivision), Subdivision, etc...
FROM Subdivisions

The problem is that, as I have found out, I can't execute dynamic SQL in an UDF. 问题是,正如我发现的那样,我无法在UDF中执行动态SQL。 This prevents me from doing the calculation in dbo.DoCalculations and ruins the whole thing. 这使我无法在dbo.DoCalculations进行计算,并破坏了整个过程。

Is there any other way I can get the desired result? 我还有其他方法可以得到理想的结果吗?

EDIT: 编辑:

Here is more data and samples of what I'm doing. 这是我正在做的更多数据和示例。

The calculations table looks like this: 计算表如下所示:

ID | Sequence | UseVariable | ItemVariable | Constant | Operator | ParenLevel
------------------------------------------------------------------------------
1  | 1        | True        | 1            | NULL     | 1        | 0

To explain: 解释:

  • 'Sequence' shows the order of the steps in the equation. “顺序”显示方程式中步骤的顺序。
  • 'UseVariable' tells us whether this step of the calculation uses an items table value as a variable, or whether it uses a constant value. “ UseVariable”告诉我们计算的这一步骤是使用项目表值作为变量还是使用常量值。
  • Depending on the value of 'UseVariable' either 'ItemVariable' or 'Constant' will have a value, the other will be null. 根据“ UseVariable”的值,“ ItemVariable”或“ Constant”将具有一个值,另一个将为null。
  • 'ItemVariable' Is a foreign key reference to the list of items which then links to the values for that item. “ ItemVariable”是对项目列表的外键引用,然后链接到该项目的值。
  • 'Operator' stores a tinyint where each value from 1 to 4 represent a numeric operator (+, -, *, /). “运算符”存储一个tinyint,其中从1到4的每个值代表一个数字运算符(+,-,*,/)。
  • 'ParenLevel' is used to enclose equation steps in '(' and ')' by comparing it to the 'ParenLevel' of the previous step in the equation. 通过将“ ParenLevel”与等式中上一步的“ ParenLevel”进行比较,可将“ ParenLevel”括在“(”和“)”中。

This is my calculation function: 这是我的计算功能:

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

You don't really NEED dynamic SQL for building the formula I believe you only need it to execute it. 您实际上并不需要动态SQL来构建公式,我相信您只需执行它即可。 And since you can't really use EXEC or sp_executesql within a function, that portion needs to remain separate. 而且由于您不能真正在函数内使用EXEC或sp_executesql,因此该部分需要保持独立。 This is kind of a crude example, but you can probably accomplish this with a Table-Valued Parameter. 这是一个粗糙的示例,但是您可以使用表值参数来完成此操作。 If we knew a little bit more about the structure and perhaps some additional data samples you might be able to avoid Dynamic SQL all together; 如果我们对结构有更多了解,也许对一些其他数据样本了解更多,那么您也许可以完全避免使用Dynamic 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)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM