Сравнение двух наборов диапазонов дат в SQL - PullRequest
3 голосов
/ 05 января 2011

У меня есть два набора данных с разными диапазонами дат.

Tbl 1:  
ID, Date_Start, Date_End
1, 2010-01-01, 2010-01-09
1, 2010-01-10, 2010-01-19
1, 2010-01-30, 2010-01-31

Tbl 2:
ID, Date_Start, Date_End
1, 2010-01-01, 2010-01-04
1, 2010-01-08, 2010-01-17
1, 2010-01-30, 2010-01-31

Я хотел бы найти случаи, когда диапазоны дат не полностью перекрывают диапазоны дат в Таблице 2. Так, например, в этом примере,Мне бы хотелось, чтобы выходные данные выглядели примерно так -

Output:
ID, Gap_Start, Gap_End
1, 2010-01-05, 2010-01-07
1, 2010-01-18, 2010-01-19

Диапазоны дат никогда не будут перекрываться в таблице.Для этого я использую DB2 SQL или SAS.К сожалению, наборы данных достаточно велики (миллионы записей), и я не могу их просто перебрать.

Спасибо!

Ответы [ 4 ]

1 голос
/ 11 января 2011

Это не полное решение, поскольку оно возвращает список дат, а не диапазонов, но, возможно, оно будет полезно:

SELECT
  R1.ID, D.Date
FROM
  #Ranges1 AS R1
  INNER JOIN Dates AS D ON D.Date BETWEEN R1.StartDate AND R1.EndDate
EXCEPT
SELECT
  R2.ID, D.Date
FROM
  #Ranges2 AS R2
  INNER JOIN Dates AS D ON D.Date BETWEEN R2.StartDate AND R2.EndDate

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

1 голос
/ 13 января 2011

Исходя из подхода Джона на все руки, это более законченное решение.Ключевыми функциями являются:

  1. Использование вспомогательной календарной таблицы , которая представляет собой просто список всех дат.
  2. Из таблицы календаря ПРИСОЕДИНЯЙТЕСЬ к Tbl1 дополучить список дат, находящихся в диапазоне.
  3. Также выполните anti-JOIN для Tbl2, чтобы получить только даты, не входящие в диапазоны Tbl2.
  4. Iзаключили эти результаты в Общее табличное выражение (CTE) с именем OutDates.
  5. Определите еще один CTE на основе OutDates, чтобы получить только даты, начинающие пробел;вызовите это как можно раньше.
  6. Определите другой CTE на основе OutDates, чтобы получить только даты, которые заканчивают пробел;вызовите это LatestDates.
  7. JOIN EarliestDates и LatestDates, чтобы поместить каждый пробел в одну строку.

WITH
OutDates(ID, dt) AS
( SELECT Tbl1.ID, Calendar.dt FROM Calendar
INNER JOIN Tbl1 ON Calendar.dt BETWEEN Tbl1.Date_Start AND Tbl1.Date_End
LEFT OUTER JOIN Tbl2 ON Calendar.dt BETWEEN Tbl2.Date_Start AND Tbl2.Date_End
WHERE Tbl2.ID IS NULL
)
,
EarliestDates AS
(   SELECT earliest.ID, earliest.dt FROM OutDates earliest
    LEFT OUTER JOIN OutDates nonesuch_earlier ON DateAdd(day, -1, earliest.dt) = nonesuch_earlier.dt
    WHERE nonesuch_earlier.ID IS NULL
)
,
LatestDates AS
(   SELECT latest.ID, latest.dt FROM OutDates latest
    LEFT OUTER JOIN OutDates nonesuch_later ON DATEADD(day, 1, latest.dt) = nonesuch_later.dt
    WHERE nonesuch_later.ID IS NULL
)
SELECT rangestart.ID, rangestart.dt AS Gap_Start, rangeend.dt AS Gap_End 
 FROM EarliestDates rangestart JOIN LatestDates rangeend
 ON rangestart.dt <= rangeend.dt
LEFT OUTER JOIN EarliestDates nonesuch_inner1
 ON nonesuch_inner1.dt <= rangeend.dt AND nonesuch_inner1.dt > rangestart.dt 
LEFT OUTER JOIN LatestDates nonesuch_inner2
 ON nonesuch_inner2.dt >= rangestart.dt AND nonesuch_inner2.dt < rangeend.dt
WHERE nonesuch_inner1.dt IS NULL AND nonesuch_inner2.dt IS NULL

Это рабочая реализация, использующая синтаксис сервера SQL для общеготабличные выражения, но их легко преобразовать в синтаксис DB2.Я не знаю, насколько хорошо он масштабируется, если честно, я тестировал его только с очень маленьким набором данных.

1 голос
/ 07 января 2011

