用于合并和求和时间段的 SQL 查询
Posted
技术标签:
【中文标题】用于合并和求和时间段的 SQL 查询【英文标题】:SQL query to merge and sum time-periods 【发布时间】:2013-01-22 08:27:18 【问题描述】:我有一个包含时间段和金额的数据库表。将它们视为具有每日期限和价格的合同:
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)随时提出这样的建议。
【问题讨论】:
total_amount_per_day
是如何计算的?
【参考方案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
来查看未聚合的原始数据,你自己可能最容易看到数据
【讨论】:
谢谢!这既是一个快速的响应,也是一个非常好的答案。以上是关于用于合并和求和时间段的 SQL 查询的主要内容,如果未能解决你的问题,请参考以下文章