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 |
|
nathans
Aged Yak Warrior
938 Posts |
Posted - 2006-07-19 : 13:01:02
|
Guys,Been working on this gem in SQL2005 but have not come up with a clean solution just yet. My first crack uses Cross Apply, but Im open to SQL2000 solutions as well. @Process is a collection of elements (a tabled array). @ProcessStage is a subset of these elements.ProcessValue is unique.I want the sum(ProcessValue) for each possible combination of the element subset. declare @Process table (ProcessID int, ProcessValue int)insert into @Process select 1, 1 union select 2, 2 union select 3, 4 union select 4, 8 union select 5, 16declare @ProcessStage table (ProcessID int)insert into @ProcessStage select 1 union select 3 union select 4select d.ProcessValueSumfrom @Process dpCROSS APPLY ( select sum(ddp.ProcessValue) ProcessValueSum from @Process ddp join @ProcessStage ps on ddp.ProcessID = ps.ProcessID where ddp.ProcessValue <> dp.ProcessValue ) dunionselect d.ProcessValueSumfrom @Process dpCROSS APPLY ( select sum(ddp.ProcessValue) ProcessValueSum from @Process ddp join @ProcessStage ps on ddp.ProcessID = ps.ProcessID where ddp.ProcessValue = dp.ProcessValue ) dwhere ProcessValueSum is not nullorder by 1-- Desired resultset:--1 -- sum(1)--4 -- sum(4)--5 -- sum(1+4)--8 -- sum(8)--9 -- sum(1+8)--12 -- sum(8+4)--13 -- sum(1+4+8) Nathan Skerl |
|
|
SwePeso
Patron Saint of Lost Yaks
30421 Posts |
Posted - 2006-07-19 : 14:09:41
|
I think this code will do the trick for any combinations you can come up with!As long as there are less than 64 matches between Process table and ProcessStage table.  -- Prepare test data.declare @Process table (ProcessID int, ProcessValue int)insert into @Process select 1, 1 union select 2, 2 union select 3, 4 union select 4, 8 union select 5, 16declare @ProcessStage table (ProcessID int)insert into @ProcessStage select 1 union select 3 union select 4-- The work start here. Stage the work data for faster processing.DECLARE @stage TABLE (ID INT IDENTITY(0, 1), bin BIGINT, Value INT)INSERT @stage (Value)SELECT p.ProcessValueFROM @Process pINNER JOIN @ProcessStage ps ON ps.ProcessID = p.ProcessIDupdate @stageset bin = power(2, id)-- Do the magic!declare @output table (ProcessValueSum bigint)declare @sum bigint, @mem bigint, @bin bigintselect @bin = sum(bin), @mem = sum(bin), @sum = 0from @stagewhile @mem > 0 begin select @sum = @sum + case when z.bin <= @bin then z.value else 0 end, @bin = @bin - case when z.bin <= @bin then z.bin else 0 end from ( select top 100 percent bin, value from @stage order by bin desc ) z insert @output select @sum select @mem = @mem - 1, @bin = @mem, @sum = 0 endselect * from @outputProcessValueSum--------------- 13 12 9 8 5 4 1 This is my first draft. I will probably come up with something more clever in a while.Peter LarssonHelsingborg, Sweden |
 |
|
|
SwePeso
Patron Saint of Lost Yaks
30421 Posts |
Posted - 2006-07-20 : 02:38:46
|
Here is a generic solution that does not have the limit mentioned in my previous post.Please note that calculating all 63 permutations take a very long time, since that is 9,223,372,036,854,775,800 combinations.Here is the code-- Prepare test data.declare @Process table (ProcessID int, ProcessValue int)insert into @Process select 1, 1 union select 2, 2 union select 3, 4 union select 4, 8 union select 5, 16declare @ProcessStage table (ProcessID int)insert into @ProcessStage select 1 union select 3 union select 4-- The work start here. Stage the work data for faster processing.DECLARE @stage TABLE (RowID INT IDENTITY(0, 1) primary key clustered, bin TINYINT default 0, pValue INT)INSERT @stage (pValue)SELECT p.ProcessValueFROM @Process pINNER JOIN @ProcessStage ps ON ps.ProcessID = p.ProcessID-- Do the magic!declare @output table (ProcessValueSum int)declare @row int, @sum intselect @row = 0while @row is not null begin update @stage set bin = 0 where rowid < @row update @stage set bin = 1 where rowid = @row insert @output select sum(pvalue) from @stage where bin = 1 select @row = min(rowid) from @stage where bin = 0 endselect * from @outputProcessValueSum--------------- 1 4 5 8 9 12 13 Peter LarssonHelsingborg, Sweden |
 |
