我将其分解为几个步骤(每个步骤都有一个单独的 CTE):
declare @Ranges table (FromDate date not null,ToDate date not null)
insert into @Ranges (FromDate,ToDate) values
('20121110','20121115'),
('20121121','20121122'),
('20121130','20121201')
;with Months as (
select
DATEADD(month,DATEDIFF(month,'20010101',FromDate),'20010101') as MonthStart,
DATEADD(month,DATEDIFF(month,'20010101',FromDate),'20010131') as MonthEnd
from @Ranges
union /* not all */
select
DATEADD(month,DATEDIFF(month,'20010101',ToDate),'20010101') as MonthStart,
DATEADD(month,DATEDIFF(month,'20010101',ToDate),'20010131') as MonthEnd
from @Ranges
), MonthRanges as (
select
CASE WHEN r.FromDate > m.MonthStart then r.FromDate ELSE m.MonthStart END as StartRange,
CASE WHEN r.ToDate < m.MonthEnd then r.ToDate ELSE m.MonthEnd END as EndRange
from
@Ranges r
inner join
Months m
on
r.ToDate >= m.MonthStart and
r.FromDate <= m.MonthEnd
)
select
DATEPART(month,StartRange),
SUM(DATEDIFF(day,StartRange,EndRange)+1) /* Inclusive */
from
MonthRanges
group by
DATEPART(month,StartRange)
首先,Months
CTE 找到我们可能感兴趣的每个月的第一天和最后几天(*)。然后,MonthRanges
将此数据与原始范围重新组合并根据需要对其进行拆分,以便我们处理的每个时间段仅代表一个月中的几天。然后我们可以只使用DATEDIFF
来计算每个范围跨越的天数(并添加 1,因为我们正在处理日期并想要包含值)
(*) Months
CTE 将起作用,前提是我们不处理跨越多个月的任何范围,并且在中间几个月内没有其他范围开始或结束。如果你需要应对这种情况,我需要修改Months
CTE。例如,如果我们('20120115','20120315')
在上面的样本中添加(并且没有其他范围),我们将不会使用上面的方法得到二月份的结果。我们需要应对这种情况吗?
为了应对 (*) 中指出的情况,我们可以将Months
上述查询中的 CTE 替换为:
;With LastMonth as (
select MAX(ToDate) as Mx from @Ranges
), MultiMonths as (
select
DATEADD(month,DATEDIFF(month,'20010101',FromDate),'20010101') as MonthStart,
DATEADD(month,DATEDIFF(month,'20010101',FromDate),'20010131') as MonthEnd
from @Ranges
union all
select
DATEADD(month,1,MonthStart),
DATEADD(month,1,MonthEnd)
from MultiMonths
where MonthStart <= (select Mx from LastMonth)
), Months as (
select distinct MonthStart,MonthEnd from MultiMonths
)