简体   繁体   中英

Oracle ordering of results using a mixed varchar column but numeric where clause

I have a table with a VARCHAR2 column which contains values that are a mixture of pure-numbers and alpha-numerics. I have a CODE column that contains:

200
215
220
A553
D545
etc.

The following query works:

select *
from   TABLE
where  CLASS = 3
AND    (CODE >= 210 and CODE < 220) or CODE = 291)

Values that are CLASS 3 are always numeric.

But when I add a ORDER BY , it doesn't work:

select *
from   TABLE
where  CLASS = 3
and    (CODE >= 210 and CODE < 220) or CODE = 291)
ORDER BY CODE

instead I get ORA-01722: invalid number . This seems to be because the Oracle Optimiser is assessing the "order by" before the where clause, and thus non-numeric values get assessed.

I have tried changing it to ORDER BY TO_CHAR(CODE) but to no affect. Similar negative result with trying to place it all into a sub-query.

So, how do I order the results of this query by CODE ( ASC ending)? I guess I can specify all possible CODE values manually in the where clause as strings (ie code = '210' or code = '211' or... ), but is there a more elegant way?

The ORDER BY has nothing to do with the problem -- at least not directly.

SQL in general, and Oracle in particular, make no promises about the order of evaluation of conditions in the WHERE clause. Hence, the WHERE clause is not (necessarily) evaluated in the order written. The presence of the ORDER BY might affect the order of evaluation of the conditions in this particular case.

In general, it is really bad practice to mix data types, the way that you are doing it. But, you can guarantee the order of evaluation by using case :

select *
from   TABLE
where  CLASS = 3 
       'true' = (case when class <> 3 then 'false'
                      when (CODE >= 210 and CODE < 220) or CODE = 291) then 'true'
                 end);

I do not recommend doing this. I only want to point out that case does force the order of evaluation of the conditions.

The correct solution is to use string comparisons. In this case, I would go with:

select *
from   TABLE
where  CLASS = 3 AND
       CODE in ('210', '211', '212', '213', '214', '215', '216', '217', '218', '219', '291')

Alternatively, you could do:

where  CLASS = 3 and length(CODE) = 3 and
       ((CODE >= '210' and CODE < '220') or CODE = '291')

Note that for accuracy you do need to take the length into account.

The problem can be in your WHERE condition, given that it forces Oracle to cast your code to number;

Try keeping the WHERE condition in varchar2 format:

with TABLE_(code, class_) as
(
select '200',3 from dual union all
select '215',3 from dual union all
select '220',3 from dual union all
select 'A553',3 from dual union all
select 'D545',3 from dual
)
select *
from   TABLE_
where  CLASS_ = 3
and   ( (CODE >= '210' and CODE < '220') or CODE = '291')
ORDER BY CODE

Since the data type of code column is VARCHAR2 , you must compare it as a string in the where clause not as a number.

Simple error reproduction :

SQL> SELECT * FROM DUAL WHERE DUMMY = 10;
SELECT * FROM DUAL WHERE DUMMY = 10
                         *
ERROR at line 1:
ORA-01722: invalid number


SQL>

You need to use single-quotation marks to make it a string and pass the values as IN-list rather than a range . Numeric operations on characters will use ASCII value for arithmetic.

For example,

SQL> WITH sample_data AS(
  2  SELECT '210' code FROM dual UNION ALL
  3  SELECT '2101' code FROM dual UNION ALL
  4  SELECT '220' code FROM dual UNION ALL
  5  SELECT 'A123' code FROM dual
  6  )
  7  --end of sample_data mimicking real table
  8  SELECT code
  9  FROM sample_data
 10  WHERE code IN ('210', '220')
 11  ORDER BY code;

CODE
----
210
220

SQL>

Now coming to the ORDERING of rows as number and not string , you must use TO_NUMBER in the SELECT and use the same alias in the ORDER BY clause.

For example,

SQL> WITH sample_data AS(
  2  SELECT '210' code FROM dual UNION ALL
  3  SELECT '2101' code FROM dual UNION ALL
  4  SELECT '220' code FROM dual UNION ALL
  5  SELECT 'A123' code FROM dual
  6  )
  7  --end of sample_data mimicking real table
  8  SELECT to_number(code) code_num
  9  FROM sample_data
 10  WHERE code IN ('210', '220')
 11  ORDER BY code_num;

  CODE_NUM
----------
       210
       220
SELECT *
FROM   table_name
WHERE  class = 3
AND    (  ( code >= '210' AND code < '220' AND LENGTH( code ) = 3 )
       OR code = '291' )
ORDER BY CODE; 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM