【问题标题】:Oracle 11.2 SQL - help to condense data in ordered setOracle 11.2 SQL - 帮助在有序集中压缩数据
【发布时间】:2016-02-01 09:57:01
【问题描述】:

我有一个包含时间戳列和多个标识符列的数据集。当按时间戳排序时,我想将具有相同标识符的相邻行的每个“块”压缩为一行。每个区块的最小和最大时间戳是必需的。

源数据:

TSTAMP  ID1  ID2
t1      A    B  <= start of new block
t2      A    B
t3      C    D  <= start of new block
t4      E    F  <= start of new block
t5      E    F
t6      E    F
t7      A    B  <= start of new block
t8      G    H  <= start of new block

期望的结果:

MIN_TSTAMP  MAX_TSTAMP  ID1  ID2
t1          t2          A    B
t3          t3          C    D
t4          t6          E    F
t7          t7          A    B
t8          t8          G    H

我认为这对于一个窗口分析函数来说已经成熟了,但是如果不将 IDn 的所有相等组合分组,我就无法进行分区——而不仅仅是相邻行中的那些,当按时间戳排序时。

一种解决方法是首先在一个内联视图中创建一个键列,我稍后可以按该键进行分组,即块中的每一行具有相同的值,而每个块的值不同。我可以使用 LAG 分析函数来比较行值,然后调用 PL/SQL 函数来返回序列的 nextval/currval 值(在此上下文中限制直接在 SQL 中调用 nextval/currval)。

select min(ilv.tstamp), max(ilv.tstamp), id1, id2
from (
  select case when (id1 != lag(id1,1,'*') over (partition by (1) order by tstamp) 
                 or id2 != lag(id2,1,'*') over (partition by (1) order by tstamp))
           then
             pk_seq_utils.gav_get_nextval
           else
             pk_seq_utils.gav_get_currval
           end ident, t.*
  from tab1 t
  order by tstamp) ilv
group by ident, id1, id2
order by 1;

gav_get_xxx 函数只是从序列中返回 currval/nextval。

但我想只使用 SQL 并避免使用 PL/SQL(因为我也可以在 PL/SQL 中轻松编写它并从管道函数中输出结果行)。

有什么想法吗?

谢谢。

【问题讨论】:

标签: sql oracle gaps-and-islands


【解决方案1】:

Tabibitosan 来救援!

with sample_data as (select 't1' tstamp, 'A' id1, 'B' id2 from dual union all
                     select 't2' tstamp, 'A' id1, 'B' id2 from dual union all
                     select 't3' tstamp, 'C' id1, 'D' id2 from dual union all
                     select 't4' tstamp, 'E' id1, 'F' id2 from dual union all
                     select 't5' tstamp, 'E' id1, 'F' id2 from dual union all
                     select 't6' tstamp, 'E' id1, 'F' id2 from dual union all
                     select 't7' tstamp, 'A' id1, 'B' id2 from dual union all
                     select 't8' tstamp, 'G' id1, 'H' id2 from dual)
select   min(tstamp) min_tstamp, max(tstamp) max_tstamp, id1, id2
from     (select tstamp,
                 id1,
                 id2,
                 row_number() over (order by tstamp) - row_number() over (partition by id1, id2 order by tstamp) grp
          from   sample_data)
group by id1,
         id2,
         grp
order by min(tstamp);

MIN_TSTAMP MAX_TSTAMP ID1 ID2
---------- ---------- --- ---
t1         t2         A   B  
t3         t3         C   D  
t4         t6         E   F  
t7         t7         A   B  
t8         t8         G   H  

【讨论】:

  • 该死!你很快。 Tabibitosan 是解决这个问题的恰当方法。
  • @LalitKumarB Tabibitosan 摇滚! *{:-D
【解决方案2】:

您可以使用an analytic 'trick' 来识别间隙和孤岛,将每一行的位置与所有行中的tstamp 进行比较,并将其位置与tstamp 进行比较,仅针对id2, id2 组合:

select tstamp, id1, id2,
  row_number() over (partition by id1, id2 order by tstamp)
    - row_number() over (order by tstamp) as block_id
from tab1;

TS I I   BLOCK_ID
-- - - ----------
t1 A B          0
t2 A B          0
t3 C D         -2
t4 E F         -3
t5 E F         -3
t6 E F         -3
t7 A B         -4
t8 G H         -7

block_id 的实际值无关紧要,只是它对于组合的每个块都是唯一的。然后您可以使用它进行分组:

select min(tstamp) as min_tstamp, max(tstamp) as max_tstamp, id1, id2
from (
  select tstamp, id1, id2,
    row_number() over (partition by id1, id2 order by tstamp)
      - row_number() over (order by tstamp) as block_id
  from tab1
)
group by id1, id2, block_id
order by min(tstamp);

MI MA I I
-- -- - -
t1 t2 A B
t3 t3 C D
t4 t6 E F
t7 t7 A B
t8 t8 G H

【讨论】:

    【解决方案3】:

    您应该可以使用row_number 窗口函数来执行此操作,如下所示:

    select 
        min(tstamp) mints, max(tstamp) maxts, id1, id2
    from (
        select 
           *, 
           row_number() over (order by tstamp) 
         - row_number() over (partition by id1, id2 order by tstamp) as rn
        from t
    ) as subq
    group by id1, id2, rn
    order by rn
    

    我无法使用任何 Oracle 数据库对其进行测试,但它适用于 MSSQL,并且应该也适用于 Oracle,因为窗口函数的工作方式相同。

    【讨论】:

      【解决方案4】:

      你需要一步一步来:

      1. 使用 LAG 检测 ID 更改,用标志 = 1 标记每个更改。
      2. 在 ID 更改标志上使用 SUM 为组(即具有相同 ID 的相邻记录)生成键(运行总计)。
      3. 按生成的组键分组并获取最小/最大时间戳。

      查询:

      select 
        min(tstamp) as min_tstamp,
        max(tstamp) as max_tstamp,
        min(id1) as id1,
        min(id2) as id2
      from
      (
        select 
          grouped.*, 
          sum(newgroup) over (order by tstamp) as groupkey
        from
        (
          select 
            mytable.*, 
            case when id1 <> lag(id1) over (order by tstamp) 
                   or id2 <> lag(id2) over (order by tstamp) 
            then 1 else 0 end as newgroup 
          from mytable
          order by tstamp
        ) grouped
      )
      group by groupkey
      order by groupkey;
      

      【讨论】:

      • 太棒了 - 我没有想到使用 SUM 的运行总方法。谢谢托尔斯滕。
      • 好吧,显然不是那么出色 ;-) 其他答案显示了一种更优雅的方式。我过去什至使用过这种技术,但没有想到。
      猜你喜欢
      • 1970-01-01
      • 2014-10-10
      • 1970-01-01
      • 1970-01-01
      • 2011-10-13
      • 2011-05-19
      • 2010-11-16
      • 2019-12-25
      • 1970-01-01
      相关资源
      最近更新 更多