Сгруппируйте по кускам и получите время начала и окончания каждого - SQL - PullRequest
0 голосов
/ 23 апреля 2020

У меня проблемы с подготовкой вывода базы данных для моей визуализации.

У меня есть таблица (в Oracle SQL), которая отслеживает, какой (столбец 3) каждый компьютер (столбец 1) на моем сеть делает и когда началось действие (колонка 2). См. Ниже:

# comps   ~ 20
# actions - 10
# rows    ~ 10_000

comp   |  time  |  action
-------+--------+----------
comp_1 | t_0    |    A
comp_1 | t_1    |    A
comp_2 | t_2    |    B
comp_1 | t_3    |    B
comp_1 | t_4    |    A
comp_2 | t_5    |    B
comp_2 | t_6    |    B
comp_1 | t_7    |    A
comp_1 | t_8    |    A
comp_2 | t_9    |    C
comp_2 | t_10   |    C
comp_1 | t_11   |    C
 ...     ...        ...
  .       .          . 

Конец одного действия - начало другого действия. Где B начинается A заканчивается. A может затем снова присутствовать в виде нового «чанка» после окончания B. Компьютер может выполнять только одно действие за раз.

Значения времени действительно являются столбцом Oracle datetime (для упрощения он записывается только как t_x).

Мне нужна помощь для группировки таблицы по куски компьютерного действия, с началом и концом каждого. t_start - время начала запроса (с), а t_end - конец (с).

Это мой желаемый вывод:

comp   | action  | start   | end
-------+---------+---------+----------
comp_1 |   A     |  t_start|  t_3
comp_2 |   B     |  t_2    |  t_9
comp_1 |   B     |  t_3    |  t_4
comp_1 |   A     |  t_4    |  t_11
comp_2 |   C     |  t_9    |  t_end
comp_1 |   C     |  t_11   |  t_end

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

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

Большое спасибо заранее.

Ответы [ 2 ]

1 голос
/ 23 апреля 2020

Это можно сделать с помощью сопоставления с шаблоном:

create table t (
  comp varchar2(10),
  time varchar2(10),
  action varchar2(1)
);

insert into t values ( 'comp_1', 't_00', 'A' );
insert into t values ( 'comp_1', 't_01', 'A' );
insert into t values ( 'comp_2', 't_02', 'B' );
insert into t values ( 'comp_1', 't_03', 'B' );
insert into t values ( 'comp_1', 't_04', 'A' );
insert into t values ( 'comp_2', 't_05', 'B' );
insert into t values ( 'comp_2', 't_06', 'B' );
insert into t values ( 'comp_1', 't_07', 'A' );
insert into t values ( 'comp_1', 't_08', 'A' );
insert into t values ( 'comp_2', 't_09', 'C' );
insert into t values ( 'comp_2', 't_10', 'C' );
insert into t values ( 'comp_1', 't_11', 'C' );
commit;

select comp, st, 
       lead ( st ) over (
         partition by comp
         order by st
       ) en
from   t
  match_recognize (
    partition by comp
    order by time
    measures
      first ( time ) st
    pattern ( init same* )
    define
      same as action = prev ( action )
  );

COMP      ST      EN       
comp_1    t_00    t_03      
comp_1    t_03    t_04      
comp_1    t_04    t_11      
comp_1    t_11    <null>    
comp_2    t_02    t_09      
comp_2    t_09    <null>  
  • same as action = prev ( action ) имеет значение true, если действие для текущей строки совпадает с действием для предыдущей строки (определяется разделом и порядок по)
  • Шаблон является регулярным выражением, поэтому init same* является одним экземпляром init, за которым следует ноль или более same
  • init не определено, поэтому имеет значение «всегда верно», соответствует первой строке, затем любой строке, действие которой отличается от предыдущей строки
  • lead в операции выбора, возвращает значение для следующего начала
0 голосов
/ 23 апреля 2020

ОК, не думал, что у меня будет время написать это, отсюда и комментарий, но:

Давайте возьмем данные и поместим столбец с 0 или 1 в зависимости от того, было ли действие предыдущей строки одинаковым. или другой, для каждого компьютера:

SELECT
  t.*,
  CASE WHEN action = LAG(action) OVER(PARTITION BY computer ORDER BY time) THEN 0 ELSE 1 END as differs
FROM
  t

Теперь давайте превратим поток 0 и 1 в инкрементный счетчик:

WITH cte AS(
  SELECT
    t.*,
    CASE WHEN action = LAG(action) OVER(PARTITION BY comp ORDER BY time) THEN 0 ELSE 1 END as differs
  FROM
    t
)

SELECT cte.*, SUM(differs) OVER(PARTITION BY comp ORDER BY time ROWS UNBOUNDED PRECEDING) as run_tot_dif

Теперь давайте сгруппируемся по этому счетчику для каждого компьютера и возьмем мин. / макс раз. Я отмечаю, что ваше время - это не конец текущего действия, а начало следующего действия. Для этого мы добавим функцию, которая выбирает следующий раз для каждого компьютера для каждой строки в первом cte, чтобы мы могли выбрать его позже, и мы также COALESCE это так, чтобы нули, которые это вызывает в конце периода времени стать:

WITH cte_diff AS(
  SELECT
    t.*,
    COALESCE(LEAD(time) OVER(PARTITION BY comp ORDER BY time), :endtime) next_start_time,
    CASE WHEN action = LAG(action) OVER(PARTITION BY comp ORDER BY time) THEN 0 ELSE 1 END as differs
  FROM
    t
  WHERE time BETWEEN :starttime and :endtime --parameterize it
), 
cte_runtot AS(
 SELECT 
   cte_diff.*, 
   SUM(differs) OVER(PARTITION BY comp ORDER BY time ROWS UNBOUNDED PRECEDING) as run_tot_dif
 FROM
   cte_diff
)

SELECT 
  comp, 
  action, 
  MIN(time) as start_time, 
  MAX(next_start_time) as end_time
FROM
  cte_runtot
GROUP BY
  comp, action, run_tot_dif
ORDER BY
  start_time

Я хотел бы отметить, что я думаю, что вы допустили ошибку в желаемом выводе - действие comp2 B выполнялось до тех пор, пока comp2 не начало действие C при t = 9, а не до тех пор, пока comp1 не начало действие A при t = 7 .. Если я не понял ваши требования

пс; Вы можете несколько свернуть его, не используя CTE, но это может не означать, что он более читабелен:

SELECT 
  comp, 
  action, 
  MIN(time) as start_time, 
  MAX(next_start) as end_time
FROM
(
  SELECT 
    x.*, 
    SUM(diff) OVER(PARTITION BY comp ORDER BY time ROWS UNBOUNDED PRECEDING) as run_tot_diff 
  FROM
    (
      SELECT 
        t.*, 
        COALESCE(LEAD(time) OVER(PARTITION BY comp ORDER BY time), 9999) next_start,
        CASE WHEN action = LAG(action) OVER(PARTITION BY comp ORDER BY time) THEN 0 ELSE 1 END diff 
        FROM t
        WHERE time BETWEEN 0 and 11
    ) x
  )y
GROUP BY
  comp, action, run_tot_diff
ORDER BY
  start_time

Это не может быть значительно уменьшено, потому что Oracle не разрешает оконные функции в GROUP BY, внутри других оконных функций или внутри агрегатов (SQL Сервер с этим справляется меньше)

...