Oracle 10g DB。我有一个名为s_contact
的表。该表有一个名为person_uid
的字段。此person_uid
字段是varchar2,但包含某些行的有效数字和其他行的有效数字。例如,一行可能有person_uid
的'2-lkjsdf'而另一行可能是1234567890。
我想在person_uid中只返回有效数字的行。我正在尝试的SQL是......
select person_uid
from s_contact
where decode(trim(translate(person_uid, '1234567890', ' ')), null, 'n', 'c') = 'n'
translate将所有数字替换为空格,以便如果字段仅包含数字,则修剪将返回null。然后我使用decode语句设置一个小代码来过滤。 n =数字,c = char。
当我只运行预览时,这似乎有效,但当我添加过滤器时,我收到“无效数字”错误...
and person_uid = 100
-- or
and to_number(person_uid) = 100
我只是不明白发生了什么!它应该过滤掉所有无效数字的记录,100显然是一个数字......
任何想法?非常赞赏!
答案 0 :(得分:6)
不幸的是,已经提出的各种子查询方法并不能保证有效。允许Oracle将谓词推送到子查询中,然后以其认为合适的顺序评估条件。如果在过滤掉非数字行之前恰好评估PERSON_UID
条件,则会出现错误。 Jonathan Gennick有一篇很好的文章Subquery Madness,它非常详细地讨论了这个问题。
这给你留下了一些选择
1)重做数据模型。将数字存储在NUMBER列以外的任何内容中通常不是一个好主意。除了导致这类问题之外,它还有一种趋势,即搞砸了优化器的基数估计,导致不太理想的查询计划。
2)更改条件以指定字符串值而不是数字。如果PERSON_UID
应该是字符串,则您的过滤条件可以是PERSON_UID = '100'
。这避免了执行隐式转换的需要。
3)编写一个自定义函数,对字符串进行数字转换并忽略任何错误并在代码中使用它,即
CREATE OR REPLACE FUNCTION my_to_number( p_arg IN VARCHAR2 )
RETURN NUMBER
IS
BEGIN
RETURN to_number( p_arg );
EXCEPTION
WHEN others THEN
RETURN NULL;
END;
然后my_to_number(PERSION_UID) = 100
4)使用一个子查询来阻止推送谓词。这可以通过几种不同的方式完成。我个人更喜欢将ROWNUM投入子查询,即建立在OMG Ponies的解决方案
上WITH valid_persons AS (
SELECT TO_NUMBER(c.person_uid) 'person_uid',
ROWNUM rn
FROM S_CONTACT c
WHERE REGEXP_LIKE(c.personuid, '[[:digit:]]'))
SELECT *
FROM valid_persons vp
WHERE vp.person_uid = 100
Oracle无法将vp.person_uid = 100
谓词推送到子查询中,因为这样做会改变结果。您还可以使用提示强制子查询实现或防止谓词推送。
答案 1 :(得分:4)
另一种方法是组合谓词:
where case when translate(person_uid, '1234567890', ' ')) is null
then to_number(person_uid) end = 100
答案 2 :(得分:3)
当您将这些数字添加到WHERE子句时,它仍在执行这些检查。您无法保证WHERE子句中的顺序。因此,它仍然试图将100与'2-lkjsdf'进行比较。
你能用'100'吗?
答案 3 :(得分:2)
另一种选择是应用子选择
SELECT * FROM (
select person_uid
from s_contact
where decode(trim(translate(person_uid, '1234567890', ' ')), null, 'n', 'c') = 'n'
)
WHERE TO_NUMBER(PERSON_UID) = 100
答案 4 :(得分:0)
正常表达救援!
where regexp_like (person_uid, '^[0-9]+$')
答案 5 :(得分:-1)
使用查询的第一部分生成临时表。然后根据person_uid = 100或其他任何内容查询临时表。
问题在于,由于where子句中的附加和声明,oracle尝试将每个person_uid转换为int。此行为可能会也可能不会显示在预览中,具体取决于选择的记录。