Я не думаю, что есть эффективное и общее решение для всех случаев. Однако при определенных обстоятельствах мы можем выяснить некоторые эффективные. Например, ниже предполагается, что: (1) наборы данных один и два имеют одинаковый набор идентификаторов в одном и том же порядке; и (2) существуют относительно короткие возможные диапазоны дат (здесь предполагается, что все даты только в 2010 году). Обратите внимание, что один входной диапазон может генерировать два пробела.

/* test data */
data one;
  input id1 (start1 finish1) (:anydtdte.);
  format start1 finish1 e8601da.;
cards;
1 2010-01-01 2010-01-09
1 2010-01-10 2010-01-19
1 2010-01-30 2010-01-31
2 2010-01-02 2010-01-10
;
run;

data two;
  input id2 (start2 finish2) (:anydtdte.);
  format start2 finish2 e8601da.;
cards;
1 2010-01-01 2010-01-04
1 2010-01-08 2010-01-17
1 2010-01-30 2010-01-31
2 2010-01-05 2010-01-06
;
run;


/* assumptions:
   (1) datasets one and two have the same set of ids in the same
       sorted order;
   (2) only possible dates are in the year of 2010
*/
%let minDate = %sysevalf('01jan2010'd - 1);
%let maxDate = %sysevalf('31dec2010'd + 1);

data gaps;

  array inRange[&minDate:&maxDate] _temporary_;
  array covered[&minDate:&maxDate] _temporary_;
  do i = &minDate to &maxDate; inRange[i] = 0; covered[i] = 0; end;

  do until (last.id1);
    set one;
    by id1;
    do i = start1 to finish1; inRange[i] = 1; end;
  end;

  do until (last.id2);
    set two;
    by id2;
    do i =  start2 to finish2; covered[i] = 1; end;
  end;

  format startGap finishGap e8601da.;
  startGap = .;
  finishGap = .;
  do i = &minDate+1 to &maxDate;
    if inRange[i] and not covered[i] and missing(startGap) then startGap = i;
    if (covered[i] or not inRange[i]) and not missing(startGap) and not covered[i-1] then do;
      finishGap = i - 1;
      output;
      call missing(startGap, finishGap);
      keep id1 startGap finishGap;
    end;
  end;     
run;

/* check */
proc print data=gaps noobs;
run; 
/* on lst 
id1     startGap     finishGap

 1     2010-01-05    2010-01-07
 1     2010-01-18    2010-01-19
 2     2010-01-02    2010-01-04
 2     2010-01-07    2010-01-10
*/
0 голосов
/ 18 января 2011

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

Шаг 1 - Я объединил диапазоны дат в обоих наборах данных.Это означает, что что-то вроде

ID, Start_Date, End_Date
1,  2010-01-01, 2010-01-31
1,  2010-02-01, 2010-02-28

было преобразовано в это -

ID, Start_Date, End_Date
1,  2010-01-01, 2010-02-28.

Запрос, который я использовал для этого, был -

WITH Cte_recomb (Id, Start_date, End_date, Hopcount) AS
        (SELECT Id,
                Start_date,
                End_date,
                1 AS Hopcount
         FROM Table1
         UNION ALL
         SELECT Cte_recomb.Id,
                Cte_recomb.Start_date,
                Table1.End_date,
                (Recomb.Hopcount + 1) AS Hopcount
         FROM Cte_recomb, Table1
         WHERE (Cte_recomb.Id = Table1.Id) AND
               (Cte_recomb.End_date + 1 day = Table1.Start_date)),
     Cte_maxenddate AS
        (SELECT Id,
                Start_date,
                Max (End_date) AS End_date
         FROM Cte_recomb
         GROUP BY Id, Start_date
         ORDER BY Id, Start_date)
SELECT Maxend.*
FROM    Cte_maxenddate AS Maxend
     LEFT JOIN
        Cte_recomb AS Nextrec
     ON (Nextrec.Id = Maxend.Id) AND
        (Nextrec.Start_date < Maxend.Start_date) AND
        (Nextrec.End_date >= Maxend.End_date)
WHERE Nextrec.Id IS NULL;

Шаг 2-

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

SELECT Table1.Id,
       Table1.Start_date AS Table1_start_date,
       Table1.End_date AS Table1_end_date,
       Table2.Start_date AS Table2_start_date,
       Table2.End_date AS Table2_end_date
FROM    Table1
     INNER JOIN
        Table2
     ON (Table1.Plcy_id_sk = Id) AND
        ( (Table1.Start_date BETWEEN Table2.Start_date AND Table2.End_date) OR
         (Table2.Start_date  BETWEEN Table1.Start_date AND Table1.End_date)) AND
        ( (Table1.Start_date <> Table2.Start_date) OR
         (Table1.End_date    <> Table2.End_date))
ORDER BY Table1.Id, Table1.Start_date, Table2.Start_date;

Шаг 3 -

Я берувыше набора данных и запустите следующее задание SAS.Я пытался сделать это на чистом SQL с помощью рекурсивных запросов, но он становился все уродливее и уродливее каждый раз, когда я на него смотрел.дал мне результаты, которые я хотел.Есть мысли?

...