Number of weeks and partial weeks between two days

2019-01-27 09:11发布

问题:

Why is this returning 4 instead of 6?

SET DATEFIRST 1
SELECT DATEDIFF(WEEK, CAST('2017-01-01' AS DATE), CAST('2017-01-31' AS DATE))

Is week as datepart calculating only weeks from monday - sunday (whole weeks)? How to get all weeks - including those which doesn't have seven days ? In the case above answer should be 6.

回答1:

DATEDIFF counts transitions, not periods (e.g. look at DATEDIFF(year,'20161231','20170101')). It also treats Sunday as the first day of the week. So, how do we compensate for these features? First, we shift our dates so that Mondays are the new Sundays, and second we add 1 to compensate for the Fence-Post error:

declare @Samples table (
    StartAt date not null,
    EndAt date not null,
    SampleName varchar(93) not null
)
insert into @Samples (StartAt,EndAt,SampleName) values
('20170101','20170131','Question - 6'),
('20170102','20170129','Exactly 4'),
('20170102','20170125','3 and a bit, round to 4'),
('20170101','20170129','4 and 1 day, round to 5')
--DATEDIFF counts *transitions*, and always considers Sunday the first day of the week
--We subtract a day from each date so that we're effectively treating Monday as the first day of the week
--We also add one because DATEDIFF counts transitions but we want periods (FencePost/FencePanel)
select *,
    DATEDIFF(WEEK, DATEADD(day,-1,StartAt), DATEADD(day,-1,EndAt)) +1
    as NumWeeks
from @Samples

Results:

StartAt    EndAt      SampleName                 NumWeeks
---------- ---------- -------------------------- -----------
2017-01-01 2017-01-31 Question - 6               6
2017-01-02 2017-01-29 Exactly 4                  4
2017-01-02 2017-01-25 3 and a bit, round to 4    4
2017-01-01 2017-01-29 4 and 1 day, round to 5    5

If this doesn't match what you want, perhaps you can adopt and adapt my @Samples table to show the results you do expect.



回答2:

What you ask though, is how many weeks are covered by a range, not how many weeks are between two dates.

DATEDIFF always uses Sunday when calculating week transitions. This isn't a bug, it's done to ensure the function is deterministic and returns the same value, for every query, no matter the DATEFIRST setting. From the documentation

Specifying SET DATEFIRST has no effect on DATEDIFF. DATEDIFF always uses Sunday as the first day of the week to ensure the function is deterministic.

One solution would be to calculate the difference between the week numbers of the start and end dates, when the first day is Monday. 1 is added to the difference to take account of the first week as well:

SET DATEFIRST 1;
select 1 +datepart(WEEK,'20170131') - datepart(WEEK,'20170101') 

That's a fragile calculation though that breaks if DATEFIRST changes or if one of the dates is on a different year.

You could use ISO Weeks to get rid of SET DATEFIRST:

select 1 +datepart(ISO_WEEK,'20170131') - datepart(ISO_WEEK,'20170101') 

but that would fail for 2017-01-01 because Sunday is counted as Week 52 of the previous year.

A far better solution though would be to count the distinct week numbers using a Calendar table that contains dates and different week numbers to cover multiple business requirements, eg both normal and ISO Week numbers, or business calendars based on a 4-4-5 calendar.

In this case, you could just count distinct week numbers:

SELECT COUNT(DISTINCT Calendar.IsoWeek )
from Calendar
where date between '20170101' and '20170131'

If the table doesn't have an ISO Week column, you can use DATEPART:

select count (distinct datepart(ISO_WEEK,date) )
from Calendar
where date between '20170101' and '20170131'