3

考虑以下场景。stupid_table我无法控制的架构中有一个表 (a )。它是第三方,不受限制。没有敏感。我可以查询它,但不能添加索引或新表或更改设计。

中的每一列stupid_table都是一个VARCHAR2(50 BYTE),有很多列,但我只需要其中两个:row_typemagic_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 功能。

有一个更好的方法吗?

4

8 回答 8

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 谓词更容易管理。

于 2010-11-11T17:30:09.547 回答
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;
于 2010-11-11T18:27:41.440 回答
3

你可以TO_NUMBER完全避免使用吗?似乎无论如何都会提高性能。就像是:

WHERE t.magic_number != '0'

如果可能有负数,或者数字是浮点数,您可能需要额外的检查,但这似乎是可行的。

于 2010-11-11T17:30:00.227 回答
3

确切的方法是使用ordered_predicates提示来更改WHERE评估条件的顺序。

文档: Oracle ORDERED_PREDICATES 提示

SELECT /*+ ORDERED_PREDICATES */ TO_NUMBER(magic_number)
FROM stupid_table
WHERE row_type = 'DATA'
AND TO_NUMBER(magic_number) > 0;

现在尝试交换条件,您会再次遇到错误。还请考虑其他答案,因为我也怀疑唤起 TO_NUMBER 是您的最佳解决方案。

于 2011-05-08T20:17:41.963 回答
2

如何创建一个只包含“DATA”行类型的愚蠢表切片的物化视图?

于 2010-11-11T17:30:32.107 回答
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;
于 2010-11-12T01:17:10.283 回答
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”行中确实存在错误数据的可能性。

于 2010-11-11T19:12:35.007 回答
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 游标可能是唯一的方法。

于 2010-11-11T17:41:58.903 回答