Oracle 连接子句中的无效数字

Posted

技术标签:

【中文标题】Oracle 连接子句中的无效数字【英文标题】:Oracle Invalid Number in Join Clause 【发布时间】:2019-03-24 04:19:01 【问题描述】:

我收到一个对我来说没有意义的 Oracle Invalid Number 错误。我了解what this error means,但在这种情况下不应该发生这种情况。抱歉这个问题太长了,但请耐心等待,我可以彻底解释一下。

我有一个存储不同来源的 ID 的表,其中一些 ID 可以包含字母。因此,该列是VARCHAR

其中一个来源具有数字 ID,我想加入该来源:

SELECT *
FROM (
    SELECT AGGPROJ_ID -- this column is a VARCHAR
    FROM AGG_MATCHES -- this is the table storing the matches
    WHERE AGGSRC = 'source_a'
) m
JOIN SOURCE_A a ON a.ID = TO_NUMBER(m.AGGPROJ_ID);

在大多数情况下,这是可行的,但取决于随机因素,例如 select 子句中的列,如果它使用左连接或内连接等,我将开始看到 Invalid Number 错误。

我已多次验证AGG_MATCHES 中的所有条目,其中AGGSRC = 'source_a'AGGPROJ_ID 列中不包含非数字字符:

-- this returns no results
SELECT AGGPROJ_ID
FROM AGG_MATCHES
WHERE AGGSRC = 'source_a' AND REGEXP_LIKE(AGGPROJ_ID, '[^0-9]');

我知道 Oracle 基本上会在内部重写查询以进行优化。回到第一个 SQL 示例,我最好的猜测是,根据整个查询的编写方式,在 某些情况 Oracle 会尝试在子查询之前执行 JOIN .换句话说,它试图将整个AGG_MATCHES 表连接到SOURCE_A,而不仅仅是子查询返回的子集。如果是这样,那么AGGPROJ_ID 列中会有包含非数值的行。

有谁知道这是否是导致错误的原因?如果是这个原因,我有没有强制 Oracle 先执行子查询部分,所以它只是试图加入 AGG_MATCHES 表的子集?


更多背景知识:

这显然是一个简化的例子来说明问题。 AGG_MATCHES 表用于存储不同来源(即项目)之间的“匹配”。换句话说,就是说sourceA中的项目与sourceB中的项目相匹配。

我没有一遍又一遍地编写相同的 SQL,而是为我们常用的源创建了视图。这个想法是有一个包含两列的视图,一列用于 SourceA,一列用于 SourceB。出于这个原因,我不想在源表的ID 列上使用TO_CHAR,因为开发人员每次进行连接时都必须记住这样做,并且我正在尝试删除代码复制。另外,由于SOURCE_A中的ID是一个数字,所以我觉得任何存储SOURCE_A.ID的视图都应该继续将其转换为数字。

【问题讨论】:

那么当你遇到一个非数字id值的记录你应该怎么做呢? @OldProgrammer 我在将记录插入AGG_MATCHES 时进行了验证,以确保AGGSRC = 'source_a' 时仅提供数字ID @OldProgrammer 同样,'source_a' 不应该有非数字 id 值,因为SOURCE_A.ID 是一个NUMBER 列。让这件事变得棘手的是SOURCE_B.IDVARCHAR。作为记录,我继承了这个数据库,如果我自己从头开始构建它,一开始就不会使用非数字 ID。 然而,真正的问题是,为什么要在 varchar 列中存储一个数字? @a_horse_with_no_name 我想我在问题中解释了这一点。有多个源表,其中一些源表具有非数字 ID,因此匹配表必须支持非数字 ID,具体取决于源。 【参考方案1】:

您是对的,Oracle 执行语句的顺序与您编写的顺序不同,导致转换错误。

