如何限制查询中返回的组数,而不是 Oracle 中的行数

Posted

技术标签:

【中文标题】如何限制查询中返回的组数,而不是 Oracle 中的行数【英文标题】:How to limit number of groups returned in a query, but not the number of rows in Oracle 【发布时间】:2020-11-26 19:26:30 【问题描述】:

如何限制查询中的组数,而不是Oracle中的行数?

如果我必须手动执行此操作,我将不得不使用 DISTINCT

应该是这样的:

FOR d IN (
            SELECT DISTINCT COLUMN_1 FROM myTable
                WHERE myDate BETWEEN x AND y
                OFFSET o ROWS
                FETCH NEXT l ROWS ONLY
        ) LOOP

然后,从查询中返回的每个 id 中进行选择,在我看来,这是一个糟糕的解决方案。

样本数据:

如果我通过使用 COLUMN_2 将组数限制为 2,则预期结果应类似于:

【问题讨论】:

样本数据和期望的结果会有所帮助。 我不明白。您是否希望遍历基表中的所有行(在日期过滤器之后),但仅限于有限数量的 id? (请注意,您的查询没有 ORDER BY 子句,因此 FETCH 将是完全随机的 - 可以吗?) 添加样本数据和所需结果 你还是没有回答我的问题。您是否关心选择了哪些组?或者你只关心多少? 只选择了多少组 【参考方案1】:

我相信您可能正在寻找这样的东西:

select *
from   mytable
where  id in (
               select distinct id
               from   my_table
               where  my_date between x and y
               fetch  first :n rows only
             )
;

:n 是一个绑定变量,编码你想要选择的组数。

这应该比使用分析函数的解决方案更有效 - 即使它必须读取基表两次。在 OTN 上发布的测试中,我表明差异不小。

编辑如果我没记错的话,FETCH 没有以最有效的方式实现(也许有充分的理由,与我们在此查询中不需要的功能有关 - 例如如何处理有领带)。 FETCH 本身类似于 DENSE_RANK() 实现,而不是更快的行限制子句(使用 ROWNUM)。如果速度真的很重要,我可能需要修改查询以取消 FETCH。 结束编辑

进一步修改进行性能比较

经常发帖者 MT0 要求提供一个指针,声称聚合解决方案可以(并且通常)比分析函数方法更有效,即使前者可能需要多次遍历分析函数所在的数据方法只需要一个。

唉,OTN(现在自称为“甲骨文开创者开发者社区”,由甲骨文自己主持的讨论板)在 2020 年 9 月末经历了一次大规模且严重拙劣的平台变更;这弄乱了搜索工具和旧帖子的格式,以至于使它们几乎无法使用。

相反,我将在此线程中展示 OP 问题的简单模型;任何人都可以运行的代码,以便他们可以在自己的机器上重复测试。

我创建了一个包含两列 ID 和 STR 的表 - ID 的作用与 OP 的问题中的相同,而 STR 只是模拟真实数据的额外负载。 ID 是数字,STR 是 varchar2(100)。我在表格中填充了 900 万行 - 100 万个 ID,每个 ID 有 9 行。任务是只选择三个“组”(三个不同的 ID,然后从基表中为这三个不同的 ID 选择所有行)。

在 ID 列上没有索引的情况下,聚合解决方案在我的机器上运行时间为 0.81 秒;在 ID 上有一个索引,它在 0.47 秒内运行。分析函数解决方案在 0.91 秒内运行,有或没有索引(显然 - 索引无法使分析函数解决方案受益)。所有这些结果都是针对未声明为 NOT NULL 的列 ID。

这是创建表的代码、ID 上的索引以及我测试的两个查询。 注意:正如我在第一次编辑(上图)中解释的那样,fetch 很慢;我在过度查询中使用ROWNUM 将其替换为标准行限制技术。

drop table t purge;

create table t (id number, str varchar2(100));

insert into t
  with    row_gen as (select level from dual connect by level <= 3000)
  select  mod(344227 * rownum, 1000000), rpad('x', 100, 'x')
  from    row_gen cross join row_gen
;

commit;

create index t_idx on t(id);

select *
from   t
where  id in (
  select id from (select distinct id from t)
  where  rownum <= 3
);

select *
from   ( select t.*, dense_rank() over (order by id) dr from t )
where  dr <= 3;

【讨论】:

