Время присоединения, группировка по идентификатору - PullRequest
0 голосов
/ 27 декабря 2018

У меня есть строки с периодами времени, которые пересекаются для одного и того же пользователя.Например:

-------------------------------------------------------------
|    ID_USER    |     START_DATE      |      END_DATE       |
-------------------------------------------------------------
|       1       | 01/01/2018 08:00:00 | 01/01/2018 08:50:00 |
|       1       | 01/01/2018 08:15:00 | 01/01/2018 08:20:00 |
|       1       | 01/01/2018 08:45:00 | 01/01/2018 09:55:00 |
|       1       | 01/01/2018 15:45:00 | 01/01/2018 17:00:00 |
|       2       | 01/01/2018 08:45:00 | 01/01/2018 09:50:00 |
|       2       | 01/01/2018 09:15:00 | 01/01/2018 10:00:00 |
-------------------------------------------------------------

Я хочу этого избежать.Я хотел бы объединить строки в одном столбце, принимая дату начала как самую старую, а дату окончания - как самую новую.Результатом приведенного выше примера будет:

-------------------------------------------------------------
|    ID_USER    |     START_DATE      |      END_DATE       |
-------------------------------------------------------------
|       1       | 01/01/2018 08:00:00 | 01/01/2018 09:55:00 |
|       1       | 01/01/2018 15:45:00 | 01/01/2018 17:00:00 |
|       2       | 01/01/2018 08:45:00 | 01/01/2018 10:00:00 |
-------------------------------------------------------------

Есть ли у вас какие-либо идеи, как я могу получить решение, которое я хочу, с предложением SQL в Oracle?

Ответы [ 3 ]

0 голосов
/ 27 декабря 2018

У вас есть два типа пересечения;первый, где один период существует полностью внутри другого (например, вторая строка, 08: 15-08: 20), а второй, где один период перекрывает начало или конец другого.

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

select id_user, start_date, end_date,
  case when start_date <= lag(end_date) over (partition by id_user order by start_date)
       then null
       else start_date
  end as calc_start_date,
  case when end_date >= lead(start_date) over (partition by id_user order by end_date)
       then null
       else end_date
  end as calc_end_date
from your_table t1
where not exists (
    select *
    from your_table t2
    where t2.id_user = t1.id_user
    and t2.start_date <= t1.start_date and t2.end_date >= t1.end_date
    and t2.rowid != t1.rowid
);
   ID_USER START_DATE          END_DATE            CALC_START_DATE     CALC_END_DATE         
---------- ------------------- ------------------- ------------------- ----------------------
         1 2018-01-01 08:00:00 2018-01-01 08:50:00 2018-01-01 08:00:00                       
         1 2018-01-01 08:45:00 2018-01-01 09:55:00                     2018-01-01 09:55:00   
         1 2018-01-01 15:45:00 2018-01-01 17:00:00 2018-01-01 15:45:00 2018-01-01 17:00:00   
         2 2018-01-01 08:45:00 2018-01-01 09:50:00 2018-01-01 08:45:00                       
         2 2018-01-01 09:15:00 2018-01-01 10:00:00                     2018-01-01 10:00:00   
         3 2018-01-01 08:00:00 2018-01-01 08:30:00 2018-01-01 08:00:00                       
         3 2018-01-01 08:15:00 2018-01-01 08:45:00                                           
         3 2018-01-01 08:45:00 2018-01-01 09:15:00                                           
         3 2018-01-01 09:00:00 2018-01-01 09:30:00                     2018-01-01 09:30:00   

Предложение not exists убрало первый тип.

Затем вы можете свернуть то, что осталось, сначалаисключение строк, перекрывающих оба конца (в моих дополнительных строках для идентификатора 3), в которых значения опережающих и запаздывающих значений равны нулю;и затем снова использовать опережающие и запаздывающие значения, чтобы заменить оставшиеся пустые значения значениями смежных строк:

select distinct id_user,
  case when calc_start_date is null
       then lag(calc_start_date) over (partition by id_user order by start_date)
       else calc_start_date
  end as start_date,
  case when calc_end_date is null
       then lead(calc_end_date) over (partition by id_user order by end_date)
       else calc_end_date
  end as end_date
from (
  select id_user, start_date, end_date,
    case when start_date <= lag(end_date) over (partition by id_user order by start_date)
         then null
         else start_date
    end as calc_start_date,
    case when end_date >= lead(start_date) over (partition by id_user order by end_date)
         then null
         else end_date
   end as calc_end_date
  from your_table t1
  where not exists (
      select *
      from your_table t2
      where t2.id_user = t1.id_user
      and t2.start_date <= t1.start_date and t2.end_date >= t1.end_date
      and t2.rowid != t1.rowid
  )
)
where calc_start_date is not null
or calc_end_date is not null
order by id_user, start_date, end_date;
   ID_USER START_DATE          END_DATE           
---------- ------------------- -------------------
         1 2018-01-01 08:00:00 2018-01-01 09:55:00
         1 2018-01-01 15:45:00 2018-01-01 17:00:00
         2 2018-01-01 08:45:00 2018-01-01 10:00:00
         3 2018-01-01 08:00:00 2018-01-01 09:30:00

Меня не удивит, если есть крайние случаи, которые я не рассматривал, и какиевызывает проблемы, но, надеюсь, в любом случае станет отправной точкой.

0 голосов
/ 27 декабря 2018

