Проверьте, содержат ли две даты данный месяц - PullRequest
5 голосов
/ 12 ноября 2009

Моя проблема проста ... или нет. У меня есть таблица, которая содержит две даты:

StartDate
EndDate

И у меня есть постоянная, которая составляет месяц. Например:

DECLARE @MonthCode AS INT
SELECT  @MonthCode = 11  /* NOVEMBER */

Мне нужен ЕДИНСТВЕННЫЙ ЗАПРОС, чтобы найти все записи, для которых StartDate и EndDate включают данный месяц. Например:

/* Case 1 */ Aug/10/2009 - Jan/01/2010
/* Case 2 */ Aug/10/2009 - Nov/15/2009
/* Case 3 */ Nov/15/2009 - Jan/01/2010
/* Case 4 */ Nov/15/2009 - Nov/15/2009
/* Case 5 */ Oct/01/2010 - Dec/31/2010

Первый и последний случай требуют особого внимания: обе даты находятся за пределами ноября, но они пересекаются.

Следующий запрос не учитывает случаи 1 и 5:

WHERE MONTH( StartDate ) = @MonthCode OR MONTH( EndDate ) = @MonthCode

Следующий запрос также не выполнен, потому что Авг <ноябрь и ноябрь <январь = false </strong>:

WHERE MONTH( StartDate ) = @MonthCode OR MONTH( EndDate ) = @MonthCode OR (
MONTH( StartDate ) < @MonthCode AND @MonthCode < MONTH( EndDate )
)

Ответы [ 6 ]

5 голосов
/ 12 ноября 2009

Я понимаю, что вы ищете способ выбрать все диапазоны, которые пересекают ноябрь, в любом году .

Вот логика:

  • если диапазон попадает на один год (например, 2009), начальный месяц должен быть до или равен ноябрю, а последний месяц - после или равным ноябрю

  • если диапазон попадает на два последующих года (например, 2009-2010), начальный месяц должен быть до или равен ноябрю ИЛИ конечному месяцу после или равным

  • если диапазон падает на два года с разницей более 1 года (например, 2008-2010), ноябрь всегда включается в диапазон (здесь ноябрь 2009)

Переведено в псевдокод, условие:

// first case
(
  (YEAR(StartDate)=YEAR(EndDate)) AND
  (MONTH(StartDate)<=MonthCode AND MONTH(EndDate)>=MonthCode)
)
OR
// second case
(
  (YEAR(EndDate)-YEAR(StartDate)=1) AND
  (MONTH(StartDate)<=MonthCode OR MONTH(EndDate)>=MonthCode)
)
OR
// third case
(
  YEAR(EndDate)-YEAR(StartDate)>1
)
2 голосов
/ 12 ноября 2009
DECLARE @MonthCode AS INT
SELECT @MonthCode = 11  /* NOVEMBER */

declare @yourtable table(
    startdate datetime
    , enddate datetime
)
insert into @yourtable(
    startdate
    , enddate
)
(
select '8/10/2009', '01/01/2010'
union all
select '8/10/2009' , '11/15/2009'
union all
select '11/15/2009' , '01/01/2010'
union all 
select '11/15/2009' , '11/15/2009'
union all
select '10/01/2010' , '12/31/2010'
union all
select '05/01/2009', '10/30/2009'
)

select *
from @yourtable
where DateDiff(mm, startdate, enddate) > @MonthCode     -- can't go over 11 months without crossing date
    OR (Month(startdate) <= @MonthCode                  -- before Month selected
            AND (month(enddate) >=@MonthCode            -- after month selected
                OR year(enddate) > year(startdate)    -- or crosses into next year
                )
        )
    OR (Month(startdate) >= @MonthCode                  -- starts after in same year after month
            and month(enddate) >= @MonthCode            -- must end on/after same month assume next year
            and year(enddate) > year(startdate)
        )
1 голос
/ 12 ноября 2009

Попробуйте это:

select * from Mytable
where 
month(StartDate) = @MonthCode or month(EndDate) = @MonthCode // Nov/15/2009 - Nov/15/2009
or
dateadd(month,@MonthCode-1,convert(datetime,convert(varchar,year(StartDate))))
between StartDate and EndDate // Oct/01/2010 - Dec/31/2010
or
dateadd(month,@MonthCode-1,convert(datetime,convert(varchar,year(EndDate))))
between StartDate and EndDate // Dec/01/2009 - Dec/31/2010 - tricky one

Основная идея - проверить, где находятся даты 01.November.StartYear и 01.November.EndYear.

Надеюсь, это поможет.

0 голосов
/ 12 ноября 2009

