SQL для определения нескольких диапазонов дат (SQL Server 2000)

У меня есть таблица, которая содержит ID и Date для события. Каждая строка предназначена для одной даты. Я пытаюсь определить последовательные диапазоны дат и объединить результаты, чтобы показать ID,StartDate,EndDate

ID      Date
200236  2011-01-02 00:00:00.000
200236  2011-01-03 00:00:00.000
200236  2011-01-05 00:00:00.000
200236  2011-01-06 00:00:00.000
200236  2011-01-07 00:00:00.000
200236  2011-01-08 00:00:00.000
200236  2011-01-09 00:00:00.000
200236  2011-01-10 00:00:00.000
200236  2011-01-11 00:00:00.000
200236  2011-01-12 00:00:00.000
200236  2011-01-13 00:00:00.000
200236  2011-01-15 00:00:00.000
200236  2011-01-16 00:00:00.000
200236  2011-01-17 00:00:00.000

Результат будет выглядеть так:

ID       StartDate    EndDate
200236   2011-01-02   2011-01-03
200236   2011-01-05   2011-01-13
200236   2011-01-15   2011-01-17

Есть мысли о том, как с этим справиться в SQL Server 2000?


person George Gonzola    schedule 05.05.2011    source источник


Ответы (5)


SELECT ...
FROM   ...
WHERE  date_column BETWEEN '2011-01-02' AND '2011-01-15'

возможно? Справочник

Или вы можете выполнить подзапрос и связать следующую запись с помощью MAX, где date ‹= текущая дата:

SELECT id, date, (SELECT MAX(date) FROM mytable WHERE date <= mytable.date) AS nextDate
FROM   mytable

Или используйте:

SELECT TOP 1 date
FROM         mytable
WHERE        date <= mytable.date AND id <> mytable.id
ORDER BY     date

В качестве подзапроса он захватывает следующую дату в строке после текущей записи.

person Brad Christie    schedule 05.05.2011
comment
Приведенные данные являются лишь образцом таблицы. Есть тысячи записей (строк), и я привел пример. Существует несколько идентификаторов, и каждый идентификатор может иметь от 1 до бесконечности дат. - person George Gonzola; 05.05.2011
comment
@GeorgeGonzola: Поэтому используйте подвыборку и ссылайтесь на дату текущей строки, чтобы найти следующую дату (затем вытащите это значение в качестве третьего столбца). - person Brad Christie; 05.05.2011

Я только что проделал то же самое в SQL Server 2008. Я думаю, что следующий перевод будет работать для SQL Server 2000:

-- Create table variable
DECLARE @StartTable TABLE
(
  rowid INT IDENTITY(1,1) NOT NULL,
  userid int,
  startDate date
)

Insert Into @StartTable(userid, startDate)
--This finds the start dates by finding unmatched values
SELECT t1.ID, t1.[Date]
FROM Example As t1
LEFT OUTER JOIN Example As t2 ON t1.ID=t2.ID 
   And DateAdd(day, 1, t2.[Date]) = t1.[Date]
WHERE t2.[Date] Is NULL
ORDER BY t1.ID, t1.[Date]

-- Create table variable
DECLARE @EndTable TABLE
(
  rowid INT IDENTITY(1,1) NOT NULL,
  userid int,
  endDate date
)

Insert Into @EndTable(userid, endDate)
--This finds the end dates by getting unmatched values 
SELECT t1.ID, t1.[Date]
FROM Example As t1
LEFT OUTER JOIN Example As t2 ON t1.ID=t2.ID
   And DateAdd(day, -1, t2.[Date]) = t1.[Date]
WHERE t2.[Date] IS NULL
ORDER BY t1.ID, t1.[Date]

Select eT.userid, startDate, endDate 
From @EndTable eT
INNER JOIN @StartTable sT On eT.userid = sT.userid 
AND eT.rowid = sT.rowid;

Итак, как вы можете видеть, я создал две переменные таблицы, одну для начала и одну для концов, путем самосоединения таблицы в дату, непосредственно предшествующую или сразу после даты в столбце [Дата]. Это означает, что я выбираю только записи, у которых нет предшествующей даты (чтобы они находились в начале периода) для начальной таблицы, и те, у которых нет даты после (так что они будут в конце период) для конечной таблицы.

Когда они вставляются в переменную таблицы, они нумеруются по порядку из-за столбца Identity. Затем я соединяю две переменные таблицы вместе. Поскольку они упорядочены, даты начала и окончания всегда должны совпадать.

Это решение работает для меня, потому что у меня есть не более одной записи для каждого идентификатора в день, и меня интересуют только дни, а не часы и т.д. курсоры или петли. Надеюсь, это сработает и для вас.

person Kit Z. Fox    schedule 09.05.2011
comment
Приятно, мне нравится эффективное использование временных таблиц. Кстати, я не уверен, насколько это решение может иметь общего с аналогичным решением в SQL Server 2008, но вы можете взглянуть на этот ответ (на всякий случай). - person Andriy M; 10.05.2011
comment
Спасибо! Мое решение похоже на это; на самом деле гораздо проще использовать CTE и секционирование для выполнения этой задачи, но, к сожалению, эти вещи недоступны в SQL Server 2000. - person Kit Z. Fox; 10.05.2011

Этот вопрос SO может вам помочь. Я напрямую связался с ответом Роба Фарли, так как считаю, что это аналогичная проблема.

person Caley Woods    schedule 05.05.2011

