Please start any new threads on our new site at https://forums.sqlteam.com. We've got lots of great SQL Server experts to answer whatever question you can come up with.

 All Forums
 SQL Server 2005 Forums
 Transact-SQL (2005)
 Formula calculations

Author  Topic 

mtl777
Yak Posting Veteran

63 Posts

Posted - 2009-08-06 : 16:47:17
Hey guys, I have an interesting problem. Suppose you have a table with the columns A, B, C, and Formula. A, B, and C are numeric and Formula is a VARCHAR that contains a formula such as A+B+C, A*B-C, (A+B)/C, etc. I would like to make a query that returns a list of A, B, C, Formula, and the Result of the formula applied on the given values of A, B, and C. What would be a good way to do this?

Thanks!

Bustaz Kool
Master Smack Fu Yak Hacker

1834 Posts

Posted - 2009-08-06 : 20:04:20
You are probably looking to use Dynamic SQL to implement the formula. This is considered by many to be an inherently bad idea.

This involves substituting the A,B,and C column into your formula, using the REPLACE function, and then executing the SQL statement you just created:

declare @sqlstmt nvarchar(500)

select @sqlStmt = Replace(Replace(Replace(Formula, 'A', A), 'B', B), 'C', C)
from MyTable
where key = xxx

exec (@sqlStmt)

I'll leave it to you to figure out how to do this for every record in the table and return the results.





=======================================
Men build too many walls and not enough bridges. -Isaac Newton, philosopher and mathematician (1642-1727)
Go to Top of Page

mtl777
Yak Posting Veteran

63 Posts

Posted - 2009-08-07 : 12:57:49
I thought of doing something like SELECT A, B, C, Formula, dbo.Calculate(Formula, A, B, C), where Calculate is a UDF that would use EXEC to calculate the formula given the values of A, B, and C. The problem is, SQL Server does not allow the use of EXEC from within a UDF! :( I mean, come on, how lame is that? Because of this, in my limited knowledge the only way I can see how to solve this problem is by using a cursor in a stored procedure (you know, calculate each row, insert each result into a temp table, etc.) but that would be way too slow. Any other ideas?
Go to Top of Page

TG
Master Smack Fu Yak Hacker

6065 Posts

Posted - 2009-08-07 : 15:40:00
The best way to do this may depend...Will each set of numbers (each row) have a unique formula or will many many rows of data all have the same formula applied to it? So how many different forumulas are you expecting? Another factor is how complex are the actual formulas? Will there be all kinds of advanced math applied or is it more like your examples? Can you post a representative sample of what the formulas actually are?

Be One with the Optimizer
TG
Go to Top of Page

mtl777
Yak Posting Veteran

63 Posts

Posted - 2009-08-07 : 15:57:43
The formulas are unpredictable and are rarely, if ever, duplicated. The only thing you know for sure is that they are simple math involving only addition, subtraction, multiplication and division. The examples I gave were representative of them.
Go to Top of Page

Vinnie881
Master Smack Fu Yak Hacker

1231 Posts

Posted - 2009-08-07 : 16:25:28
You can do something like this. It's not blazing fast, but if it's for a one time update it should be sufficient.

Select Row_Number() over (order by (select 0)) as rowID,replace(replace(replace(Formula,'a',a),'b',b),'c',c) as MyMath,*
into #TMP
from
(
Select 'a+b+c'as formula,12 as a,2 as b,3 as c Union all
Select 'a+b-c',12,2,3
) a


Declare @StrSql varchar(100),@MyCount int
set @MyCount = 1

while @Mycount <= (select count(*) From #TMP)
begin
select @StrSql = 'Select ' + MyMath from #TMP where RowID = @MyCount
exec(@StrSql)
set @MyCount = @MyCount + 1
ENd
drop table #TMP



Success is 10% Intelligence, 70% Determination, and 22% Stupidity.
\_/ _/ _/\_/ _/\_/ _/ _/- 881
Go to Top of Page

mtl777
Yak Posting Veteran

63 Posts

Posted - 2009-08-07 : 19:32:50
Thanks! :)

That code will display the result for each row but what if I want to store the result in a variable or update a column with it? Is there a way to make EXEC return a value? I wish I could do something like SET @Result = EXEC(@StrSql) but I can't. :(
Go to Top of Page

Vinnie881
Master Smack Fu Yak Hacker

1231 Posts

Posted - 2009-08-07 : 19:55:31
You can do something like this (This is just a rough sample, you might want to clean the code a little.


Select Row_Number() over (order by (select 0)) as rowID,replace(replace(replace(Formula,'a',a),'b',b),'c',c) as MyMath,*
into #TMP
from
(
Select 'a+b+c'as formula,12 as a,2 as b,3 as c Union all
Select 'a+b-c',12,2,3
) a


Declare @StrSql varchar(100),@MyCount int
set @MyCount = 1
Create table #Results(RowID int,Result decimal(12,2))
while @Mycount <= (select count(*) From #TMP)
begin
select @StrSql = 'Select ' + Convert(varchar(10),@MyCount) + ', ' + MyMath from #TMP where RowID = @MyCount
print @StrSQL
Insert Into #Results(RowID,Result)
exec(@StrSql)
set @MyCount = @MyCount + 1
ENd

Select * from
#TMP a
inner Join
#Results b
on a.RowID =b.RowID

drop table #TMP
Drop table #Results



Success is 10% Intelligence, 70% Determination, and 22% Stupidity.
\_/ _/ _/\_/ _/\_/ _/ _/- 881
Go to Top of Page

mtl777
Yak Posting Veteran

63 Posts

Posted - 2009-08-08 : 00:48:52
So it seems like EXEC can be used as an extension to a command. In this case it's like putting a SELECT statement after the INSERT, with the SELECT statement being "fetched" from a variable! Nice! I'm unable to test this yet as I'm at home now, but I'll check this out at work come Monday.

Does this work with any command that could involve a SELECT statement or even a sub-query? For example:

UPDATE C SET C.Balance = C.Balance + T.Amount
FROM Customer C INNER JOIN (EXEC(@SelectStatement)) T ON C.AcctNo = T.AcctNo

Very interesting! Please show me other examples of this technique. Thank you so much for this great info! :)
Go to Top of Page

mtl777
Yak Posting Veteran

63 Posts

Posted - 2009-08-10 : 16:49:41
Bump. :)
Go to Top of Page

X002548
Not Just a Number

15586 Posts

Posted - 2009-08-10 : 17:06:28
post table ddl, sample data and expected results



Brett

8-)

