我有一个包含时间段和金额的数据库表。将它们视为具有持续时间和每日价格的合同:
start | end | amount_per_day
2013-01-01 | 2013-01-31 | 100
2013-02-01 | 2013-06-30 | 200
2013-01-01 | 2013-06-30 | 100
2013-05-01 | 2013-05-15 | 50
2013-05-16 | 2013-05-31 | 50
我想提出一个查询,显示每个时期的总数,即:
从2013-01-01到2013-01-31,第一个和第三个合同是有效的,因此每天的总金额是200.从2013-02-01到2013-04-30,第二个和第三个合同行是活动的,因此总数为300.从2013-05-01到2013-05-15,第二,第三和第四行是活动的,因此总数为350.从2013-05-16到2013-05-31第二,第三和第五行是活动的,所以总数再次为350.最后,从2013-06-01到2013-06-30只有第二和第三行是活动的,所以总数回到300.
start | end | total_amount_per_day
2013-01-01 | 2013-01-31 | 200
2013-02-01 | 2013-04-30 | 300
2013-05-01 | 2013-05-31 | 350
2013-06-01 | 2013-06-30 | 300
(没有必要检测间隔2013-05-01 -> 2013-05-15
和2013-05-16 -> 2013-05-31
是否具有相同的总和并合并它们,但它会很好。)
我更喜欢便携式解决方案,但如果不可能,SQL Server也可以使用。
我可以对表的结构进行细微的更改,因此如果它会使查询更简单,例如标注结束日期独占的时间段(因此第一个时段将是开始= 2013-01-01,结束时间= 2013-02-01)随时提出此类建议。
答案 0 :(得分:1)
我将从完整查询开始,然后将其分解并解释它。这是特定于SQL Server的,但通过微调可以适应任何支持分析功能的DMBS。
WITH Data AS
( SELECT Start, [End], Amount_Per_Day
FROM (VALUES
('20130101', '20130131', 100),
('20130201', '20130630', 200),
('20130101', '20130630', 100),
('20130501', '20130515', 50),
('20130516', '20130531', 50)
) t (Start, [End], Amount_Per_Day)
), Numbers AS
( SELECT Number
FROM Master..spt_values
WHERE Type = 'P'
), DailyData AS
( SELECT [Date] = DATEADD(DAY, Number, Start),
[AmountPerDay] = SUM(Amount_Per_Day)
FROM Data
INNER JOIN Numbers
ON Number BETWEEN 0 AND DATEDIFF(DAY, Start, [End])
GROUP BY DATEADD(DAY, Number, Start)
), GroupedData AS
( SELECT [Date],
AmountPerDay,
[GroupByValue] = DATEADD(DAY, -ROW_NUMBER() OVER(PARTITION BY AmountPerDay ORDER BY [Date]), [Date])
FROM DailyData
)
SELECT [Start] = MIN([Date]),
[End] = MAX([Date]),
AmountPerDay
FROM GroupedData
GROUP BY AmountPerDay, GroupByValue
ORDER BY [Start], [End];
Data
CTE只是您的样本数据。
Numbers
CTE只是0到2047之间的一系列数字(如果您的开始和结束日期超过2047天,则会失败,需要稍微调整)
下一个CTE DailyData
只是使用这些数字将您的范围扩展到各自的日期,所以
20130101, 20130131, 100
变为
20130101, 100
20130102, 100
20130103, 100
....
20130131, 100
然后,只需要在ROW_NUMBER函数的帮助下按每天的数量对数据进行分组,以查找何时更改并定义每天类似金额的范围,然后获取每个范围的MIN和MAX日期。
我总是很难解释/演示这种分组范围方法的确切运作方式,如果它没有意义,如果你只是在最后使用SELECT * FROM DailyData
来查看原始未分类数据