Группировка строк с учетом «разницы» между строками - PullRequest
5 голосов
/ 25 ноября 2010

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

Я хотел бы определить «блоки» и их начало, а такжевремя окончания.
Всякий раз, когда разница между временем окончания (временем начала + продолжительностью) предыдущей строки (отсортированным по времени начала) и временем начала текущей строки составляет >=5, новый "блок "должен начинаться.

Это мои тестовые данные, включая попытку графического объяснения в комментариях:

WITH test_data AS (
  SELECT  0 s, 2 dur FROM dual UNION ALL   --# ■■
  SELECT  2  , 2     FROM dual UNION ALL   --#   ■■
  SELECT 10  , 1     FROM dual UNION ALL   --#           ■
  SELECT 13  , 4     FROM dual UNION ALL   --#              ■■■■
  SELECT 15  , 4     FROM dual             --#                ■■■■
)
--# Should return
--#   0 ..  4                              --# ■■■■
--#  10 .. 19                              --#           ■■■■■■■■■

Первый блок начинается в 0 и заканчивается в 4.Поскольку разница со следующей строкой составляет >=5, начните другой блок с 10, который заканчивается на 19.


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

И я мог бы решить эту проблему в PL / SQL-цикле, но я пытаюсь избежать этого по соображениям производительности.


Любые предложения о том, как написать этот запрос?

Заранее спасибо, Питер

Ответы [ 4 ]

3 голосов
/ 25 ноября 2010

Я использую подзапросы с аналитикой для определения и группировки смежных диапазонов:

SQL> WITH test_data AS (
  2    SELECT  0 s, 2 dur FROM dual UNION ALL   --# ■■
  3    SELECT  2  , 2     FROM dual UNION ALL   --#   ■■
  4    SELECT 10  , 1     FROM dual UNION ALL   --#           ■
  5    SELECT 13  , 4     FROM dual UNION ALL   --#              ■■■■
  6    SELECT 15  , 4     FROM dual             --#                ■■■■
  7  )
  8  SELECT MIN(s) "begin", MAX(s + dur) "end"
  9    FROM (SELECT s, dur, SUM(gap) over(ORDER BY s) my_group
 10             FROM (SELECT s, dur,
 11                           CASE
 12                              WHEN lag(s + dur) over(ORDER BY s) >= s - 5 THEN
 13                               0
 14                              ELSE
 15                               1
 16                           END gap
 17                      FROM test_data
 18                     ORDER BY s))
 19   GROUP BY my_group;

     begin        end
---------- ----------
         0          4
        10         19
2 голосов
/ 25 ноября 2010

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

Работа с временными данными - это всегда боль!

WITH test_data AS (
  SELECT  0 s, 2 dur FROM dual UNION ALL   --# ■■
  SELECT  2  , 2     FROM dual UNION ALL   --#   ■■
  SELECT 10  , 1     FROM dual UNION ALL   --#           ■
  SELECT 13  , 4     FROM dual UNION ALL   --#              ■■■■
  SELECT 15  , 4     FROM dual             --#                ■■■■
)
select 
-- Group on each block
  min(start_time) as s, 
  max(end_time) - min(start_time) as dur
from (
  select 
    start_time,
    duration, 
    end_time, 
-- number the blocks sequentially 
    sum(is_block_start) over (order by start_time) as block_num
  from (
    select 
      start_time, 
      duration, 
      end_time, 
-- Mark the start of each block
      case 
        when nvl2(prev_end_time, start_time - prev_end_time,5) >= 5 
        then 1 else 0 end as is_block_start
    from (
      select 
        s as start_time, 
        dur as duration, 
        s+dur as end_time,
        lag(s+dur) over (order by s) prev_end_time
      from test_data
    )
  )
)
group by block_num
1 голос
/ 25 ноября 2010

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

Просмотрите на странице Ричардса ссылки на некоторые исправления книг и соответствующий компакт-диск в формате zip.

1 голос
/ 25 ноября 2010

В MS-SQL я бы использовал ROW_NUMBER() OVER(ORDER BY starttime) AS Rank для ранжирования строк по времени начала.

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

Затем я бы выбрал все строки с этим флагом, которые являются начальными строками, и для этого подмножества повторите процесс нумерации строк и соединения со следующей строкой, чтобы получить интервалы времени:

blockstarttime1 nextstarttime1 (=starttime2)
blockstarttime2 nextstarttime2 (=starttime3)
blockstarttime3 NULL

Наконец, этот набор данных может быть присоединен к исходным данным с помощью WHERE starttime BETWEEN blockstarttime and nextstarttime для разделения результатов.

Вам решать перевести это в Oracle ...

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...