Один из подходов, который вы можете использовать, - это добавить поле, которое указывает следующую дату в последовательности. (Либо добавьте его в свою текущую таблицу, либо используйте временную таблицу, сохраните базовые данные во временной таблице, а затем обновите следующую дату в последовательности).

Ваша начальная структура данных будет выглядеть примерно так:

ID, PerfDate, NextDate
200236, 2011-01-02, 2011-01-03
200236, 2011-01-03, 2011-01-04
etc.

Затем вы можете использовать серию коррелированных подзапросов для сворачивания данных в желаемый результат:

SELECT ID, StartDate, EndDate
FROM (
SELECT DISTINCT ID, PerfDate AS StartDate, 
    (SELECT MIN([PerfDate]) FROM [SourceTable] S3
    WHERE S3.ID = S1.ID
    AND S3.NextDate > S1.PerfDate
    AND ISNULL(
        (SELECT MIN(PerfDate) 
        FROM [SourceTable] AS S4
        WHERE S4.ID = S1.ID 
        AND S4.NextDate > S3.NextDate), S3.NextDate + 1) > S3.NextDate) AS EndDate
FROM [SourceTable] S1
WHERE 
    ISNULL(
        (SELECT MAX(NextDate) 
        FROM [SourceTable] S2 
        WHERE S2.ID = S1.ID 
        AND S2.PerfDate < S1.PerfDate), PerfDate -1) < S1.PerfDate)q
ORDER BY q.ID, q.StartDate
person Tim Lentine    schedule 05.05.2011

Так я поступал раньше. Это двухэтапный процесс:

  1. Создайте набор кандидатов смежных периодов
  2. Если есть какие-либо перекрывающиеся периоды, удалите все, кроме самого длинного из таких периодов.

Вот сценарий, который показывает, как это делается. Возможно, вам удастся справиться с этим с помощью одного [ошибка, уродливый] запрос, но при попытке сделать это у меня болит голова. Я использую временные таблицы, так как это значительно упрощает отладку.

drop table #source
create table #source
(
  id    int      not null ,
  dtCol datetime not null ,

  -----------------------------------------------------------------------
  -- ASSUMPTION 1: Each date must be unique for a given ID value.
  -----------------------------------------------------------------------
  unique clustered ( id , dtCol ) ,

  -----------------------------------------------------------------------
  -- ASSUMPTION 2: The datetime column only represents a day.
  -- The value of the time component is always 00:00:00.000
  -----------------------------------------------------------------------
  check ( dtCol = convert(datetime,convert(varchar,dtCol,112),112) ) ,

)
go

insert #source values(1,'jan 1, 2011')
insert #source values(1,'jan 4, 2011')
insert #source values(1,'jan 5, 2011')
insert #source values(2,'jan 1, 2011')
insert #source values(2,'jan 2, 2011')
insert #source values(2,'jan 3, 2011')
insert #source values(2,'jan 5, 2011')
insert #source values(3,'jan 1, 2011')
insert #source values(4,'jan 1, 2011')
insert #source values(4,'jan 2, 2011')
insert #source values(4,'jan 3, 2011')
insert #source values(4,'jan 4, 2011')
go

insert #source values( 200236 , '2011-01-02')
insert #source values( 200236 , '2011-01-03')
insert #source values( 200236 , '2011-01-05')
insert #source values( 200236 , '2011-01-06')
insert #source values( 200236 , '2011-01-07')
insert #source values( 200236 , '2011-01-08')
insert #source values( 200236 , '2011-01-09')
insert #source values( 200236 , '2011-01-10')
insert #source values( 200236 , '2011-01-11')
insert #source values( 200236 , '2011-01-12')
insert #source values( 200236 , '2011-01-13')
insert #source values( 200236 , '2011-01-15')
insert #source values( 200236 , '2011-01-16')
insert #source values( 200236 , '2011-01-17')
go

drop table #candidate_range
go
create table #candidate_range
(
  rowId   int      not null identity(1,1) ,
  id      int      not null ,
  dtFrom  datetime not null ,
  dtThru  datetime not null ,
  length  as 1+datediff(day,dtFrom,dtThru) ,

  primary key nonclustered ( rowID ) ,
  unique clustered (id,dtFrom,dtThru) ,

)
go

--
-- seed the candidate range table with the set of all possible contiguous ranges for each id
--
insert #candidate_range ( id , dtFrom , dtThru )
select id      = tFrom.id    ,
       valFrom = tFrom.dtCol ,
       valThru = tThru.dtCol
from #source tFrom
join #source tThru on tThru.id     = tFrom.id
                  and tThru.dtCol >= tFrom.dtCol
where 1+datediff(day,tFrom.dtCol,tThru.dtCol) = ( select count(*)
                                                  from #source t
                                                  where t.id = tFrom.id
                                                    and t.dtCol between tFrom.dtCol and tThru.dtCol
                                                )
order by 1,2,3
go

--
-- compare the table to itself. If we find overlapping periods,
-- we'll keep the longest such period and delete the shorter overlapping periods.
--
delete t2
from #candidate_range t1
join #candidate_range t2 on t2.id      = t1.id
                        and t2.rowId  != t1.rowID
                        and t2.length <  t1.length
                        and t2.dtFrom <= t1.dtThru
                        and t2.dtThru >= t1.dtFrom
go

Вот и все, что нужно знать об этом.

person Nicholas Carey    schedule 05.05.2011