Я пытаюсь сгруппировать некоторые данные, используя только один запрос, который использует даты между строками. Позвольте мне привести пример:
ДАННЫЕ
IDE DATE
------ ----------
AA1111 23-05-2016
AA1111 25-05-2016
AA1111 25-05-2016
AA1111 13-09-2016
AA1111 02-11-2016
AA1111 23-11-2016
AA1111 06-02-2017
AA1111 06-06-2017
AA1111 01-09-2017
AA1111 12-10-2017
AA1111 17-04-2018
AA1111 25-05-2018
AA1111 05-06-2018
Я хочу сгруппировать даты, когда разница составляет менее 16 дней. Я уже рассчитываю разницу между датой и следующей датой с помощью:
SELECT T.IDE,
T.DATE,
MAX(T.DATE) OVER (ORDER BY DATE ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) - T.DATE AS DIF
FROM TESTPAT1 T ;
ВЫХОД 1
IDE DATE DIF
------ ---------- ---
AA1111 23-05-2016 2
AA1111 25-05-2016 0
AA1111 25-05-2016 111
AA1111 13-09-2016 50
AA1111 02-11-2016 21
AA1111 23-11-2016 75
AA1111 06-02-2017 120
AA1111 06-06-2017 87
AA1111 01-09-2017 41
AA1111 12-10-2017 187
AA1111 17-04-2018 38
AA1111 25-05-2018 11
AA1111 05-06-2018 0
Отсюда я мог бы использовать разницу между строками, но окно в 16 дней - моя проблема, поскольку каждая дата в группе должна быть внутри этого окна, начиная с первой даты окна.
Некоторые вещи, на которые следует обратить внимание: даты отсортированы по возрастанию, и мой ожидаемый результат будет таким:
ОЖИДАЕМЫЙ РЕЗУЛЬТАТ
IDE DATE GROUP
AA1111 23-05-2016 1
AA1111 25-05-2016 1
AA1111 25-05-2016 1
AA1111 13-09-2016 2
AA1111 02-11-2016 3
AA1111 23-11-2016 4
AA1111 06-02-2017 5
AA1111 06-06-2017 6
AA1111 01-09-2017 7
AA1111 12-10-2017 8
AA1111 17-04-2018 9
AA1111 25-05-2018 10
AA1111 05-06-2018 10
ПРИМЕЧАНИЕ: ЭТО НЕ НАСТОЯЩИЕ ИМЕНА ПЕРЕМЕННЫХ


