2011-01-06 2 views
4

У меня есть ситуация, когда мне нужно найти промежутки времени между изменениями значений. Я попробовал простой элемент group by, но он устраняет перекрывающиеся изменения. Рассмотрим следующий пример:Как избежать перекрытия диапазонов дат при использовании предложения группировки?

create table #items (
     code varchar(4) 
    , class varchar(4) 
    , txdate datetime 
) 

insert into #items (code, class, txdate) values ('A', 'C', '2010-01-01'); 
insert into #items (code, class, txdate) values ('A', 'C', '2010-01-02'); 
insert into #items (code, class, txdate) values ('A', 'C', '2010-01-03'); 
insert into #items (code, class, txdate) values ('A', 'D', '2010-01-04'); 
insert into #items (code, class, txdate) values ('A', 'D', '2010-01-05'); 
insert into #items (code, class, txdate) values ('A', 'C', '2010-01-06'); 
insert into #items (code, class, txdate) values ('A', 'C', '2010-01-07'); 
insert into #items (code, class, txdate) values ('A', 'D', '2010-01-08'); 
insert into #items (code, class, txdate) values ('A', 'D', '2010-01-09'); 

select code 
, class 
, min(txdate) mindate 
, max(txdate) maxdate 
from #items 
group by code, class 

Это возвращает следующие результаты (обратите внимание на перекрывающиеся диапазоны дат):

|code|class|mindate |maxdate | 
---------------------------------- 
|A |C |2010-01-01|2010-01-07| 
|A |D |2010-01-04|2010-01-09| 

Я хотел бы иметь запрос возвращал следующее:

|code|class|mindate |maxdate | 
---------------------------------- 
|A |C |2010-01-01|2010-01-03| 
|A |D |2010-01-04|2010-01-05| 
|A |C |2010-01-06|2010-01-07| 
|A |D |2010-01-08|2010-01-09| 

Любые идеи и предложения?

+0

Ваши даты ввода всех содержат январь месяца, но некоторые из ваших результатов имеют апрель. Вы переносили месяцы и дни в свои результаты, когда их вводили? – CanSpice

+0

@CanSpice: мои даты используют формат yyyy-mm-dd. Я поймал ошибку и исправил ее. Thx –

+0

Хорошо, тогда ваш пример должен включать даты в апреле, потому что все, что вы вводите, это даты в январе, но ваш пример возвращает даты в апреле. – CanSpice

ответ

0

После исследования SQL SERVER ISLANDS как это было предложено @KM, я придумал следующий запрос, который, кажется, работает хорошо, когда дополнительные коды класса добавляются в набор данных.

select a.code, a.class, a.txdate as mindate, b.txdate as maxdate 
from (
    --Find minimum island 
    select code 
     , class 
     , txdate 
     , row_number() over (order by code, class, txdate) as n 
    from #items tb1 
    where not exists (
     select * 
     from #items tb2 
     where datediff(d, tb1.txdate, tb2.txdate) = -1  
      and tb1.class = tb2.class 
      and tb1.code = tb2.code 
    ) 
) as a 
inner join (
    --Find maximum island 
    select code 
     , class 
     , txdate 
     , row_number() over (order by code, class, txdate) as n 
    from #items tb1 
    where not exists (
     select * 
     from #items tb2 
     where datediff(d, tb1.txdate, tb2.txdate) = 1 
      and tb1.class = tb2.class 
      and tb1.code = tb2.code 
    ) 
) as b on a.n = b.n 

Единственное ограничение этого подхода заключается в том, что число записей в минимальном наборе должно соответствовать количеству записей в наборе максимального. До сих пор я не мог сделать ничего, что сделало бы это неправдой. Однако я не тестировал нулевые значения или производительность.

0

Я думаю, вы не можете сделать это с помощью простого оператора выбора.

Вы можете использовать курсор для итерации по строкам и определения изменений класса.

2

EDIT: Как указано в комментариях, это все еще не совсем верно.

;with cteNtile as (
    select code, class, txdate, 
      ntile((select count(*) from (select NULL as dummy from #items group by code, class) a)) over(partition by code, class order by txdate) as tilenum 
     from #items 
) 
select code, class, MIN(txdate) as mindate, MAX(txdate) as maxdate 
    from cteNtile 
    group by code, class, tilenum 
    order by mindate, maxdate 
+0

+1: он возвращает правильный выход. В любом случае, чтобы сделать это без подсчета записей? –

+0

@ k rey: Не то, чтобы я еще мог придумать. –

+0

+1, вы можете удалить локальную переменную и просто использовать: 'ntile ((выберите count (*) from (выберите NULL как фиктивный элемент из группы @items по коду, классу) a))' –

2

Вот запрос, который может дать желаемый результат.

;WITH items1 AS (
SELECT ROW_NUMBER() OVER (ORDER BY txdate) rowid, code, class, txdate 
from #items 
), 
items2 AS (
SELECT ROW_NUMBER() OVER (ORDER BY rowid) id, rowid, i1.Code, i1.Class, i1.txdate 
FROM items1 i1 
WHERE NOT EXISTS (SELECT 1 FROM items1 i2 
        WHERE i2.txdate < i1.txdate 
        AND i2.class = i1.class 
        AND i2.Code = i1.Code 
        AND i2.rowid+1=i1.rowid) 
) 
SELECT items2.code, items2.class, items2.txdate mindate, items1.txdate maxdate 
FROM items2, items2 items3, items1 
WHERE (items2.id+1=items3.id AND items3.rowid-1=items1.rowid) 
OR items2.rowid = (SELECT MAX(t.rowid) FROM items1 t) 
UNION 
SELECT items2.code, items2.class, MAX(items2.txdate) mindate, MAX(items1.txdate) maxdate 
FROM items2, items1 
WHERE items1.class = items2.class 
GROUP BY items1.class, items2.class, items2.code, items2.class 
ORDER BY items2.txdate 
+0

+1: Это работает с заданным результатом.Однако добавление дополнительной комбинации кодов классов приводит к сбою вывода. –

+0

@k rey: ok .. У меня есть причина. Он не работал для таких данных, как (код: B и класс: D). Я пропустил одно сравнение между кодами. Я изменил свой запрос соответствующим образом. Дайте мне знать, если это сработает для вас сейчас. –

Смежные вопросы