有没有一种方法可以强制Oracle在Join Access之后评估筛选器?

时间:2019-06-26 16:41:17

标签: sql oracle oracle11g oracle18c

我想使用强制转换对联接的结果进行过滤。问题在于原始字段的一部分不能转换为整数。如果在联接之后应用了过滤器,那将不是问题。这就是为什么我想知道是否有一种方法(也许是Optimizer Hint之类的东西)在加入操作之后推动过滤器评估。

这是我为示例构建的查询。我希望它能正常工作,但是失败并显示“ 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标准版2版本18.0.0.0.0以及Oracle Database 11g企业版11.2.0.1.0版中进行了尝试

非常感谢。

2 个答案:

答案 0 :(得分:2)

过去的技巧是构造查询,以使谓词不能被推入联接。如果将联接放入内联视图并添加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;

答案 1 :(得分:1)

对于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,但这似乎有些过分。