Hint: Want your questions answered fast? Follow the direction in this link
http://weblogs.sqlteam.com/brettk/archive/2005/05/25/5276.aspx

Add yourself!
http://www.frappr.com/sqlteam



Go to Top of Page

Vinnie881
Master Smack Fu Yak Hacker

1231 Posts

Posted - 2009-08-10 : 17:09:55
Exec can not be used as a extension of a command with the exception of a into statement to the best of my knowledge.

Insert MYTable...
Exec (select ...)

You can not use it in the method you illustrated. You would need to enclose the entire query into the exec to use your variable as you illustrated:

EXEC ('UPDATE C SET C.Balnace... From Customer c inner join (' + @SelectStatement + ') T on c.AcctNo = T.AccNo')



Success is 10% Intelligence, 70% Determination, and 22% Stupidity.
\_/ _/ _/\_/ _/\_/ _/ _/- 881
Go to Top of Page

TG
Master Smack Fu Yak Hacker

6065 Posts

Posted - 2009-08-10 : 17:20:57
The "technique" you are referring to is called Dynamic Sql. Dynamic sql is any valid sql statement prepared as a string. The string is then executed using: exec(<'string or string variable'>) OR sp_executeSql.

You can not use it in the context you asked about. The only statement it can be combined with (off the top of my head) is an Insert statement. dynamic sql has it's place as a tool but using it can result in performance and security issues.

Be One with the Optimizer
TG
Go to Top of Page

mtl777
Yak Posting Veteran

63 Posts

Posted - 2009-08-11 : 14:59:17
Thanks Vinnie and everyone! I appreciate your help and have learned a lot from you. :)
Go to Top of Page

TG
Master Smack Fu Yak Hacker

6065 Posts