"在 OTN 上发布的测试中,我发现差异不小。"请我们对此声明进行引用。 @MT0 - 唉,OTN切换平台,现在搜索旧线程非常困难(我刚刚尝试过,我无法快速找到相关帖子)。更糟糕的是,即使你能找到它们,旧线程(即 2020 年 10 月之前)的格式都乱七八糟,所以帖子几乎无法阅读。不过,您可以在其他地方找到我的一些 cmets - 在 Jonathan Lewis 的博客上,例如此处的第二条评论:jonathanlewis.wordpress.com/2020/03/12/dense_rank Jonathan 的帖子包含设置测试所需的所有代码。 @MT0 - 虽然......如果我没记错的话,FETCH 本身的实现类似于分析 DENSE_RANK 而不是简单的 ROWNUM 限制;所以如果它必须很快,我可能不得不使用 ROWNUM 重写我的查询。我将编辑我的帖子以至少澄清这一点。 我想没有真正的“表演者”方式来做这种事情,因为 DISTINCT 总是导致查询变慢......我的意思是,解决方案效果很好,但是,如果我需要要返回 1000 个组,具体取决于用例,这将是一个很大的瓶颈。我的用例是返回所有这些组的宁静 API,因此性能可能是一个问题。使用 ROWNUM 限制结果的事件,我没有看到性能显着提高。无论如何,这解决了问题。谢谢! @MatheusCirillo - 应该是一种执行此操作的高效方法,因为不应为所有输入处理“不同”。一旦识别出至少“n”个不同的“组”,该部分查询应该停止执行并将控制权交还给环境。在 Oracle SQL 中是否有办法做到这一点,我不确定。我会做一些测试(使用 ROWNUM 而不是 FETCH)来看看它是如何进行的;有时间我会做的。【参考方案2】:

你可以使用DENSE_RANK:

SELECT *
FROM   (
  SELECT t.*,
         DENSE_RANK() OVER ( ORDER BY column2 ) AS rnk
  FROM   table_name t
)
WHERE  rnk <= 2;

其中,对于样本数据:

CREATE TABLE table_name ( column1, column2, column3, column4 ) AS
SELECT 1, 1, 1.0, 1.0 FROM DUAL UNION ALL
SELECT 2, 2, 2.0, 2.0 FROM DUAL UNION ALL
SELECT 2, 2, 2.2, 2.1 FROM DUAL UNION ALL
SELECT 2, 2, 2.2, 2.2 FROM DUAL UNION ALL
SELECT 2, 2, 2.0, 2.3 FROM DUAL UNION ALL
SELECT 3, 3, 3.0, 3.1 FROM DUAL UNION ALL
SELECT 3, 3, 3.1, 3.1 FROM DUAL UNION ALL
SELECT 3, 3, 3.1, 3.1 FROM DUAL UNION ALL
SELECT 4, 4, 4.2, 4.0 FROM DUAL;

输出:

第 1 列 |第 2 栏 |第 3 栏 |第 4 栏 |核糖核酸 ------: | ------: | ------: | ------: | --: 1 | 1 | 1 | 1 | 1 2 | 2 | 2 | 2 | 2 2 | 2 | 2.2 | 2.1 | 2 2 | 2 | 2.2 | 2.2 | 2 2 | 2 | 2 | 2.3 | 2

(并且,如果您想要 DISTINCT 行,则将 DISTINCT 添加到外部查询)

db小提琴here

【讨论】:

【参考方案3】:

如果我理解正确,你想要ROW_NUMBER()

SELECT t.*
FROM (SELECT t.*,
             ROW_NUMBER() OVER (PARTITION BY id ORDER BY id) as seqnum
      FROM myTable t
      WHERE t.myDate BETWEEN x AND y
      ) t
WHERE seqnum = 1;

这将为每个满足条件的id 返回任意行。

【讨论】:

在分析函数中,使用与分区相同的表达式进行排序的意义是什么? 在问题中添加了示例数据 + 所需结果 @mathguy 。 . .有些数据库需要order by,所以我使用它作为默认值。它适用于所有数据库。 是的,但是“正常”的做法是写order by null,或者如果你不喜欢这样,可以通过一些硬编码的文字(order by 0order by 'x')来排序。拥有order by id 会使读者感到困惑——尤其是对分析函数不太熟悉的读者。 @mathguy 。 . . SQL Server 不支持这些。

以上是关于如何限制查询中返回的组数,而不是 Oracle 中的行数的主要内容,如果未能解决你的问题,请参考以下文章

377. Combination Sum IV 返回符合目标和的组数

Oracle 查询:如何将返回的记录限制为计数 > 1 但显示完整结果的记录?

oracle中group by用法

如何在 Oracle 的组中获得第三个 [重复]

正则表达式 Match.Value 返回整个值,而不是匹配的组

新手BigQuery,SQL如何统计包含特殊行的组数