请考虑以下情形。我在模式中有一个表(stupid_table
),我无法控制。这是第三方,不受限制。没有敏感。我可以查询它,但不能添加索引或新表或更改设计。
stupid_table
中的每一列都是VARCHAR2(50 BYTE)
,有很多列,但我只需要其中两列:row_type
和magic_number
。 magic_number
填充了整数的字符串表示形式,但仅 ,其中row_type
设置为'DATA'
,我只需要大于零。
SELECT TO_NUMBER(magic_number)
FROM stupid_table
WHERE row_type = 'DATA'
AND TO_NUMBER(magic_number) > 0;
这导致“无效数字”Oracle错误,因为基于成本的优化器(CBO)选择在检查TO_NUMBER
之前评估row_type
并且有一大堆行与不同的row_type
以及magic_number
字段的不同用途。
好的,如果我先过滤行,然后进行比较呢?
SELECT TO_NUMBER(t.magic_number)
FROM (
SELECT magic_number
FROM stupid_table
WHERE row_type = 'DATA'
) t
AND TO_NUMBER(t.magic_number) > 0;
现在CBO似乎认为查询很简单,忽略了我所使用的狡猾,产生了与原始查询相同的查询计划。
最后,令人沮丧的是,我采用了肮脏的黑客攻击:使用/*+RULE*/
查询提示强制Oracle使用旧的基于规则的优化器。这就像一个梦想,但它不应该是必要的,更不用说它使用不再受支持的Oracle功能。
有更好的方法吗?
答案 0 :(得分:4)
我会通过编写你自己的吞噬异常的转换函数来解决这个问题,即
CREATE OR REPLACE FUNCTION my_to_number( p_str IN VARCHAR2 )
RETURN number
IS
BEGIN
RETURN to_number( p_str );
EXCEPTION
WHEN OTHERS THEN
RETURN null;
END;
然后更改查询
SELECT TO_NUMBER(magic_number)
FROM stupid_table
WHERE row_type = 'DATA'
AND MY_TO_NUMBER(magic_number) > 0;
除此之外,您当然可以采用RBO生成的查询计划并创建一个强制CBO使用该计划的配置文件。这可能比尝试提供一组完整的提示更容易管理,这些提示会阻止CBO在ROW_TYPE谓词之前应用MAGIC_NUMBER谓词。
答案 1 :(得分:4)
让CASE为您完成工作
select to_number(magic_number)
from stupid_table
where row_type = 'DATA'
and case when row_type = 'DATA' then to_number(magic_number) else 0 end > 0
在我的测试用例中,我无法重新创建您的错误,所以想知道是否有一些DATA
行没有数字。但它也可能是优化器处理查询的方式。
我认为no_merge提示也可以解决你的问题,但由于我无法重现这个问题,我无法确定。
SELECT --+ no_merge(t)
TO_NUMBER(t.magic_number)
FROM (
SELECT magic_number
FROM mike_temp_stupid_table
WHERE row_type = 'DATA'
) t
where TO_NUMBER(t.magic_number) > 0;
答案 2 :(得分:3)
你能完全避免使用TO_NUMBER
吗?似乎这样可以提高性能。类似的东西:
WHERE t.magic_number != '0'
如果可能存在负数,或者数字是浮点数,则可能需要额外检查,但这似乎是可行的。
答案 3 :(得分:3)
确切的方法是使用ordered_predicates
提示,更改评估WHERE
条件的顺序。
文档: Oracle ORDERED_PREDICATES Hint
SELECT /*+ ORDERED_PREDICATES */ TO_NUMBER(magic_number)
FROM stupid_table
WHERE row_type = 'DATA'
AND TO_NUMBER(magic_number) > 0;
现在尝试交换条件,然后再次出现错误。还请考虑其他答案,因为我也怀疑唤起TO_NUMBER是你最好的解决方案。
答案 4 :(得分:2)
如何创建仅包含'DATA'的row_type的stupid_table切片的物化视图?
答案 5 :(得分:2)
我通常添加一个rownum来阻止谓词推送。 (提示也可以这样做,但它们很容易出错,如果你弄错了这个类型的问题,你可能不会马上注意到。)此外你应该添加一个评论,以便有人以后不会尝试“优化”您的代码并删除看似不必要的逻辑。
SELECT TO_NUMBER(t.magic_number)
FROM (
--Bad data, use rownum for type safety
SELECT magic_number, rownum
FROM stupid_table
WHERE row_type = 'DATA'
) t
AND TO_NUMBER(t.magic_number) > 0;
答案 6 :(得分:1)
with语句允许您应用特定的评估顺序。
WITH
has_numerics_only AS
(
SELECT magic_number
FROM stupid_table
WHERE row_type = 'DATA'
)
SELECT TO_NUMBER(t.magic_number)
FROM has_numerics_only
WHERE TO_NUMBER(t.magic_number) > 0;
还要考虑一个或多个“DATA”行中确实存在错误数据的可能性。
答案 7 :(得分:0)
您可以尝试:
SELECT TO_NUMBER(magic_number)
FROM stupid_table
WHERE row_type = 'DATA'
AND REGEXP_LIKE(magic_number, '^\d{1,}$');
如果这仍然不起作用,将条件移动到HAVING子句可能会强制优化器首先对其进行评估。
SELECT TO_NUMBER(magic_number)
FROM (
SELECT magic_number
FROM stupid_table
WHERE row_type = 'DATA'
GROUP BY magic_number
HAVING REGEXP_LIKE(magic_number, '^\d{1,}$')) ilv;
如果失败,物化视图或使用PL / SQL游标可能是唯一的方法。