【问题标题】:Limit number of rows from join, in oracle在oracle中限制连接的行数
【发布时间】:2011-07-13 21:42:57
【问题描述】:

对于我冗长的问题,我提前道歉,如果格式不符合标准(新手),那就去吧。

我有一个表 MY_TABLE 具有以下架构 -

MY_ID |类型 | REC_COUNT 个 1 |一个 | 1 1 |乙| 3 2 |一个 | 0 2 |乙| 0 ……

第一列对应一个ID,第二列是一些类型,第三列是一些计数。请注意,MY_ID 列不是主键,可能有许多记录具有相同的 MY_ID。

我想编写一个存储过程,它将接受一个 ID 数组并返回符合以下条件的 ID 子集 - ID 应与表中至少 1 条记录的 MY_ID 字段匹配,并且至少 1 条匹配记录不应具有 TYPE = A OR REC_COUNT = 0。

这是我想出的程序 -

PROCEDURE get_id_subset(
    iIds IN ID_ARRAY,
    oMatchingIds OUT NOCOPY ID_ARRAY
)
IS
BEGIN
SELECT t.column_value
BULK COLLECT INTO oMatchingIds
FROM TABLE(CAST(iIds AS ID_ARRAY)) t
WHERE EXISTS (
    SELECT /*+ NL_SJ */ 1
    FROM MY_TABLE m
    WHERE (m.my_id = t.column_value)
    AND (m.type != 'A' OR m.rec_count != 0)
);
END get_id_subset;

但我真的很关心性能,有些 ID 可以匹配表中的 1000 条记录。 MY_ID 和 TYPE 列上有索引,但 REC_COUNT 列上没有索引。所以我在想如果有超过 1000 行具有匹配的 MY_ID 字段,那么我将只返回 ID 而不应用 TYPE 和 REC_COUNT 谓词。这是这个版本 -

PROCEDURE get_id_subset(
        iIds IN ID_ARRAY,
        oMatchingIds OUT NOCOPY ID_ARRAY
)
IS
BEGIN
SELECT t.column_value
BULK COLLECT INTO oMatchingIds
FROM TABLE(CAST(iIds AS ID_ARRAY)) t, MY_TABLE m
WHERE (m.my_id = t.column_value)
AND ( ((SELECT COUNT(m.my_id) FROM m WHERE 1) >= 1000)
    OR EXISTS (m.type != 'F' OR m.rec_count != 0)
);
END get_id_subset;

但这不能编译,我在内部选择时收到以下错误 -

PL/SQL:ORA-00936:缺少表达式

还有其他写法吗?内部选择需要在连接表上工作。

澄清一下,我同意这个查询的结果集不同。我的假设是,由于 my_id 列上有一个索引,因此执行 count(*) 比将 rec_count 谓词实际应用于 10000 行要便宜得多,因为该列上没有索引。我错了吗?

【问题讨论】:

  • 您的第二个 EXISTS 使检查类型和 rec_count 的那个无效,因为第一个 EXISTS 是第二个的子集。
  • 发布两个版本查询的执行计划
  • 用新版本的查询更新了问题。

标签: sql performance oracle join plsql


【解决方案1】:

我认为您的第二个查询比第一个查询有什么改进。充其量,第一个子查询必须达到 1000 条匹配记录才能确定计数是否小于 1000,所以我认为它不会节省大量工作。它也改变了实际结果,从你的描述中不清楚你是否说只要它更有效就可以。 (如果没问题,那么业务逻辑就很不清楚了——如果有很多记录时其他条件无关紧要,为什么其他条件根本重要?)

您问,“分组是在谓词之前还是之后应用”。我不清楚你在说查询的哪一部分,但从逻辑上讲,顺序总是

  1. Where 谓词
  2. 分组方式
  3. 有谓词

优化器可以更改事物实际评估的顺序,但结果必须始终在逻辑上等同于上述评估顺序(除非优化器出现错误)。

1000 条记录真的不多。您是否真的遇到过第一次查询的性能无法接受的情况?

对于任一查询,最好将相关的 EXISTS 子查询重写为不相关的 IN 子查询。您需要对此进行测试。

您需要展示实际的执行计划以获得更有用的反馈。

编辑

对于您正在谈论的那种短路,我认为您需要像这样重写您的子查询(从查询的初始版本开始)(对不起,我第一次尝试这样做是行不通的,因为我尝试过在子查询中访问顶级表中的列):

WHERE EXISTS (
    SELECT /*+ NL_SJ */ 1
      FROM MY_TABLE m
      WHERE (m.my_id = t.column_value)
        AND rownum <= 1000
      HAVING MAX( CASE WHEN m.type != 'A' OR m.rec_count != 0 THEN 1 ELSE NULL END ) I S NOT NULL
          OR MAX(rownum) >= 1000
)

这应该强制它每个 id 的记录不超过 1,000 条,然后如果至少有一行符合 typerec_count 的条件,或者达到 1,000 条记录的限制,则返回一行。如果查看执行计划,应该会看到COUNT STOPKEY 操作,这表明Oracle 将在返回一定数量的行后停止运行查询块。

【讨论】:

  • 感谢您的回复。你是对的,第二个查询并不是真正的改进,但出于我的目的,我正在寻找如果有超过 1000 条记录时不会应用计数谓词的东西。这确实改变了实际结果,我对此表示满意。我的假设是,由于有一个包含 my_id 列的索引,因此与在 1000 行上应用谓词相比,选择计数不会那么昂贵。我错了吗?
  • 您说第二个查询没有改进,但据我所知,您在上面发布的第二个查询无效。这种方法可能会产生好的结果,但我不太喜欢它。我添加了一条新建议,将子查询重写为我的答案,我认为这是一种更好的方法。
  • 非常感谢您的建议。建议的查询可以工作,但我无法编译,t.column_value 在内部选择中不可访问。关于如何重写的任何想法?
  • 抱歉,忘记了嵌套子查询的问题。我以我认为可行的方式重写了它。
  • 这需要一个小小的调整,非常感谢!!我唯一改变的是在 HAVING 子句中使用 MAX(rownum) = 1000 而不是 >=。由于我们的 rownum 1000 的行,对吧?我对此很好。这就像说如果有 1000 行不符合我的标准,那么这意味着有 1000 行或超过 1000 行,所以无论如何都返回它。极端情况是一个包含 1000 行的 ID,它们与我们将返回的谓词不匹配,这没什么大不了的。再次,非常感谢!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-06-15
  • 2022-09-26
  • 1970-01-01
  • 2016-09-26
  • 2010-09-08
相关资源
最近更新 更多