【发布时间】:2015-08-06 20:50:25
【问题描述】:
我有大量数据。我需要在每个值上实现一个产品聚合。让我用例子来说明清楚。
这是一个示例数据-
/*SampleTable*/
|ID|Date |Value |
| 1|201401|25 |
| 1|201402|-30 |
| 1|201403|-15 |
| 1|201404|50 |
| 1|201405|70 |
| 2|201010|1.15 |
| 2|201011|1.79 |
| 2|201012|0.82 |
| 2|201101|1.8 |
| 2|201102|1.67 |
必须做这张桌子-
/*ResultTable*/
|ID|Date |Aggregated Value |
| 1|201312|100 |
| 1|201401|125 |
| 1|201402|87.5 |
| 1|201403|74.375 |
| 1|201404|111.563 |
| 1|201405|189.657 |
| 2|201009|100 |
| 2|201010|101.15 |
| 2|201011|102.960 |
| 2|201012|103.804 |
| 2|201101|105.673 |
| 2|201102|107.438 |
-- Note: The 100 values are separately inserted for each ID at the month before first date
-- of previous table
这里对于每个 ID,我都有一个 Value (第 2 列) 给出了相应的 Date (YYYYMM 格式)。我必须执行以下公式来计算按每个 ID 分组的 Aggregated Value 列 -
current_Aggregated_Value = previous_aggregated_value * ((current_value/100) + 1))
对此没有简单的解决方案。我必须取上一行的聚合值,这也是同一个查询生成的值(100除外,它是手动添加的),来计算聚合值当前行。由于无法在运行时为 SQL 获取生成的值,因此我必须实现描述 here 的产品聚合函数。
so 2nd aggregated_value (125) was derived by (100 * ((25 / 100) + 1)) = 125
3rd aggregated_value (87.5) was derived by (125 * ((-30 / 100) + 1)) = 87.5
But as we cannot take the generated '125' value in runtime, I had to take the product aggregate of the all previous value, 100 * ((25 / 100) + 1) * ((-30 / 100) + 1) = 87.5
similarly 4th value (74.375) comes from, 100 * ((25 / 100) + 1) * ((-30 / 100) + 1) * ((-15 / 100) + 1) = 74.375
在下面给出一个示例查询 -
INSERT INTO ResultTable (ID, [Date], [Aggregate Value])
SELECT temps.ID, temps.[Date],
CASE
WHEN temps.min_val = 0 THEN 0
WHEN temps.is_negative % 2 = 1 THEN -1 * EXP(temps.abs_multiplier) * 100
ELSE EXP(temps.abs_multiplier) * 100
END AS value
FROM
(
SELECT st1.ID, st1.[Date],
-- Multiplication by taking all +ve values
SUM(LOG(ABS(NULLIF(((st2.Value / 100) + 1), 0)))) AS abs_multiplier,
-- Count of -ve values, final result is -ve if count is odd
SUM(SIGN(CASE WHEN ((st2.Value / 100) + 1) < 0 THEN 1 ELSE 0 END)) AS is_negative,
-- If any value in the multipliers is 0 the whole multiplication result will be 0
MIN(ABS((st2.Value / 100) + 1)) AS min_val
FROM SampleTable AS st1
INNER JOIN SampleTable AS st2 ON (st2.ID = st1.ID AND st2.[Date] <= st1.[Date])
GROUP BY st1.id, st1.[Date]
) AS temps;
基本上,它会为每个值获取先前日期的所有聚合值的产品聚合,以计算所需的值。 好吧,它听起来和看起来一样乱七八糟,而且“h-word”很慢!但是我在 SQL Server 2008 R2 中找不到任何更好的解决此类问题的方法(除非你能给我一个)。
所以,我想知道两件事-
1. 不加入同一张桌子也可以吗?
2. 在SQL Server 2008 R2 上做产品聚合有没有更好的方法? (我知道 Server 2012 中有一种方法,但这不是我的选择)
抱歉,L-O-N-G 问题!但是提前谢谢!
【问题讨论】:
-
似乎您的联接返回的行数比预期的多得多(检查
AND st2.[Date] <= st1.[Date]条件)。每个ID应该总是有一行,对吧?你检查过执行计划最慢的部分是什么? -
实际上要计算一行,我需要所有先前值的聚合乘积。我必须使用
st2.[Date] <= st1.[Date]部分吗?让我解释一下,
对于第二个值 (125),计算结果是 100*((25/100)+1)
对于第三个值 (87.5),计算结果是 125*((-30/100)+1 )。在运行时不可能取 125。所以它必须像 100*((25/100)+1) * ((-30/100)+1)
对于第四个值 (74.375) 它是 100*((25/100)+1 ) * ((-30/100)+1) * ((-15/100)+1)
等等...@Jan Zahradník -
从描述看来,第 3 行仅根据第 2 行计算,而不是第 1 行和第 2 行一起计算。结果还表明您仅使用了上个月的值。
-
在 SQL Server 2012+ 中,您可以使用累积和功能。但是,在 SQL Server 2008 中,我认为任何方法(没有游标)都将具有与您现在所做的相似的性能。
-
有两种方式,一种简单而缓慢的递归,另一种是使用
LOG和EXP的技巧,与递归相比,这种技巧并不容易且快速。
标签: sql sql-server-2008 aggregate-functions query-performance