Oracle 11.2 SQL - 帮助压缩有序集中的数据

Oracle 11.2 SQL - help to condense data in ordered set

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

源数据:

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

我认为这对于 window-ing 分析函数来说已经成熟,但如果不对 IDn 的所有相等组合进行分组,我就无法进行分区 - 而不是仅按时间戳排序的相邻行中的组合。

一种解决方法是首先在一个内联视图中创建一个键列,我可以稍后对其进行分组,即块中的每一行具有相同的值,而每个块具有不同的值。我可以使用 LAG 分析函数来比较行值,然后将 PL/SQL 函数调用到序列的 return nextval/currval 值(直接在 [=43 中调用 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 的功能只是 return currval/nextval 来自一个序列。

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

有什么想法吗?

谢谢。

你需要一步一步来:

  1. 用 LAG 检测 ID 变化,用标志 = 1 标记每个变化。
  2. 使用 SUM 对 ID 更改标志(总共 运行 个)为组(即具有相同 ID 的相邻记录)生成键。
  3. 按生成的组密钥分组并获得 min/max 时间戳。

查询:

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;

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  

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

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 中使用,因为 window 函数的工作方式相同。

您可以使用 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