当必须根据条件对记录进行分组时如何选择最多 x 行

Posted

技术标签:

【中文标题】当必须根据条件对记录进行分组时如何选择最多 x 行【英文标题】:How to select up to x rows when records have to be grouped based on criteria 【发布时间】:2020-01-17 08:09:47 【问题描述】:

我想创建查询,最多选择 x 行,记录按一个 id 分组,整个组必须是查询的结果。我基于过滤的值存储在 p_id 列中,具有相同值的行创建组。 如果是该表:

    p_id    age
0   00170   64  
1   00170   64  
2   00201   24  
3   00201   64  
4   00201   64  
5   00300   24  
6   00300   20  

我想选择 4 行,但是因为 p_id 为 00170,00201 的组总共有 5 条记录我得到:

0   00170   64 
1   00170   64

如果我选择 5 行,我会得到:

0   00170   64 
1   00170   64 
2   00201   24 
3   00201   64 
4   00201   64 

如果我选择 6 行,我会得到(p_id 00300 是 2 条记录,因此不包括在内,总和超过 6):

0   00170   64 
1   00170   64 
2   00201   24 
3   00201   64 
4   00201   64 

因此返回整个组。我正在使用 oracle db,使用 ROWNUM 选择 x 行很容易。当我尝试使用附加条件达到一定数量的行时,我会迷路。

【问题讨论】:

到目前为止你尝试了什么,你能分享一些你的代码吗?不要使用rownum来限制行数见***.com/questions/470542/… @Hrzug 。 . .从您的问题中不清楚是否订购了 p_id 值。或者,如果有另一列对它们进行排序。也就是说,您总是想要p_id 的最小值,还是想要基于表中其他列的“最早”。 【参考方案1】:

Oracle 设置

CREATE TABLE test_data ( p_id, age ) AS
SELECT '00170', 64 FROM DUAL UNION ALL
SELECT '00170', 64 FROM DUAL UNION ALL
SELECT '00201', 24 FROM DUAL UNION ALL
SELECT '00201', 64 FROM DUAL UNION ALL
SELECT '00201', 64 FROM DUAL UNION ALL
SELECT '00300', 24 FROM DUAL UNION ALL  
SELECT '00300', 20 FROM DUAL;

查询

对行排序,然后找到每个组的最大行号,然后过滤以仅返回最大行号包含在您想要的行限制中的组:

SELECT p_id,
       age
FROM   (
  SELECT t.*,
         MAX( ROWNUM ) OVER ( PARTITION BY p_id ) AS grp
  FROM   (
    SELECT *
    FROM   test_data
    ORDER BY p_id
  ) t
)
WHERE  grp <= 4;

输出

P_ID |年龄 :---- | --: 00170 | 64 00170 | 64

如果将最后一行更改为 5 则返回 5 行并将其更改为 6 则仍将返回 5 行。

db小提琴here

【讨论】:

【参考方案2】:

我会通过窗口计数和过滤来解决这个问题:

select p_id, age
from (select p_id, age, count(*) over(order by p_id) cnt from mytable t) t
where cnt <= 5
order by p_id

您可以根据需要更改cnt &lt;= 5

Demo on DB Fiddle

cnt &lt;= 4:

P_ID |年龄 ---: | --: 170 | 64 170 | 64

cnt &lt;= 5:

P_ID |年龄 ---: | --: 170 | 64 170 | 64 201 | 24 201 | 64 201 | 64

cnt &lt;= 6:

P_ID |年龄 ---: | --: 170 | 64 170 | 64 201 | 24 201 | 64 201 | 64

【讨论】:

【参考方案3】:

GMB 的回答很好。但可以通过使用RANK() 稍微简化一下。这个函数恰好完全做你想做的事:

select p_id, age
from (select t.*,
             rank() over (order by p_id) as rnk
      from t
     ) t
where rnk <= 5
order by p_id;

不过,更重要的是,如果 p_id 值没有排序,那么您可能需要一个额外的步骤:将某个排序列的最小值分配给每个 p_id。让我叫那个订购栏id

select p_id, age
from (select t.*,
             rank() over (order by p_id_grp) as rnk
      from (select t.*, min(id) over (partition by p_id) as p_id_grp
            from t
           ) t
     ) t
where rnk <= 5
order by p_id;

【讨论】:

【参考方案4】:

这是一个典型的 Top-N 查询:

使用带有有序视图的 ROWNUM 来获得正确的排序:

SELECT p_id, age
FROM   (SELECT p_id, age
        FROM   table
        ORDER BY age DESC)
WHERE ROWNUM <= 4;

对于 Oracle v 12c 及以后的版本,有新的 FETCH 子句:

SELECT p_id, age
FROM   table
GROUP BY p_id
FETCH FIRST 4 ROWS ONLY;

更多资源:https://oracle-base.com/articles/misc/top-n-queries

【讨论】:

根据 OP 的意图,这不是正确的解决方案。

以上是关于当必须根据条件对记录进行分组时如何选择最多 x 行的主要内容,如果未能解决你的问题,请参考以下文章

根据 3 到 4 个条件对数据库表中的行进行计数和分组查询

Oracle PL/SQL - 根据条件对不同列进行选择、分组、排序、where-clause 的最佳方法?

如何根据数据框中的值有条件地对数据进行分组?

根据条件对两行和其间的所有行进行分组

根据 ID 选择行,然后再根据文本框的基数选择行

PostgreSQL如何对结果进行分组以使所有行都必须为真?