按顺序解决此问题的最佳方法是:

    更改数据模型以始终将数据存储为正确的类型。始终将数字存储为数字,将日期存储为日期,将字符串存储为字符串。 (您已经知道这一点并说您不能更改数据模型,这是对未来读者的警告。) 使用TO_CHAR 将数字转换为字符串。

    如果您使用的是 12.2,请使用 DEFAULT return_value ON CONVERSION ERROR 语法将字符串转换为数字,如下所示:

    SELECT *
    FROM (
        SELECT AGGPROJ_ID -- this column is a VARCHAR
        FROM AGG_MATCHES -- this is the table storing the matches
        WHERE AGGSRC = 'source_a'
    ) m
    JOIN SOURCE_A a ON a.ID = TO_NUMBER(m.AGGPROJ_ID default null on conversion error);
    

    ROWNUM 添加到内联视图以防止可能重写语句的优化器转换。 ROWNUM 总是在最后进行评估,它强制 Oracle 按特定顺序运行,即使 ROWNUM 未使用。 (官方提示是这样做的方法,但要正确提示太难了。)

    SELECT *
    FROM (
        SELECT AGGPROJ_ID -- this column is a VARCHAR
        FROM AGG_MATCHES -- this is the table storing the matches
        WHERE AGGSRC = 'source_a'
            --Prevent optimizer transformations for type safety.
            AND ROWNUM >= 1
    ) m
    JOIN SOURCE_A a ON a.ID = TO_NUMBER(m.AGGPROJ_ID);
    

【讨论】:

感谢您的详细回答! #4 解决了这个问题,但并不理想,因为我更愿意让优化器运行最有效的查询。不幸的是,我们使用的是 12.1,所以我不能使用我非常喜欢的 #3(我将敦促我们的 DBA 团队升级)。我们一开始是在做#2,但是其他开发人员每次在比赛视图中添加一个连接时总是忘记包含TO_CHAR,这一直导致错误弹出,这就是我试图转换它的全部原因到视图中的一个数字。至少你现在给了我一个短期的解决方案。再次感谢。【参考方案2】:

我认为最简单的解决方案使用case,它对评估的顺序有更多的保证:

SELECT a.*
FROM AGG_MATCHES m JOIN
     SOURCE_A a
     ON a.ID = (CASE WHEN m.AGGSRC = 'source_a' THEN TO_NUMBER(m.AGGPROJ_ID) END);

或者,更好的是,转换为字符串:

SELECT a.*
FROM AGG_MATCHES m JOIN
     SOURCE_A a
     ON TO_CHAR(a.ID) = m.AGGPROJ_ID AND
        m.AGGSRC = 'source_a' ;

也就是说,最好的建议是修复数据模型。

在您的情况下,最好的解决方案可能只是一个视图或一个生成列:

create view v_agg_matches_a as 
    select . . .,
           (case when regexp_like(AGGPROJ_ID, '^[0-9]+$')
                 then to_number(AGGPROJ_ID)
            end) as AGGPROJ_ID
    from agg_matches am
    where m.AGGSRC = 'source_a';

如果您使用视图,case 可能不是必需的,但它更安全。

然后在后续查询中使用该视图。

【讨论】:

CASE 解决方案很有趣,我没想到。但是,这将如何影响性能?在执行联接时对每一行执行检查似乎效率很低。至于使用TO_CHAR,正如之前的评论中所述,我们最初是这样做的,但其他开发人员在加入视图时一直忘记包含TO_CHAR,其想法是删除代码重复。显然,最好的解决方案是重构和修复数据模型,但这是我继承的一个大型数据库,从业务角度来看,重构它是不切实际的。

以上是关于Oracle 连接子句中的无效数字的主要内容,如果未能解决你的问题,请参考以下文章

在 Oracle SQL 中,除了外连接之外,where 子句中的 (+) 运算符的目的是啥?

如何使用带有过滤器 where 子句的 oracle 外连接

oracle 之 using 使用

在 Oracle 查询中将连接从 Where 子句移动到 From 子句

oracle笔记2

求oracle 数据库高手 围观 此问题: 执行下面代码 报此错误:ora-01722:无效数字