带有数字和非数字数据的VARCHAR列,可通过WHERE子句中的数字比较进行过滤

时间:2015-02-18 07:30:15

标签: sql oracle performance

DB:Oracle(但我正在寻找ANSI SQL中的通用解决方案)

SELECT *
  FROM TABLE1 A INNER JOIN TABLE2 B ON A.FIELD_KEY = B.FIELD_KEY
 WHERE TO_NUMBER (FIELD_VALUE) < 10 

我有一个表(TABLE1)存储'Value'和'Frequency'。现在FIELD_VALUE列中的数据可以是数字也可以是非数字。此列的数据类型为VARCHAR2。我想过滤这个值,其中Value&lt; 10(说)。

我明白'哪里有TO_NUMBER(价值)&lt; 10'不起作用,因为值列也包含非数字数据。

但是,我正在使用表TABLE1加入表TABLE2这样,只有数字值才会在“值”列的结果集中返回,然后我将应用'Where TO_NUMBER(Value) )&lt; 10'这个已经过滤的结果集。我期待由于结果集已经过滤并且只包含“值”列中的数字数据,我应该能够使用“Where TO_NUMBER(Value)&lt;”进一步过滤结果集。 10'caluse但由于Oracle优化器改变了我的where子句和连接条件的顺序而没有发生这种情况,因此我得到'ORA-01722:无效数字'错误。

对我有用的解决方案是:

WITH BASE_QUERY
     AS (SELECT *
           FROM TABLE1 A INNER JOIN TABLE2 B ON A.FIELD_KEY = B.FIELD_KEY)
SELECT *
  FROM    BASE_QUERY A
       INNER JOIN
          BASE_QUERY B
       ON A.VALUE = B.VALUE AND TO_NUMBER (FIELD_VALUE) < 10

但我要在这里进行自我加入,这是昂贵且不必要的。另外,我不确定这个解决方案是否会一直有效。我的意思是,如果Oracle更改执行计划并在加入表之前执行TO_NUMBER (FIELD_VALUE) < 10,那么查询可能会再次失败,并显示相同的“ORA-01722:无效数字”错误。

问题:

  1. 我的解决方案是否始终有效?
  2. 我的解决方案是否合理有效?
  3. 有更好的方法吗?

2 个答案:

答案 0 :(得分:0)

  

但我在这里进行自我加入,这是昂贵且不必要的。

所以根本不要加入。您只需要FIELD_VALUE只有 DIGIT 的行。您想忽略 ALPHANUMERIC 值。因此,只过滤掉数字。

例如,

翻译和替换

SQL> WITH DATA AS(
  2  SELECT 'mix-'||LEVEL str FROM dual CONNECT BY LEVEL <=10
  3  UNION ALL
  4  SELECT to_char(LEVEL) FROM dual CONNECT BY LEVEL <=10
  5  )
  6  SELECT str FROM DATA
  7  WHERE REPLACE(translate(str, '0123456789',' '), ' ') IS NULL
  8  /

STR
--------------------------------------------
1
2
3
4
5
6
7
8
9
10

10 rows selected.

SQL>

<强> REGEXP_LIKE

SQL> WITH DATA AS(
  2  SELECT 'mix-'||LEVEL str FROM dual CONNECT BY LEVEL <=10
  3  UNION ALL
  4  SELECT to_char(LEVEL) FROM dual CONNECT BY LEVEL <=10
  5  )
  6  SELECT str FROM DATA
  7  WHERE REGEXP_LIKE(str, '^[[:digit:]]+')
  8  /

STR
--------------------------------------------
1
2
3
4
5
6
7
8
9
10

10 rows selected.

SQL>

就个人而言,我会选择TRANSLATE和REPLACE,因为REGEXP仍然非常耗费资源。

答案 1 :(得分:0)

如果您可以保证field_value中以数字开头的内容是严格数字的,您可以使用过滤表的子查询,只留下field_value列中包含数字内容的记录:

select *
  from (
           select t1.*
             from table t1
            where t1.field_value >= '0'
              and t1.field_value <= chr(ascii('9')+1) -- ':'; invariant to charset and encoding
       ) t
 where to_number(t.field_value) < 10
     ;

我宁愿建议你使用Lalit Kumar B的解决方案之一,这个解决方案似乎更加强大(考虑放松未来的“从初始数字推断数字”政策)。