Понедельник начинается в субботу, а MSSQL мне не верит! У финансистов и бухгалтеров это действительно так. Например, если день сдачи налогового отчёта приходится на субботу, это значит, что у них есть на написание суббота, воскресенье и половина понедельника. Перевести деньги в выходные тоже не получается: то ли банковские компьютеры отдыхают по выходным, то ли просто выходной у бабушки, что проверяет все электронные платежи на деревянных счётах. Раньше следующей недели денег не получишь.
Пришлось вспомнить эту особенность взаимоотношений времени и денег, когда понадобилось построить понедельный отчёт по ДДС, что расшифровывается как «Движение Денежных Средств». Если некое движение приходится на субботу или воскресенье – его следует отнести уже к следующей неделе. Ведь в действительности оно осуществится не раньше понедельника. Отчёт строился одним запросом на MSSQL2005, расстояние в неделях от текущей даты я считал при помощи функции datediff. Казалось бы, чего проще: на время сессии дать команду SET DATEFIRST, установив первым днём субботу…
А не тут-то было! Не поверил своим глазам: на datediff эта команда не оказала никакого влияния. Даже пример написал, думал всем показать своё открытие. Мол, ай да я, нашёл такую ошибку в MSSQL2005!
/*
Если сегодня установить первым днём недели,
то вчера будет считаться днём предыдущей недели, а завтра - текущей.
Для datepart это так,
для datediff - нет!
*/
declare @OldDatefirst int
declare @NewDatefirst int
declare @StartDate datetime
declare @EndDate datetime
SET @OldDatefirst = @@Datefirst -- запоминаем день недели
SET @NewDatefirst = datepart(dw, getdate()) -- неделя начинается сегодня
SET @StartDate = getdate() - 1 -- вчера
SET @EndDate = getdate() + 1 -- завтра
SET DATEFIRST @NewDatefirst -- установили сегодняшний день первым днём недели
--по идее, вчера и завтра должны попадать в разные недели
SELECT @StartDate, @EndDate, -- сами даты
datepart(ww, @StartDate), datepart(ww, @EndDate), -- номера недель в году отличаются
datediff(ww, @StartDate, @EndDate) -- а вот datediff даёт разницу 0
SET DATEFIRST @OldDatefirst -- возвращаем первый день недели, что был по-умолчанию
Оказалось, правда, что это не ошибка, а документированная особенность поведения функции datediff. Если бы я читал внимательно нужную литературу, то и сам бы знал, что
DATEDIFF() ignores the DATEFIRST setting, it assumes every week boundary begins on Sunday. You can see this by putting your start and end dates on a Saturday and Monday, and cycling through all possible DATEFIRST values. You'll see a "1" value regardless of setting.
SET DATEFIRST does control the DOW return value on DATEPART though obviously.
А так как внимательно читал документацию Иван Клёва, то он мне эту цитату и предоставил…
Америку я открыл, а вот отчёт так и не построил. Как же быть?
К счастью, хотя бы datepart подвержен влиянию команды set datefirst. Оставалась надежда как-то извернуться и заставить сервер служить высоким финансовым целям. Вариант с вычитанием двух datepart в режиме недель пришлось отбросить, ведь функция возвращает номер недели в году. Стоит одной из дат оказаться в другом году - и метод тут же перестанет работать без значительного усложнения...
«Привычность мыслей надо гнать, столовый нож оружьем может стать»
Отогнав привычность мыслей, мы решили, всё же, использовать datediff, но сравнивать не сами исходные даты, а исходные даты, приведённые к новому первому дню недели. Судя по этому примеру, цель была достигнута.
declare @OldDatefirst int
declare @NewDatefirst int
declare @StartDate datetime
declare @EndDate datetime
declare @StartDateModified datetime
declare @EndDateModified datetime
SET @OldDatefirst = @@Datefirst -- запоминаем день недели
SET @NewDatefirst = datepart(dw, getdate()) -- неделя начинается сегодня
SET @StartDate = getdate() - 1 --вчера
SET @EndDate = getdate() + 1 -- завтра
SET DATEFIRST @NewDatefirst -- неделя начинается сегодня
--приводим обе даты к первому дню недели
SET @StartDateModified = @StartDate - datepart(dw, @StartDate) + 1
SET @EndDateModified = @EndDate - datepart(dw, @EndDate) + 1
SELECT
@StartDate, @EndDate, -- сами даты
datediff(ww, @StartDate, @EndDate), -- разница в неделях для начальных дат
datediff(ww, @StartDateModified, @EndDateModified) -- теперь для datediff это дни разных недель
SET DATEFIRST @OldDatefirst -- возвращаем первый день недели, что был по-умолчанию
Всякий, кто сталкивался с такой задачей, проходил тот же путь поисков удачного решения. Может быть, предлагаемый способ послужит готовым рецептом, хотя наверняка есть и другие, более изящные методы доказать серверу, что понедельник начинается в субботу...