【问题标题】:Oracle "Select Level from dual" does not work as expected with to_number resultOracle“从双重选择级别”无法按预期使用 to_number 结果
【发布时间】:2014-01-21 15:40:32
【问题描述】:

为什么

select *
from (
   SELECT LEVEL as VAL
   FROM DUAL
   CONNECT BY LEVEL <= 1000
   ORDER BY LEVEL
) n
left outer join (select to_number(trim(alphanumeric_column)) as nr from my_table
where NOT regexp_like (trim(alphanumeric_column),'[^[:digit:]]')) d
on n.VAL = d.nr
where d.nr is null 
and n.VAL >= 100

抛出一个 ORA-01722 无效数字(原因是最后一行,n.VAL),而带有数字列的类似版本 im my_table 工作正常:

 select *
    from (
       SELECT LEVEL as VAL
       FROM DUAL
       CONNECT BY LEVEL <= 1000
       ORDER BY LEVEL
    ) n
    left outer join (select numeric_column as nr from my_table) d
    on n.VAL = d.nr
    where d.nr is null 
    and n.VAL >= 100

假设 numeric_column 是 number 类型,alphanumeric_column 是 nvarchar_2 类型。请注意,上面的示例在没有数值比较的情况下也能正常工作 (n.VAL >= 100)。

有人知道吗?

【问题讨论】:

  • 好吧,那么你有一个不是数字的 alphanumeric_column 值。如果您显示表架构和示例数据会有所帮助。
  • No: (select to_number(trim(alphanumeric_column)) as nr from my_table where NOT regexp_like (trim(alphanumeric_column),'[^[:digit:]]') 只留下数值

标签: sql oracle


【解决方案1】:

这个问题快把我逼疯了。我将问题缩小到更简单的查询

SELECT *
  FROM (SELECT TO_NUMBER(TRIM (alphanumeric_column)) AS nr
          FROM my_table
         WHERE NOT REGEXP_LIKE (TRIM (alphanumeric_column), '[^[:digit:]]')) d
 WHERE d.nr > 1

使用 alphanumeric_colum 值为 ('100','200','XXXX');运行上述语句给出了“无效号码”错误。然后,我对查询进行了细微更改,以使用 CAST 函数而不是 TO_NUMBER:

SELECT *
  FROM (SELECT CAST (TRIM (alphanumeric_column) AS NUMBER) AS nr
          FROM my_table
         WHERE NOT REGEXP_LIKE (TRIM (alphanumeric_column), '[^[:digit:]]')) d
 WHERE d.nr > 1

这正确返回 - 100、200。我认为这些函数的行为会相似。看起来好像 oracle 试图在构建视图之前评估 d.nr > 1 约束,这是没有意义的。如果有人能解释为什么会发生这种情况,我将不胜感激。见SQLFiddle example

更新:我做了更多的挖掘,因为我不喜欢不知道为什么某些东西会起作用。我对这两个查询都运行了 EXPLAIN PLAN 并得到了一些有趣的结果。

对于失败的查询,谓词信息如下所示:

   1 - filter(TO_NUMBER(TRIM("ALPHANUMERIC_COLUMN"))>1 AND  NOT 
              REGEXP_LIKE (TRIM("ALPHANUMERIC_COLUMN"),'[^[:digit:]]'))

您会注意到 TO_NUMBER 函数首先在 AND 条件中被调用,然后 用于排除 alpha 值的正则表达式。我在想 oracle 可能会使用 AND 条件进行短路评估,并且由于它首先执行 TO_NUMBER,所以它失败了。

但是,当我们使用 CAST 函数时,评估顺序会交换,并且 首先评估正则表达式排除。因为对于 alpha 值,它是假的,那么 AND 子句的第二部分未计算,查询有效。

   1 - filter( NOT REGEXP_LIKE (TRIM("ALPHANUMERIC_COLUMN"),'[^[:digit:]
              ]') AND CAST(TRIM("ALPHANUMERIC_COLUMN") AS NUMBER)>1)

Oracle 有时会很奇怪。

【讨论】:

  • 谢谢,这正是我想要的。
  • 由于predicate pushing,Oracle 会乱序评估这些步骤。有许多类型的转换,执行计划通常看起来与原始查询完全不同。这些都是很好的性能特性,但它们会导致一些这样的问题。最好的解决方案是从不将数字和日期存储为字符串。我不是 100% 确定您的解决方案将始终有效,您可能只是走运了。强制 Oracle 以特定顺序评估事物是非常困难的。
  • @jonearles - 完全同意不将数字存储为字符串。感谢您对谓词推送的见解。今天学到了一些新东西。
【解决方案2】:

我相信,当涉及到谓词 (where) 子句时,Oracle 可以/将按照它认为合适的方式重新排序整个计划。所以关于谓词,它会短路(正如 OldProgrammer 指出的那样)评估,但是你不能保证它发生的确切顺序。

在您当前的 SQL 中,您依赖于谓词来删除非数字。一种选择是不使用“WHERE NOT regexp_like ...”,而是使用带有合并的 regexp_substr。例如:

create table t_tab2
(
  col varchar2(10)
);

create index t_tab2_idx on t_tab2(col);

insert into t_tab2
select level from dual
connect by level <= 100;

insert into t_tab2 values ('123ABC456');
commit;

-- select values > 95 (96->100 exclude non numbers)
select d.* from 
(
  select COALESCE(TO_NUMBER(REGEXP_SUBSTR(trim(col), '^\d+$')), 0) as nr
  from t_tab2
) d
where d.nr > 95;

这应该运行而不会引发无效数字错误。请注意,对于来自数据的任何非数字,合并将返回数字 0,您可能需要根据您的需要和数据进行更改。

【讨论】:

    猜你喜欢
    • 2020-06-22
    • 2019-03-12
    • 2022-10-12
    • 2021-01-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-05-13
    • 1970-01-01
    相关资源
    最近更新 更多