简体   繁体   中英

SQL Server query for total number of days for a month between date ranges

I have specific date range like

From Date        To Date
---------------------------    
2012-11-10       2012-11-15
2012-11-21       2012-11-22
2012-11-30       2012-12-01

I want to write a SQL query which calculates the total no of days between two dates and sum total number of days of particular month

The output I wanted is,

No of days     month
--------------------
   9            11
   1            12

Can anyone help me to write this SQL query?

Ideally, you have a table named "Dates" with all the dates you will ever use, eg year 1950 through 2100. This query will give you the result you want:

  select dateadd(m,datediff(m, 0, d.thedate),0) themonth, count(1)
    from dates d
    join ranges r on d.thedate between r.[from date] and r.[to date]
group by datediff(m, 0, d.thedate)
order by themonth;

Result:

|   themonth | COLUMN_1 |
-------------------------
| 2012-11-01 |        9 |
| 2012-12-01 |        1 |

Note that instead of just showing "11" or "12" as month, which doesn't work well if you have ranges going above 12 months, or doesn't help sorting when it crosses a new year, this query shows the first day of the month instead.

If not, you can virtually create a dates table on the fly, per the expanded query below:

;with dates(thedate) as (
  select dateadd(yy,years.number,0)+days.number
    from master..spt_values years
    join master..spt_values days
      on days.type='p' and days.number < datepart(dy,dateadd(yy,years.number+1,0)-1)
   where years.type='p' and years.number between 100 and 150
      -- note: 100-150 creates dates in the year range 2000-2050
      --       adjust as required
)
  select dateadd(m,datediff(m, 0, d.thedate),0) themonth, count(1)
    from dates d
    join ranges r on d.thedate between r.[from date] and r.[to date]
group by datediff(m, 0, d.thedate)
order by themonth;

The full working sample is given here: SQL Fiddle

尝试这个

select ((day(date_to)) - (day(date_from))) as no_of_days,month(date_from)as month from tablename 

Pardon me for badly written SQL.
The assumption is that the month diff. between fromdate and todate is 1.

Schema

CREATE TABLE dateData
    (fromdate datetime, todate datetime)
;

INSERT INTO dateData
    (fromdate, todate)
VALUES
    ('2012-11-10', '2012-11-15'),
    ('2012-11-21', '2012-11-22'),
    ('2012-11-30', '2012-12-01')
;

SQL

select mth, sum(days) as daysInMth
from
(
select month(fromdate) as mth,
sum(case 
when month(fromdate) = month(todate) then datediff(dd, fromdate, todate)+1
else datediff(dd, fromdate, dateadd(mm, 1, fromdate) - day(fromdate)) + 1 end)
as days 
from dateData
group by month(fromdate)
union
select month(todate) as mth,
sum(case when month(todate) <> month(fromdate) then
datediff(dd, fromdate, dateadd(mm, 1, fromdate) - day(fromdate)) + 1
else 
case when month(todate) = month(fromdate) then 0 else
datediff(dd, convert(datetime, year(todate) + '-' + month(todate) + '-1'), todate) 
end
end) as days
from dateData
group by month(todate)
) aggregated
group by mth

View on SQLFiddle: http://www.sqlfiddle.com/#!3/9f7da/56

I'd break it down into several steps (each given a separate 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)

First, the Months CTE finds the first and last days of each month that we might be interested in(*). Then, MonthRanges recombines this data with the original ranges and splits them as required so that each period we're dealing with only represents days from a single month. Then we can just use DATEDIFF to calculate the number of days that each range spans (and add 1 since we're dealing with dates and want inclusive values)

(*) the Months CTE will work provided that we're not dealing with any ranges that span over multiple months and where no other ranges start or end in the intervening months. If you need to cope with this situation, I need to revise the Months CTE. Eg if we add ('20120115','20120315') (and no other range) to the above sample, we won't get a result for February using the above. Do we need to cope with this situation?


To cope with the situation noted in (*), we can replace the Months CTE in the above query with:

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM