Oracle находит и переписывает последовательные строки - PullRequest
0 голосов
/ 28 октября 2018

У меня есть таблица с такими строками, как;

ID  DATE
1   1.01.2018 13:30
1   1.01.2018 13:31
2   1.01.2018 13:32
2   1.01.2018 13:33
1   1.01.2018 13:34
3   1.01.2018 13:35
3   1.01.2018 13:35
3   1.01.2018 13:35
3   1.01.2018 13:36
1   1.01.2018 13:37
3   1.01.2018 13:38
4   1.01.2018 13:39
4   1.01.2018 13:40
1   1.01.2018 13:40

Я хочу найти даты начала и окончания событий.

Желаемый выход;

ID    START_DATE              END_DATE
1   1.01.2018 13:30     1.01.2018 13:31
2   1.01.2018 13:32     1.01.2018 13:33
1   1.01.2018 13:34     1.01.2018 13:34
3   1.01.2018 13:35     1.01.2018 13:36
1   1.01.2018 13:37     1.01.2018 13:37
3   1.01.2018 13:38     1.01.2018 13:38
4   1.01.2018 13:39     1.01.2018 13:40
1   1.01.2018 13:40     1.01.2018 13:40

в назначенную дату, если тот же идентификатор продолжается

  • дата начала = это первая дата

  • дата окончания = это последняя дата изменения идентификатора

Как мне написать этот запрос?

Спасибо.

Ответы [ 4 ]

0 голосов
/ 29 октября 2018

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

SELECT THE_ID,
       TO_CHAR(MIN_DATE , 'MM.DD.YYYY HH24:MI:SS') AS START_DATE,
       TO_CHAR(MAX_DATE , 'MM.DD.YYYY HH24:MI:SS') AS END_DATE
FROM T
       MATCH_RECOGNIZE (
         ORDER BY "DATE"
         MEASURES
           ID AS THE_ID,
           MIN("DATE") AS MIN_DATE,
           MAX("DATE") AS MAX_DATE
         ONE ROW PER MATCH
         AFTER MATCH SKIP PAST LAST ROW
         PATTERN (IN_RUN{0,} END_RUN )
         DEFINE
           IN_RUN AS (ID = NEXT(ID)),
           END_RUN AS ID != ANY (NEXT(ID) , PREV(ID)))
ORDER BY START_DATE ASC, END_DATE ASC;

Результат:

    THE_ID START_DATE          END_DATE
---------- ------------------- -------------------
     1 01.01.2018 13:30:00 01.01.2018 13:31:00
     2 01.01.2018 13:32:00 01.01.2018 13:33:00
     1 01.01.2018 13:34:00 01.01.2018 13:34:00
     3 01.01.2018 13:35:00 01.01.2018 13:36:00
     1 01.01.2018 13:37:00 01.01.2018 13:37:00
     3 01.01.2018 13:38:00 01.01.2018 13:38:00
     4 01.01.2018 13:39:00 01.01.2018 13:40:00
     1 01.01.2018 13:40:00 01.01.2018 13:40:00

8 rows selected.
0 голосов
/ 28 октября 2018

Порядок строк неясен, поскольку для значения даты / времени имеется несколько строк.Поэтому я решил сделать заказ по дате / дате + идентификатор.

Примечание : я изменил имя столбца с date на d, поскольку DATE является зарезервированным словом в Oracle.

Если ваши данные:

create table t (
  id number(6),
  d date
);

insert into t (id, d) values (1, timestamp '2018-01-01 13:30:00');
insert into t (id, d) values (1, timestamp '2018-01-01 13:31:00');
insert into t (id, d) values (2, timestamp '2018-01-01 13:32:00');
insert into t (id, d) values (2, timestamp '2018-01-01 13:33:00');
insert into t (id, d) values (1, timestamp '2018-01-01 13:34:00');
insert into t (id, d) values (3, timestamp '2018-01-01 13:35:00');
insert into t (id, d) values (3, timestamp '2018-01-01 13:35:00');
insert into t (id, d) values (3, timestamp '2018-01-01 13:35:00');
insert into t (id, d) values (3, timestamp '2018-01-01 13:36:00');
insert into t (id, d) values (1, timestamp '2018-01-01 13:37:00');
insert into t (id, d) values (3, timestamp '2018-01-01 13:38:00');
insert into t (id, d) values (4, timestamp '2018-01-01 13:39:00');
insert into t (id, d) values (4, timestamp '2018-01-01 13:40:00');
insert into t (id, d) values (1, timestamp '2018-01-01 13:40:00');

Решение для вашего запроса может быть:

with x as (
select
    t.*,
    case when id = lag(id) over(order by d, id) then 0 else 1 end as ini,
    case when id = lead(id) over(order by d, id) then 0 else 1 end as fin
  from t  
),
y as (
select * from x where ini <> 0 or fin <> 0
)
select
    id,
    d as start_date,
    case when fin = 1 then d else lead(d) over (order by d, id) end as end_date
  from y where ini = 1

Результат:

ID  START_DATE             END_DATE
--  ---------------------  ---------------------
1   2018-01-01 13:30:00.0  2018-01-01 13:32:00.0
2   2018-01-01 13:32:00.0  2018-01-01 13:34:00.0
1   2018-01-01 13:34:00.0  2018-01-01 13:34:00.0
3   2018-01-01 13:35:00.0  2018-01-01 13:37:00.0
1   2018-01-01 13:37:00.0  2018-01-01 13:37:00.0
3   2018-01-01 13:38:00.0  2018-01-01 13:38:00.0
4   2018-01-01 13:39:00.0  2018-01-01 13:39:00.0
1   2018-01-01 13:40:00.0  2018-01-01 13:40:00.0
4   2018-01-01 13:40:00.0  2018-01-01 13:40:00.0
0 голосов
/ 28 октября 2018

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

