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.
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.
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.
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.
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. This prevents me from doing the calculation in dbo.DoCalculations
and ruins the whole thing.
Is there any other way I can get the desired result?
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:
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. And since you can't really use EXEC or sp_executesql within a function, that portion needs to remain separate. 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;
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)
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.