|
|
nathans
Aged Yak Warrior
938 Posts |
Posted - 2006-07-20 : 11:27:03
|
| Nice work Peter!Do you think its possible without a loop? I can see using a cross join per array value, but thats hard to do considering we dont know the number of array values passed in. Nathan Skerl |
 |
|
|
SwePeso
Patron Saint of Lost Yaks
30421 Posts |
Posted - 2006-07-20 : 11:33:07
|
| Yes, it is possible to do, but only if you know how many permutations there will be at most at any given time.Or always calculate no more than 8 permutations at any given time.SELECT TOP 8, SELECT NEXT TOP 8 and so on? Will that do? With this approach you have more control over execution.Peter LarssonHelsingborg, Sweden |
 |
|
|
SwePeso
Patron Saint of Lost Yaks
30421 Posts |
Posted - 2006-07-20 : 11:48:45
|
| Or you can use dynamic SQL to do the trick?But remember that calculating all permutations is 2^(numbers in array), so this value quickly grows to eternity...Peter LarssonHelsingborg, Sweden |
 |
|
|
nathans
Aged Yak Warrior
938 Posts |
Posted - 2006-07-20 : 11:48:48
|
| In my case, the array is always <= 6 elements. So that would give us a max permutation value of 64.Nathan Skerl |
 |
|
|
SwePeso
Patron Saint of Lost Yaks
30421 Posts |
Posted - 2006-07-20 : 11:51:01
|
quote: Originally posted by nathans In my case, the array is <= 6 alwaysNathan Skerl
Ok, give me 30 minutes...Peter LarssonHelsingborg, Sweden |
 |
|
|
SwePeso
Patron Saint of Lost Yaks
30421 Posts |
Posted - 2006-07-20 : 12:21:16
|
Why is it important to do without a loop? My second post will take care of any vector sizes.-- Prepare test data.declare @Process table (ProcessID int, ProcessValue int)insert into @Process select 1, 1 union select 2, 2 union select 3, 4 union select 4, 8 union select 5, 16declare @ProcessStage table (ProcessID int)insert into @ProcessStage select 1 union select 3 union select 4-- The work start here. Stage the work data for faster processing.DECLARE @stage TABLE (ID INT IDENTITY(1, 1), Value INT)INSERT @stageSELECT p.ProcessValueFROM @Process pINNER JOIN @ProcessStage ps ON ps.ProcessID = p.ProcessIDDECLARE @p1 INT, @p2 INT, @p3 INT, @p4 INT, @p5 INT, @p6 INTSELECT @p1 = Value FROM @Stage WHERE ID = 1SELECT @p2 = Value FROM @Stage WHERE ID = 2SELECT @p3 = Value FROM @Stage WHERE ID = 3SELECT @p4 = Value FROM @Stage WHERE ID = 4SELECT @p5 = Value FROM @Stage WHERE ID = 5SELECT @p6 = Value FROM @Stage WHERE ID = 6-- Do the magic!declare @output table (ProcessValueSum bigint)INSERT @OutputSELECT SUM( CASE WHEN z.i / 32 % 2 = 1 THEN @p1 ELSE 0 END + CASE WHEN z.i / 16 % 2 = 1 THEN @p2 ELSE 0 END + CASE WHEN z.i / 8 % 2 = 1 THEN @p3 ELSE 0 END + CASE WHEN z.i / 4 % 2 = 1 THEN @p4 ELSE 0 END + CASE WHEN z.i / 2 % 2 = 1 THEN @p5 ELSE 0 END + CASE WHEN z.i / 1 % 2 = 1 THEN @p6 ELSE 0 END ) TFROM ( SELECT b0.i + b1.i + b2.i + b3.i + b4.i + b5.i i FROM (SELECT 0 i UNION ALL SELECT 1) b0 CROSS JOIN (SELECT 0 i UNION ALL SELECT 2) b1 CROSS JOIN (SELECT 0 i UNION ALL SELECT 4) b2 CROSS JOIN (SELECT 0 i UNION ALL SELECT 8) b3 CROSS JOIN (SELECT 0 i UNION ALL SELECT 16) b4 CROSS JOIN (SELECT 0 i UNION ALL SELECT 32) b5 ) zWHERE z.i > 0GROUP BY z.iSELECT ProcessValueSumFROM @OutputWHERE ProcessValueSum IS NOT NULLORDER BY ProcessValueSum Peter LarssonHelsingborg, Sweden |
 |
|
|
nathans
Aged Yak Warrior
938 Posts |
Posted - 2006-07-20 : 12:28:50
|
| No, not important, I was just curious how it would be done. I love seeing t-sql extended (over extended?) to solve these math probs.Great work!! I learned a lot on this one.Nathan Skerl |
 |
|
|
SwePeso
Patron Saint of Lost Yaks
30421 Posts |
Posted - 2006-07-20 : 12:34:00
|
| Are you reading about polynomic calculations in school?Peter LarssonHelsingborg, Sweden |
 |