Шаг 1 - Упорядочить строки по отметке времени и использовать LEAD, чтобы выяснить, когда заканчивается каждая «группа».То есть, когда значение id изменяется в следующей строке.Пометьте любую строку как «Y», если это правда.

Шаг 2 - Посчитайте отмеченные значения «Y» до текущей строки.Этот счет будет «номером группы».Это дает каждой последовательной группе с одинаковым идентификатором один и тот же «номер группы».

Шаг 3 - Теперь возьмите отметку времени min и max в каждой «группе» как время начала и окончанияэто событие.

Возможно, оно не так компактно и круто, как другие возможные решения, но у меня гораздо больше шансов вспомнить, как оно работало, когда я вернусь к нему через 6 месяцев.Это всего лишь я.

Здесь все вместе.

WITH input (id, ts) AS (
SELECT 1, TO_DATE(  '01.01.2018 13:30','DD.MM.YYYY HH24:MI') FROM DUAL UNION ALL
SELECT 1, TO_DATE(  '01.01.2018 13:31','DD.MM.YYYY HH24:MI') FROM DUAL UNION ALL
SELECT 2, TO_DATE(  '01.01.2018 13:32','DD.MM.YYYY HH24:MI') FROM DUAL UNION ALL
SELECT 2, TO_DATE(  '01.01.2018 13:33','DD.MM.YYYY HH24:MI') FROM DUAL UNION ALL
SELECT 1, TO_DATE(  '01.01.2018 13:34','DD.MM.YYYY HH24:MI') FROM DUAL UNION ALL
SELECT 3, TO_DATE(  '01.01.2018 13:35','DD.MM.YYYY HH24:MI') FROM DUAL UNION ALL
SELECT 3, TO_DATE(  '01.01.2018 13:35','DD.MM.YYYY HH24:MI') FROM DUAL UNION ALL
SELECT 3, TO_DATE(  '01.01.2018 13:35','DD.MM.YYYY HH24:MI') FROM DUAL UNION ALL
SELECT 3, TO_DATE(  '01.01.2018 13:36','DD.MM.YYYY HH24:MI') FROM DUAL UNION ALL
SELECT 1, TO_DATE(  '01.01.2018 13:37','DD.MM.YYYY HH24:MI') FROM DUAL UNION ALL
SELECT 3, TO_DATE(  '01.01.2018 13:38','DD.MM.YYYY HH24:MI') FROM DUAL UNION ALL
SELECT 4, TO_DATE(  '01.01.2018 13:39','DD.MM.YYYY HH24:MI') FROM DUAL UNION ALL
SELECT 4, TO_DATE(  '01.01.2018 13:40','DD.MM.YYYY HH24:MI') FROM DUAL UNION ALL
SELECT 1, TO_DATE(  '01.01.2018 13:40','DD.MM.YYYY HH24:MI') FROM DUAL ), 
-- Solution starts here
input_with_group_markers as (
SELECT id, ts,
case when lead(id,1) over ( order by ts ) != id THEN 'Y' ELSE NULL END last_row_in_group
FROM input
),
grouped_input as (
SELECT igwm.*, count(last_row_in_group) OVER ( order by ts rows between unbounded preceding and 1 preceding ) group_number
FROM input_with_group_markers igwm )
SELECT min(id) id, 
       to_char(min(ts),'DD.MM.YYYY HH24:MI') event_start, 
       to_char(max(ts),'DD.MM.YYYY HH24:MI') event_end
FROM grouped_input
group by group_number
order by group_number;
0 голосов
/ 28 октября 2018

Это проблема пробелов и островков.Для этой версии я рекомендую разницу номеров строк:

select id, min(date), max(date)
from (select t.*,
             row_number() over (order by date) as seqnum,
             row_number() over (partition by id order by date) as seqnum_i
      from t
     ) t
group by id, (seqnum - seqnum_i);

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

select id, min(dte), max(dte)
from (select t.*,
             row_number() over (order by dte) as seqnum,
             row_number() over (partition by id order by dte) as seqnum_i
      from (select distinct id, dte from t) t
     ) t
group by id, (seqnum - seqnum_i)

(см. Db <> fiddle здесь ) Из-за этой проблемы в db <> fiddle есть две строки для «4».

Но связи делают проблему неопределенной.

РЕДАКТИРОВАТЬ:

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

Итак:

select id, min(dte), max(dte)
from (select t.*,
             sum(case when prev_id_dte = prev_dte then 0 else 1 end) over (partition by id order by dte) as grp
      from (select t.*,
                   lag(dte) over (partition by id order by dte) as prev_id_dte,
                   (select max(dte) from t t2 where t2.dte < t.dte) as prev_dte
            from (select distinct id, dte
                  from t
                 ) t
           ) t
     ) t
group by id, grp;

Здесь - это скрипта db <> для этой версии.

Я не в восторгес подзапросом.Но я не думаю, что есть простой способ получить предыдущее значение из сгруппированных данных, используя оконные функции.Есть метод, но он требует нескольких уровней вложенности.Подзапрос проще.

...