如何找到按某个字段排序的每N行的最大值?

时间:2017-04-04 20:35:11

标签: sql oracle oracle12c

我在Oracle 12c中有一个类似于:

的表
CREATE TABLE test (
    id INT PRIMARY KEY,
    foo VARCHAR2(20),
    seconds int,
    system_dt DATE
);

使用一组数据,如

ABC, 10, 2016-01-01 00:00:01
ABC, 11, 2016-01-01 00:00:02
ABC, 5,  2016-01-01 00:35:54
ABC, 6, 2016-01-01 00:41:01
DEF, 15, 2016-01-01 00:00:02
DEF, 14, 2016-01-01 00:01:03
DEF, 51,  2016-01-01 00:36:55
DEF, 1, 2016-01-01 00:42:02

(值得注意的是idsystem_dt(显然rownum)不会以任何一致的值递增)

我想要做的是按foo, system_dt对此表格进行排序,然后在每N行中找到MAX(seconds),按foo分组。

所以在上面的例子中,N是2,输出将是:

ABC, 11, 2015-01-01 00:00:02
ABC, 6, 2016-01-01 00:41:01
DEF, 15, 2016-01-01 00:00:02
DEF, 51, 2016-01-01 00:36:55

我考虑过使用rownum,但rownum增量在以下查询中并不一致:

SELECT foo, rownum, seconds, system_dt
FROM test
ORDER BY foo, system_dt
-----
ABC, 3, 20
ABC, 134 25
ABC, 137, 18
ABC, 5086, 15
ABC, 5098, 16

2 个答案:

答案 0 :(得分:1)

您可以使用rownumrow_number()

select foo, max(seconds),
       max(system_dt) keep (dense_rank first order by seconds desc) as system_dt
from (select t.*,
             row_number() over (partition by foo order by system_dt) - 1 as seqnum
      from test t
     ) t
group by foo, trunc(seqnum / 2);

答案 1 :(得分:0)

以下是使用MATCH_RECOGNIZE(仅限Oracle 12.1以来可用)按固定行数进行分组的解决方案。不幸的是,至少在12.1(我使用的版本)中,MATCH_RECOGNIZE尚不支持聚合函数FIRST / LAST;如果是的话,一切都可以在match_recognize中完成,并且不需要GROUP BY。

with
     test ( foo, seconds, system_dt ) as (
       select 'ABC', 10, to_date('2016-01-01 00:00:01', 'yyyy-mm-dd hh24:mi:ss') from dual union all
       select 'ABC', 11, to_date('2016-01-01 00:00:02', 'yyyy-mm-dd hh24:mi:ss') from dual union all
       select 'ABC',  5, to_date('2016-01-01 00:35:54', 'yyyy-mm-dd hh24:mi:ss') from dual union all
       select 'ABC',  6, to_date('2016-01-01 00:41:01', 'yyyy-mm-dd hh24:mi:ss') from dual union all
       select 'DEF', 15, to_date('2016-01-01 00:00:02', 'yyyy-mm-dd hh24:mi:ss') from dual union all
       select 'DEF', 14, to_date('2016-01-01 00:01:03', 'yyyy-mm-dd hh24:mi:ss') from dual union all
       select 'DEF', 51, to_date('2016-01-01 00:36:55', 'yyyy-mm-dd hh24:mi:ss') from dual union all
       select 'DEF',  1, to_date('2016-01-01 00:42:02', 'yyyy-mm-dd hh24:mi:ss') from dual
     )
-- end of test data (not part of the SQL query); query begins BELOW THIS LINE
select foo, max(seconds) as seconds, 
       max(system_dt) keep (dense_rank last order by seconds) as system_dt
from   test
match_recognize (
  partition by foo
  order by system_dt
  measures match_number() as mn
  all rows per match
  pattern ( a{2} | a+ $ )
  define a as 0 = 0
)
group by foo, mn
;

FOO  SECONDS  SYSTEM_DT
---  -------  -------------------
ABC       11  01/01/2016 00:00:02
ABC        6  01/01/2016 00:41:01
DEF       15  01/01/2016 00:00:02
DEF       51  01/01/2016 00:36:55