考虑行之间“差异”的行分组

时间:2010-11-25 13:01:45

标签: sql oracle oracle11g

我有一个包含开始时间的表格(在示例中使用数字来保持简单)和事件持续时间。

我想识别“块”以及它们的开始和结束时间 每当前一行的结束时间(开始时间+持续时间)(按开始时间排序)与当前行的开始时间之间的差异为>=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 个答案:

答案 0 :(得分:3)

我使用带有分析的子查询来识别和分组连续范围:

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

答案 1 :(得分:2)

代码变得有点复杂,有许多子查询等。可能是数据的实例,这不起作用,但我不能想到任何超出我的头脑。

使用时态数据总是很痛苦!

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

答案 2 :(得分:1)

在MS-SQL中,我会使用ROW_NUMBER() OVER(ORDER BY starttime) AS Rank在开始时间对行进行排名。

然后,我会编写一个查询,将每行连接到具有先前Rank的行,如果差值大于5或NULL(第一行),则设置一个标志。

然后,我将选择具有此标志的所有行作为起始行,并且对于此子集重复编号行并连接到下一行以获得时间跨度的过程:

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

最后,可以使用WHERE starttime BETWEEN blockstarttime and nextstarttime将此数据集连接到原始数据,以对结果进行分区。

由您决定将其转换为Oracle ...

答案 3 :(得分:1)

理查德·斯诺德格拉斯(Richard Snodgrass)有一本很棒的书可能会有所帮助:Developing Time-Oriented Database Applications in SQL(免费下载),我在数据库中处理时间时发现它非常宝贵。

Richards page上查看一些图书更正的链接以及zip格式的相关CD-ROM。