Требуется четыре шага, чтобы получить результат, представленный тремя подзапросами и одним основным запросом:

1) увеличить END_DATE до максимума до настоящего времени

Этотребуется, поскольку ваш END_DATE не упорядочен, например, первая запись пересекается с третьей записью, но вторая запись не пересекается с третьей.

   ID_USER START_DATE          END_DATE          
---------- ------------------- -------------------
         1 01.01.2018 08:00:00 01.01.2018 08:50:00 
         1 01.01.2018 08:15:00 01.01.2018 08:50:00 
         1 01.01.2018 08:45:00 01.01.2018 09:55:00 
         1 01.01.2018 15:45:00 01.01.2018 17:00:00 
         2 01.01.2018 08:45:00 01.01.2018 09:50:00 
         2 01.01.2018 09:15:00 01.01.2018 10:00:00 

2) Определитьновая группа для каждого неперекрывающегося фрагмента

Технически для первой записи (для USER_ID) и для каждой записи, которая не перекрывается с предшественником ist - назначьте новый group_id (GRP)

    ID_USER START_DATE          END_DATE                   GRP
---------- ------------------- ------------------- ----------
         1 01.01.2018 08:00:00 01.01.2018 08:50:00          1 
         1 01.01.2018 08:15:00 01.01.2018 08:50:00            
         1 01.01.2018 08:45:00 01.01.2018 09:55:00            
         1 01.01.2018 15:45:00 01.01.2018 17:00:00          4 
         2 01.01.2018 08:45:00 01.01.2018 09:50:00          1 
         2 01.01.2018 09:15:00 01.01.2018 10:00:00         

3) Заполнить группы

Заполнить NULL с последним идентификатором группы, назначенным для включения GROUP BY.

   ID_USER START_DATE          END_DATE                  GRP2
---------- ------------------- ------------------- ----------
         1 01.01.2018 08:00:00 01.01.2018 08:50:00          1 
         1 01.01.2018 08:15:00 01.01.2018 08:50:00          1 
         1 01.01.2018 08:45:00 01.01.2018 09:55:00          1 
         1 01.01.2018 15:45:00 01.01.2018 17:00:00          4 
         2 01.01.2018 08:45:00 01.01.2018 09:50:00          1 
         2 01.01.2018 09:15:00 01.01.2018 10:00:00          1  

4) GROUP BY

Остальное просто, даты в группе минимальные и максимальные.Вы группируете по kay (ID_USER) и teh GRP.

   ID_USER START_DATE          END_DATE          
---------- ------------------- -------------------
         1 01.01.2018 08:00:00 01.01.2018 09:55:00 
         1 01.01.2018 15:45:00 01.01.2018 17:00:00 
         2 01.01.2018 08:45:00 01.01.2018 10:00:00  

Запрос

with myt1 as (
select ID_USER, START_DATE, 
max(END_DATE) over (partition by ID_USER order by START_DATE) END_DATE
from my_table),
myt2 as (
select ID_USER,START_DATE, END_DATE,
case when (nvl(lag(END_DATE) over (partition by ID_USER order by START_DATE),START_DATE-1) < START_DATE ) then 
     row_number() over (partition by ID_USER order by START_DATE) end grp
from myt1 
), 
myt3 as (
select ID_USER,START_DATE, END_DATE,
last_value(grp ignore nulls) over (partition by ID_USER order by START_DATE) as grp2
from myt2
),
select
ID_USER, 
min(START_DATE) START_DATE, 
max(END_DATE) END_DATE
from myt3
group by ID_USER, GRP2
order by 1,2;

Данные

create table my_table as 
select      1 ID_USER,   to_date('01/01/2018 08:00:00','dd/mm/yyyy hh24:mi:ss') START_DATE, to_date('01/01/2018 08:50:00','dd/mm/yyyy hh24:mi:ss') END_DATE from dual union all
select      1 ID_USER,   to_date('01/01/2018 08:15:00','dd/mm/yyyy hh24:mi:ss') START_DATE, to_date('01/01/2018 08:20:00','dd/mm/yyyy hh24:mi:ss') END_DATE from dual union all
select      1 ID_USER,   to_date('01/01/2018 08:45:00','dd/mm/yyyy hh24:mi:ss') START_DATE, to_date('01/01/2018 09:55:00','dd/mm/yyyy hh24:mi:ss') END_DATE from dual union all
select      1 ID_USER,   to_date('01/01/2018 15:45:00','dd/mm/yyyy hh24:mi:ss') START_DATE, to_date('01/01/2018 17:00:00','dd/mm/yyyy hh24:mi:ss') END_DATE from dual union all
select      2 ID_USER,   to_date('01/01/2018 08:45:00','dd/mm/yyyy hh24:mi:ss') START_DATE, to_date('01/01/2018 09:50:00','dd/mm/yyyy hh24:mi:ss') END_DATE from dual union all
select      2 ID_USER,   to_date('01/01/2018 09:15:00','dd/mm/yyyy hh24:mi:ss') START_DATE, to_date('01/01/2018 10:00:00','dd/mm/yyyy hh24:mi:ss') END_DATE from dual;
0 голосов
/ 27 декабря 2018

Вы ищете функцию MIN / MAX:

SELECT MIN(aggregate_expression),MAX(aggregate_expression)
FROM tables
[WHERE conditions]
GROUP BY ID;

Ссылка: https://www.techonthenet.com/oracle/functions/min.php

...