2

我有一个包含时间段和金额的数据库表。将它们视为具有每日期限和价格的合同:

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-152013-05-16 -> 2013-05-31具有相同的总数并合并它们,但这会很好)。

我更喜欢可移植的解决方案,但如果不可能的话,SQL Server 也可以工作。

我可以对表的结构进行一些小的更改,因此如果它可以使查询更简单,例如用结束日期独占来标记时间段(所以第一个时间段将是 start = 2013-01-01,end = 2013-02-01) 随时提出这样的建议。

4

1 回答 1

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];

DataCTE 只是您的样本数据。

CTE 只是从 0 到 2047的Numbers数字序列(如果您的开始日期和结束日期相隔超过 2047 天,这将失败并且需要稍微调整)

Next CTEDailyData只是使用这些数字将您的范围扩展到它们各自的日期,所以

20130101, 20130131, 100

变成

20130101, 100
20130102, 100
20130103, 100
....
20130131, 100

然后,它只是在 ROW_NUMBER 函数的帮助下按每天的数量对数据进行分组,以查找它何时更改并定义每天相似数量的范围,然后获取每个范围的 MIN 和 MAX 日期。

我总是很难解释/演示这种分组范围方法的确切工作原理,如果它没有意义,如果你只是SELECT * FROM DailyData在最后使用查看原始未聚合数据,它可能最容易让你自己看到

于 2013-01-22T08:53:01.377 回答