有没有办法强制 Oracle 在加入访问后评估过滤器?

Posted

技术标签:

【中文标题】有没有办法强制 Oracle 在加入访问后评估过滤器?【英文标题】:Is there a way to force Oracle to evaluate the Filter after the Join Access? 【发布时间】:2019-06-26 16:41:17 【问题描述】:

我想使用强制转换过滤连接的结果。问题是原始字段的一部分不能转换为整数。如果在加入后应用过滤器,那将不是问题。这就是为什么我想知道是否有办法(可能是优化器提示或其他方式)在连接操作之后推送过滤器评估。

这是我为示例构建的查询。我希望它可以工作,但会因“ORA-01722 无效号码”而失败:

WITH "literal" AS (
    SELECT 1 AS "literal_id", 'abc' AS "literal"
    FROM "DUAL"
    UNION
    SELECT 2 AS "literal_id", '7' AS "literal"
    FROM "DUAL"
),
     "scalar" AS (
         SELECT 3 AS "scalar_id", 2 AS "literal_id"
         FROM "DUAL"
         CONNECT BY ROWNUM <= 10000
     )
SELECT *
FROM "scalar"
         JOIN "literal" USING ("literal_id")
WHERE TO_NUMBER("literal") > 6;

ORA-01722 被抛出,因为它应用于“文字”CTE,因此崩溃,因为 'abc' 显然不是数字。 我们可以在执行计划中看到这一点:

Query execution plan

为了减少问题原因的可能性,我执行了该查询:

CREATE TABLE "literal" AS (
    SELECT 1 AS "literal_id", 'abc' AS "literal"
    FROM "DUAL"
    UNION
    SELECT 2 AS "literal_id", '7' AS "literal"
    FROM "DUAL"
);
CREATE TABLE "scalar" AS (
    SELECT 3 AS "scalar_id", 2 AS "literal_id"
    FROM "DUAL"
    CONNECT BY ROWNUM <= 10000
);
CREATE TABLE "joined" AS (
    SELECT *
    FROM "scalar"
             JOIN "literal" USING ("literal_id")
);
SELECT *
FROM "joined"
WHERE TO_NUMBER("literal") > 6;

效果很好。

那么,有没有办法重写这个查询(虽然我仍然需要这是一个单一的查询),所以它不会尝试转换“abc”?

作为参考,我在 Oracle Database 18c Standard Edition 2 Release 18.0.0.0.0 以及 Oracle Database 11g Enterprise Edition Release 11.2.0.1.0 上进行了尝试

非常感谢。

【问题讨论】:

【参考方案1】:

老派的技巧是构造查询,使得谓词不能被推入连接。如果将连接放入内联视图并添加rownum,这将阻止优化器在连接完成之前评估谓词

WITH "literal" AS (
    SELECT 1 AS "literal_id", 'abc' AS "literal"
    FROM "DUAL"
    UNION
    SELECT 2 AS "literal_id", '7' AS "literal"
    FROM "DUAL"
),
     "scalar" AS (
         SELECT 3 AS "scalar_id", 2 AS "literal_id"
         FROM "DUAL"
         CONNECT BY ROWNUM <= 10000
     )
select *
  from (
SELECT "scalar_id",  "literal_id", "literal", rownum
FROM "scalar"
         JOIN "literal" USING ("literal_id")
)
WHERE TO_NUMBER("literal") > 6;

如果您使用的是 12.2 或更高版本,则可以利用 to_number 函数的增强功能在出现转换错误时返回 NULL。

WITH "literal" AS (
    SELECT 1 AS "literal_id", 'abc' AS "literal"
    FROM "DUAL"
    UNION
    SELECT 2 AS "literal_id", '7' AS "literal"
    FROM "DUAL"
),
     "scalar" AS (
         SELECT 3 AS "scalar_id", 2 AS "literal_id"
         FROM "DUAL"
         CONNECT BY ROWNUM <= 10000
     )
SELECT "scalar_id",  "literal_id", "literal"
FROM "scalar"
         JOIN "literal" USING ("literal_id")
WHERE to_number("literal" default null on conversion error) > 6;

【讨论】:

谢谢!我喜欢将 rownum 添加到子查询的想法,因此它必须首先被评估。此外,我还没有注意到“版本错误时默认为空”选项。这种方法似乎效率更高,但我将无法使用它,因为我们的一个合作伙伴仍在使用 10g。【参考方案2】:

对于where,使用条件转换。例如:

SELECT *
FROM "scalar" s JOIN
     "literal" l
      USING ("literal_id")
WHERE (CASE WHEN REGEXP_LIKE(l.literal, '[^[0-9]+$')
            THEN TO_NUMBER(l.literal)
       END) > 6;

至于你的问题,我不这么认为。 Oracle 有一个非常复杂的优化器,因此它会重新安排操作以优化性能。您可以使用 CTE 和编译器提示来实现 CTE,但这似乎有点过头了。

【讨论】:

感谢您的回答,尽管由于此查询将在数亿行上执行,我尽量减少这些比较。这就是为什么我更愿意欺骗查询规划器。

以上是关于有没有办法强制 Oracle 在加入访问后评估过滤器?的主要内容,如果未能解决你的问题,请参考以下文章

停止调试会话后,有没有办法强制上次热重载的更改持续存在?

在 Oracle 12 中强制执行函数名称长度以满足 SQL-92 标准

在分析函数中过滤行 - Oracle

有没有办法强制使用SoundCloud javascript流API的MP3块?

使用 Oracle 瘦客户端和 jdbc 强制加密网络流量?

有没有办法强制刷新主机?