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.ID
是VARCHAR
。作为记录,我继承了这个数据库,如果我自己从头开始构建它,一开始就不会使用非数字 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 外连接