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.
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 MyTablewhere key = xxxexec (@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) |
 |
|
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? |
 |
|
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 OptimizerTG |
 |
|
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. |
 |
|
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 #TMPfrom(Select 'a+b+c'as formula,12 as a,2 as b,3 as c Union allSelect 'a+b-c',12,2,3) aDeclare @StrSql varchar(100),@MyCount intset @MyCount = 1while @Mycount <= (select count(*) From #TMP)begin select @StrSql = 'Select ' + MyMath from #TMP where RowID = @MyCount exec(@StrSql) set @MyCount = @MyCount + 1ENddrop table #TMP Success is 10% Intelligence, 70% Determination, and 22% Stupidity.\_/ _/ _/\_/ _/\_/ _/ _/- 881 |
 |
|
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. :( |
 |
|
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 #TMPfrom(Select 'a+b+c'as formula,12 as a,2 as b,3 as c Union allSelect 'a+b-c',12,2,3) aDeclare @StrSql varchar(100),@MyCount intset @MyCount = 1Create 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 = @MyCountprint @StrSQL Insert Into #Results(RowID,Result) exec(@StrSql) set @MyCount = @MyCount + 1ENdSelect * from#TMP ainner Join#Results bon a.RowID =b.RowIDdrop table #TMPDrop table #Results Success is 10% Intelligence, 70% Determination, and 22% Stupidity.\_/ _/ _/\_/ _/\_/ _/ _/- 881 |
 |
|
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.AmountFROM Customer C INNER JOIN (EXEC(@SelectStatement)) T ON C.AcctNo = T.AcctNoVery interesting! Please show me other examples of this technique. Thank you so much for this great info! :) |
 |
|
mtl777
Yak Posting Veteran
63 Posts |
Posted - 2009-08-10 : 16:49:41
|
Bump. :) |
 |
|
X002548
Not Just a Number
15586 Posts |
|
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 |
 |
|
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 OptimizerTG |
 |
|
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. :) |
 |
|
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 OptimizerTG |
 |
|
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... |
 |
|
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 OptimizerTG |
 |
|
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 1736The ROLLBACK TRANSACTION request has no corresponding BEGIN TRANSACTION |
 |
|
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 IMPLEMENT1. 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 Allselect 2,'Subtraction' Union Allselect 3,'Multiply'Union Allselect 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 Allselect 1,2,80.5,1 Union Allselect 1,3,97.782,1Union Allselect 2,1,12345.55,2 Union Allselect 2,2,12345.55,2 Union Allselect 2,3,12445.55,2 Union Allselect 2,4,155.55,2 Union Allselect 2,5,123495.55,2Declare @MyFormula table (FormulaID int,SequenceID int,OperationID int)Insert INto @MyFormula(FormulaID,SequenceID,OperationID)select 1,1,1 Union Allselect 1,2,3 Union Allselect 1,3,4 Union Allselect 2,1,1 Union Allselect 2,2,1 Union Allselect 2,3,1 Union Allselect 2,4,1 Union Allselect 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.MyValFrom@MyValues aInner Join@MyFormula bon a.FormulaID = b.FormulaIDand a.SequenceID = b.SequenceIDInner Join@Operations con b.OperationID = c.OperationIDorder by a.ValID,a.SequenceIDDeclare @Anchor int, @MyNewVal decimal(30,5), @PriorvalID intset @Anchor = 0 set @MyNewVal = 0 set @PriorValID = 0Update aset @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.ValIDfrom @MyWorkingTable aSelect *from @MyWorkingTable aSelect valID,NewValue as FinalValuefrom(Select row_Number() over (Partition by a.ValID order by sequenceID desc) as RowID,*from @MyWorkingTable a) aawhere aa.RowID = 1 Success is 10% Intelligence, 70% Determination, and 22% Stupidity.\_/ _/ _/\_/ _/\_/ _/ _/- 881 |
 |
|
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 OptimizerTG |
 |
|
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 OptimizerTG
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 1736The ROLLBACK TRANSACTION request has no corresponding BEGIN TRANSACTION |
 |
|
Next Page
|
|
|
|
|