在oracle中限制连接的行数

时间:2011-03-12 01:30:31

标签: sql performance oracle join plsql

我提前为我冗长的问题道歉,如果格式不符合标准(新手),那么就这样了。

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

 MY_ID | TYPE | REC_COUNT
 1     | A    | 1
 1     | B    | 3
 2     | A    | 0
 2     | B    | 0
 ....

第一列对应一个ID,第二列对应某种类型,第三列对应一些ID。请注意,MY_ID列不是主键,可能有许多记录具有相同的MY_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行要便宜得多,因为该列上没有索引。我错了吗?

1 个答案:

答案 0 :(得分:1)

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

你问,“将在谓词之前或之后应用该组”。我不清楚你在说什么部分的查询,但从逻辑上讲,订单总是

  1. 谓词
  2. 分组依据
  3. 有谓词
  4. 优化器可以更改实际评估事物的顺序,但结果必须始终在逻辑上等同于上述评估顺序(禁止优化器错误)。

    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在返回一定数量的行后将停止运行查询块。