Посмотрите на предыдущий ряд. Посмотрите, не превышает ли разница дат 16 дней. Если да, то начинается новая группа. Тогда идентификатор группы представляет собой сумму этих значений «начальной группы».
В SQL:
select t.*,
sum(case when prev_date > date - interval '16' day then 0 else 1 end) over (partition by ide order by date) as grp
from (select t.*,
lag(date) over (partition by ide order by date) as prev_date
from TESTPAT1 T
) t;
Примечание. Это предполагает, что вам действительно нужны отдельные группы для каждого ide. Если это не так, то удалите пункты partition by.
Проблема заключается не в сравнении со следующей строкой, а в сравнении с неизвестным количеством строк на основе разницы дат.
@ Роландок . . . Я думал, вам нужны скользящие периоды, поэтому, если бы каждый день января был в этих данных, то все они были бы в одной группе.
Это сложная проблема для объяснения, так как я не говорю по-английски. Если бы в данных были все дни января, то с 1 по 16 должна быть группа 1, остальные должны быть группой 2
@ Роландок . . . Я понимаю. Для этого вам нужен рекурсивный или иерархический запрос. Если у вас много данных, это может быть не очень эффективно.
Случаи того, что я объясняю, на самом деле являются некоторыми исключениями, поэтому я собираюсь подойти к проблеме с этим решением.
Это то, что известно как проблема «подгонки битов». В вашем случае вы пытаетесь поместить свои данные в группы, каждая из которых может содержать данные за 16 дней.
Существует несколько хорошо известных способов использования SQL для решения проблем с подгонкой контейнеров. MATCH RECOGNIZE так же хорош, как любой из них:
with test_data (IDE, "DATE") AS (
SELECT 'AA1111', TO_DATE('23-05-2016','DD-MM-YYYY') FROM DUAL UNION ALL
SELECT 'AA1111', TO_DATE('25-05-2016','DD-MM-YYYY') FROM DUAL UNION ALL
SELECT 'AA1111', TO_DATE('25-05-2016','DD-MM-YYYY') FROM DUAL UNION ALL
SELECT 'AA1111', TO_DATE('13-09-2016','DD-MM-YYYY') FROM DUAL UNION ALL
SELECT 'AA1111', TO_DATE('02-11-2016','DD-MM-YYYY') FROM DUAL UNION ALL
SELECT 'AA1111', TO_DATE('23-11-2016','DD-MM-YYYY') FROM DUAL UNION ALL
SELECT 'AA1111', TO_DATE('06-02-2017','DD-MM-YYYY') FROM DUAL UNION ALL
SELECT 'AA1111', TO_DATE('06-06-2017','DD-MM-YYYY') FROM DUAL UNION ALL
SELECT 'AA1111', TO_DATE('01-09-2017','DD-MM-YYYY') FROM DUAL UNION ALL
SELECT 'AA1111', TO_DATE('12-10-2017','DD-MM-YYYY') FROM DUAL UNION ALL
SELECT 'AA1111', TO_DATE('17-04-2018','DD-MM-YYYY') FROM DUAL UNION ALL
SELECT 'AA1111', TO_DATE('25-05-2018','DD-MM-YYYY') FROM DUAL UNION ALL
SELECT 'AA1111', TO_DATE('05-06-2018','DD-MM-YYYY') FROM DUAL )
SELECT ide, "DATE", mno as "GROUP"
FROM test_data
match_recognize (
partition by ide
order by "DATE"
measures
match_number() as mno,
"DATE" - FIRST(GRP."DATE") as dif
all rows per match
pattern ( grp* )
define
grp AS "DATE" - FIRST("DATE") < 16
);
+--------+-----------+-------+ | IDE | DATE | GROUP | +--------+-----------+-------+ | AA1111 | 23-MAY-16 | 1 | | AA1111 | 25-MAY-16 | 1 | | AA1111 | 25-MAY-16 | 1 | | AA1111 | 13-SEP-16 | 2 | | AA1111 | 02-NOV-16 | 3 | | AA1111 | 23-NOV-16 | 4 | | AA1111 | 06-FEB-17 | 5 | | AA1111 | 06-JUN-17 | 6 | | AA1111 | 01-SEP-17 | 7 | | AA1111 | 12-OCT-17 | 8 | | AA1111 | 17-APR-18 | 9 | | AA1111 | 25-MAY-18 | 10 | | AA1111 | 05-JUN-18 | 10 | +--------+-----------+-------+
MODELЭтот запрос должен работать на 11g, чтобы решить вашу проблему с установкой мусорного ведра. Те же результаты, что и выше, просто другой подход.
with
-- First, sort the input data because we need to be able to refer
-- to the prior row and `lag` doesn't really work in `MODEL`, afaik.
sorted_inputs ( ide, sort_order, "DATE", first_date_in_group, grp, diff) as
( SELECT ide,
row_number() over ( partition by ide order by "DATE" ) sort_order,
"DATE",
-- These columns are place holders for the MODEL clause to update
CAST(NULL AS DATE) first_date_in_group,
0 grp,
0 diff
FROM test_data )
SELECT ide, "DATE", grp "GROUP"
from sorted_inputs
model
partition by (ide)
dimension by (sort_order)
measures ( "DATE", grp, first_date_in_group, diff )
rules update automatic order
( grp[1] = 1,
first_date_in_group[1] = "DATE"[1],
diff[ANY] = "DATE"[CV()] - first_date_in_group[CV()-1],
grp[sort_order>1] = grp[cv()-1] + CASE WHEN diff[CV()] > 16 THEN 1 ELSE 0 END,
first_date_in_group[sort_order>1] = CASE WHEN diff[CV()] > 16 THEN "DATE"[CV()] ELSE first_date_in_group[CV()-1] END
)
Спасибо за ответ. Кажется, это хороший ответ, но я использую 11g, поэтому не могу его использовать.
Вы также можете использовать предложение MODEL. Я могу обновить свой ответ, чтобы показать это позже, если у меня будет время.
@Rolandocq Я обновил свой ответ решением для 11g.
Вау, это то, что мне было нужно, спасибо
Это довольно близко, однако, если бы дата
'09-06-2016'была в данных, ей было бы присвоено значение 1, а поскольку'23-05-2016'является первой датой окна, а разница между ними составляет 17, дата'09-06-2016'должна быть группой 2.