|
|
nathans
Aged Yak Warrior
938 Posts |
Posted - 2006-07-20 : 12:47:04
|
Well, yes... but not in school any longer . Its been years since I studied this stuff, but this example has really got me tinkering again. This question stems from a generic rules engine I am developing, the elements being various qualifying attributes (Age, sex, etc.) and the ProcessID being an group classification.Im glad to have you as a resource, thanks again!Nathan Skerl |
 |
|
|
nathans
Aged Yak Warrior
938 Posts |
Posted - 2006-07-20 : 12:50:17
|
| If you know of any online resources I should check out please pass them on. Im always looking for knowledge!Nathan Skerl |
 |
|
|
SwePeso
Patron Saint of Lost Yaks
30421 Posts |
Posted - 2006-07-20 : 15:46:58
|
I could not resist this one. I don't think I can get it more simplified than this-- Prepare test data.declare @Process table (ProcessID int, ProcessValue int)insert into @Process select 1, 1 union select 2, 2 union select 3, 4 union select 4, 8 union select 5, 16declare @ProcessStage table (ProcessID int)insert into @ProcessStage select 1 union select 3 union select 4-- The work start here. Stage the work data for faster processing.DECLARE @stage TABLE (ID INT IDENTITY(1, 1), Value INT)INSERT @stageSELECT p.ProcessValueFROM @Process pINNER JOIN @ProcessStage ps ON ps.ProcessID = p.ProcessIDDECLARE @p1 INT, @p2 INT, @p3 INT, @p4 INT, @p5 INT, @p6 INTSELECT @p1 = Value FROM @Stage WHERE ID = 1SELECT @p2 = Value FROM @Stage WHERE ID = 2SELECT @p3 = Value FROM @Stage WHERE ID = 3SELECT @p4 = Value FROM @Stage WHERE ID = 4SELECT @p5 = Value FROM @Stage WHERE ID = 5SELECT @p6 = Value FROM @Stage WHERE ID = 6-- Do the magic!SELECT DISTINCT b1.i * ISNULL(@p1, 0) + b2.i * ISNULL(@p2, 0) + b3.i * ISNULL(@p3, 0) + b4.i * ISNULL(@p4, 0) + b5.i * ISNULL(@p5, 0) + b6.i * ISNULL(@p6, 0) NumFROM (SELECT 0 i UNION ALL SELECT 1) b1CROSS JOIN (SELECT 0 i UNION ALL SELECT 1) b2CROSS JOIN (SELECT 0 i UNION ALL SELECT 1) b3CROSS JOIN (SELECT 0 i UNION ALL SELECT 1) b4CROSS JOIN (SELECT 0 i UNION ALL SELECT 1) b5CROSS JOIN (SELECT 0 i UNION ALL SELECT 1) b6WHERE b1.i * ISNULL(@p1, 0) + b2.i * ISNULL(@p2, 0) + b3.i * ISNULL(@p3, 0) + b4.i * ISNULL(@p4, 0) + b5.i * ISNULL(@p5, 0) + b6.i * ISNULL(@p6, 0) > 0 Peter LarssonHelsingborg, Sweden |
 |
|
|
nathans
Aged Yak Warrior
938 Posts |
Posted - 2006-07-20 : 20:30:13
|
| yup, that is clean. Great job.Nathan Skerl |
 |
|
|
RyanRandall
Master Smack Fu Yak Hacker
1074 Posts |
Posted - 2006-07-21 : 07:00:09
|
Here's another method using a numbers table (which can be found here: http://www.sqlteam.com/forums/topic.asp?TOPIC_ID=47685).--datadeclare @Process table (ProcessID int, ProcessValue int)insert @Process select 1, 1 union select 2, 2 union select 3, 4 union select 4, 8 union select 5, 16declare @ProcessStage table (ProcessID int)insert @ProcessStage select 1 union select 3 union select 4--calculationDECLARE @stage TABLE (i INT IDENTITY(0, 1), v INT)INSERT @stageSELECT p.ProcessValueFROM @Process pINNER JOIN @ProcessStage ps ON ps.ProcessID = p.ProcessIDdeclare @NumberCount intselect @NumberCount = power(2, count(*)) from @stageselect sum(case when x > 0 then v end) as xfrom (select v, NUMBER, NUMBER & power(2, i) as x from @stage, dbo.F_TABLE_NUMBER_RANGE(1, @NumberCount)) agroup by NUMBER having sum(case when x > 0 then v end) is not null/*resultsx ----------- 145891213*/ Ryan Randallwww.monsoonmalabar.com London-based IT consultancy Solutions are easy. Understanding the problem, now, that's the hard part. |
 |
|
|
|
|
|
|
|