Для этого можно использовать различные функции, такие как DATEPART и DATETIFF . Однако реальная проблема не в том, как выразить условие StartDate или EndDate, приходящееся на данный месяц, а в том, как сделать это таким образом, чтобы сделать запрос эффективным. Другими словами, как выразить это в форме SARGable.

Если вы выполняете поиск в маленькой таблице изменений, размером менее 10 тыс. Страниц, тогда это не имеет большого значения, полное сканирование, вероятно, будет вполне приемлемым. Реальный вопрос заключается в том, значительны ли размер таблицы и полное сканирование неприемлемо.

Если у вас нет индекса ни по одному из столбцов StartDate или EndDate, это не имеет значения, критерии не доступны для поиска, и запрос все равно будет сканировать всю таблицу. Однако, если в StartDate и EndDate имеются индексы, то способ выражения условия имеет все значение. Важной частью для индексов DATETIME является то, что вы должны выражать поиск в точном диапазоне дат. Выражение условия как функции в зависимости от поля DATETIME сделает условие неисследимым, что приведет к полному сканированию таблицы. Таким образом, эти знания правильно отображаются в диапазоне дат:

select ... from table
where StartDate between '20091101' and '20091201'
or EndDate between '20091101' and '20091201';

Это также может быть выражено как:

select ... from table
where StartDate between '20091101' and '20091201'
union all
select ... from table 
where EndDate between '20091101' and '20091201'
and StartDate not between '20091101' and '20091201';

Какой запрос работает лучше, зависит от ряда факторов, таких как размер таблицы и статистика фактических данных в таблице.

Однако вы хотите, чтобы ноябрьский месяц начинался с любого года, который этот запрос вам не даст. Решение этой проблемы против каждого инстинкта, который имеет программист: жесткий код соответствующих лет. В большинстве случаев таблицы в любом случае имеют небольшой набор лет, что находится в диапазоне 4-5 лет прошлых данных, и планируйте еще 3-4 года, пока система не будет капитально отремонтирована:

select ... from table
where StartDate between '20051101' and '20051201'
or EndDate between '20051101' and '20051201'
union all
select ... from table
where StartDate between '20061101' and '20061201'
or EndDate between '20061101' and '20061201'
union all
...
select ... from table
where StartDate between '20151101' and '20151201'
or EndDate between '20151101' and '20151201';

В году 12 месяцев, напишите 12 отдельных процедур. Это звучит безумно? Конечно, это так, но это оптимальная вещь с точки зрения компилятора и оптимизатора SQL-запросов. Как можно поддерживать такой код? 12 отдельных процедур, с запросом, который повторяется 10 раз (20 раз, если вы используете UNION между StartDate и EndDate для удаления ИЛИ), 120 повторений кода, это должно быть бессмысленным. На самом деле это не так. Используйте генерацию кода для создания процедур, таких как XML / XSLT, чтобы вы могли легко изменять и поддерживать их. Должен ли клиент знать о 12 процедурах и вызвать соответствующую? Конечно, нет, он вызывает одну процедуру-обертку, которая различает аргумент @Month для вызова правильной.

Я считаю, что любой, кто будет смотреть на систему после фактов, скорее всего, поверит, что этот запрос был написан группой пьяных обезьян. Тем не менее, где-то между анализом параметров, индексом SARGability и SQL DATETIME изумляет, в результате получается, что это состояние дел на сегодняшний день, когда оно относится к поиску календарных интервалов.

Да, и если запрос достигнет критической точки индекса , это все равно отключит весь аргумент ...

Обновление

Кстати, есть и дешевый выход, если вы готовы пожертвовать некоторым пространством хранения: два постоянных вычисляемых столбца для StartMonth AS DATEPART(month, StartDate) и EndDate AS DATEPART(month, EndDate), индекс для каждого и запрос WHERE StartMonth = @Month OR EndMonth = @Month (или снова UNION между двумя запрашивает один для начала, один для конца, чтобы удалить ИЛИ).

0 голосов
/ 12 ноября 2009

SQL Server 200/2005, вы также можете сделать это:

select 
   * 
from 
   table
where 
   datepart(m,startDate) = 11
   and datepart(m,EndDate) = 11

UPDATE: Удалено and datepart(yyyy,startDate) = datepart(yyyy,endDate) Хотите ли вы данный месяц независимо от года или дня?

0 голосов
/ 12 ноября 2009

Фильтр для строк, которые начинаются до конца месяца и заканчиваются после начала месяца. За октябрь 2009 года:

select *
from YourTable
where StartDate < '2009-11-01' and EndDate >= '2009-10-01'

Или, с вводом только месяца:

declare @month datetime
set @month = '2009-10-01'

select *
from YourTable
where StartDate < dateadd(month,1,@month)
and EndDate >= @month
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...