Posted - 2009-08-11 : 15:25:25
Please follow up with your solution if you come up with anything cool - I'd be curious.

I have a very involved process that calculates Financial ratios for publicly traded companies. There are hundreds of ratio calculations, some of which are pretty intense. There is an entire database model for storing a relational interpretation of the formulas which are applied to financial data for all companies, all periods, and all durations at once. It is all set-based with no dynamic sql and the only iterations are to honor the calculations' sequence. It is a little tedious to maintain the formulas since we aren't storing them human readable math expressions but it calculates a heck of a lot of ratios very quickly.

I wouldn't recommend the solution unless you have a whole lot of time for development :) Also this solution is designed to apply the same formula to millions of rows of data where as it sounds like you have just one row per formula.

Be One with the Optimizer
TG
Go to Top of Page

waterduck
Aged Yak Warrior

982 Posts

Posted - 2009-08-12 : 03:19:55
@.@ very curious to TG solution


Hope can help...but advise to wait pros with confirmation...
Go to Top of Page

TG
Master Smack Fu Yak Hacker

6065 Posts

Posted - 2009-08-12 : 11:03:23
quote:
Originally posted by waterduck

@.@ very curious to TG solution


Hope can help...but advise to wait pros with confirmation...



Well, the basic idea is that any formula can be defined by a sequence of 1 or more calculations. Each calculation would have a single operator such as add, subtract, multiply, devide, sum, coalesce, avg, power, etc). Each calculation can have 1, 2, or many operands. The operands could be a reference to a data value (from a company's financials) or a reference to the result of a previous calculation, or a constant. These concepts have been modeled in a database. Most of the formulas can be performed with 1 or 2 sequenced calculations. I think the formula with the most calculation steps is still under 10.

The stored procedure that actually produces the results iterates through the sequence steps so the all ratios are complete with less than 10 iterations. A series of statements performs the calcs for the current sequence. One statement may handle all calcs with 2 operands and a CASE statement to use the appropriate operator. Another statement may perform all the aggregates, etc.

So that is it in a nutshell.


Be One with the Optimizer
TG
Go to Top of Page

Transact Charlie
Master Smack Fu Yak Hacker

3451 Posts

Posted - 2009-08-12 : 11:40:37
Sounds like you *might* be able to do that in a recursive CTE...... (evaluating expressions from the inside out)

Care to publish a little example or is it a trade secret / too much effort?


Charlie
===============================================================
Msg 3903, Level 16, State 1, Line 1736
The ROLLBACK TRANSACTION request has no corresponding BEGIN TRANSACTION
Go to Top of Page

Vinnie881
Master Smack Fu Yak Hacker

1231 Posts

Posted - 2009-08-12 : 12:32:30
Here's a method that I would use if I was creating from scratch that would be very fast in calculations, and fully expandable.

--NOTES: ON HOW TO IMPLEMENT
1. From your front end you would need to first select a formula. The available formulas would be any formula with the same amount of sequences as individual values you have that need to be factored in(You can make as many new/custom formulas as you would likewith as many differant steps as illustrated in the formula field.)

2. Once the values are associated with the formula the computer will do the rest very quick using a clustered update.

Important: Make sure you factor in order of operations on the front end to ensure the sequence is corrent.


Declare @Operations table (OperationID int, Description varchar(20))
Insert INto @Operations(OperationID,Description)
select 1,'Addition' Union All
select 2,'Subtraction' Union All
select 3,'Multiply'Union All
select 4,'Divide'


Declare @MyValues table(ValID int,SequenceID int, MyVal Decimal(30,5),FormulaID int)
Insert INto @MyValues(ValID,SequenceID,MyVal,FormulaID )
select 1,1,22.5,1 Union All
select 1,2,80.5,1 Union All
select 1,3,97.782,1Union All
select 2,1,12345.55,2 Union All
select 2,2,12345.55,2 Union All
select 2,3,12445.55,2 Union All
select 2,4,155.55,2 Union All
select 2,5,123495.55,2


Declare @MyFormula table (FormulaID int,SequenceID int,OperationID int)
Insert INto @MyFormula(FormulaID,SequenceID,OperationID)
select 1,1,1 Union All
select 1,2,3 Union All
select 1,3,4 Union All
select 2,1,1 Union All
select 2,2,1 Union All
select 2,3,1 Union All
select 2,4,1 Union All
select 2,5,4

Declare @MYWORKINGTABLE table (WorkTableID int Identity(1,1) Primary Key Clustered,ValID int,SequenceID int,OperationID int,Description varchar(50),MyVal decimal(30,5),NewValue decimal(30,5))
Insert Into @MyWorkingTable(ValID,SequenceID,OperationID,Description,MyVal)
SElect a.ValID,a.SequenceID,c.OperationID,c.Description,a.MyVal
From
@MyValues a
Inner Join
@MyFormula b
on a.FormulaID = b.FormulaID
and a.SequenceID = b.SequenceID
Inner Join
@Operations c
on b.OperationID = c.OperationID
order by a.ValID,a.SequenceID

Declare @Anchor int, @MyNewVal decimal(30,5), @PriorvalID int
set @Anchor = 0
set @MyNewVal = 0
set @PriorValID = 0
Update a
set @Anchor = WorktableiD
,@MyNewVal = a.NewValue = case
when @PriorValID = ValID and OperationID = 1 then convert(decimal(30,5),@MyNewVal + a.Myval)
when @PriorValID = ValID and OperationID = 2 then convert(decimal(30,5),@MyNewVal - a.Myval)
when @PriorValID = ValID and OperationID = 3 then convert(decimal(30,5),@MyNewVal * a.Myval)
when @PriorValID = ValID and OperationID = 4 then convert(decimal(30,5),@MyNewVal / a.Myval)
else a.MyVal
end
,@PriorValID = a.ValID
from @MyWorkingTable a

Select *
from @MyWorkingTable a

Select valID,NewValue as FinalValue
from
(Select row_Number() over (Partition by a.ValID order by sequenceID desc) as RowID,*
from @MyWorkingTable a
) aa
where aa.RowID = 1




Success is 10% Intelligence, 70% Determination, and 22% Stupidity.
\_/ _/ _/\_/ _/\_/ _/ _/- 881
Go to Top of Page

TG
Master Smack Fu Yak Hacker

6065 Posts

Posted - 2009-08-12 : 13:16:42
quote:
Originally posted by Transact Charlie

Sounds like you *might* be able to do that in a recursive CTE...... (evaluating expressions from the inside out)

Care to publish a little example or is it a trade secret / too much effort?

Charlie


probably could be done with cte but at this point I don't think it would buy me anything to switch other than about two weeks of regression testing.

Regarding the example, I looked for (but couldn't find) my little "proof of concept" testing code. I think its long gone. To post a working sample would be too much effort. The current code requires about 30 dependent opbjects and forumla data as well as the value data tables from too many sources (across multiple databases). It may violate some trade secrets but probably not - because the value is not so much the technology but rather all the company data.

Be One with the Optimizer
TG
Go to Top of Page

Transact Charlie
Master Smack Fu Yak Hacker

3451 Posts

Posted - 2009-08-13 : 03:45:57
quote:
Originally posted by TG

quote:
Originally posted by Transact Charlie

Sounds like you *might* be able to do that in a recursive CTE...... (evaluating expressions from the inside out)

Care to publish a little example or is it a trade secret / too much effort?

Charlie


probably could be done with cte but at this point I don't think it would buy me anything to switch other than about two weeks of regression testing.

Regarding the example, I looked for (but couldn't find) my little "proof of concept" testing code. I think its long gone. To post a working sample would be too much effort. The current code requires about 30 dependent opbjects and forumla data as well as the value data tables from too many sources (across multiple databases). It may violate some trade secrets but probably not - because the value is not so much the technology but rather all the company data.

Be One with the Optimizer
TG


Yeah -- definitely not worth the development / testing time and effort if you have a good solution anyway. It just sounded like it might be some fun making a CTE from that.


Charlie
===============================================================
Msg 3903, Level 16, State 1, Line 1736
The ROLLBACK TRANSACTION request has no corresponding BEGIN TRANSACTION
Go to Top of Page
    Next Page

- Advertisement -