Skip to Main Content

Breadcrumb

Question and Answer

Tom Kyte

Thanks for the question, manasi.

Asked: April 30, 2008 - 6:38 pm UTC

Last updated: March 23, 2009 - 10:05 am UTC

Version: 10.2.0.3

Viewed 10K+ times! This question is

You Asked

When I am running the following query its giving ORA-01722.
SELECT *
FROM v_indexed_docs
WHERE screen_name = DECODE (0, 1, NULL, 'ORD_F010')
AND screen_type = DECODE (0, 1, NULL, 'O')
AND (publish_flag = 'Y')
AND ( doc_attribute4 = 'ALL'
OR (doc_attribute4 = 'OWNER' AND created_by = 'JOHNSONN')
OR (doc_attribute4 = 'BUS_DVN' AND doc_attribute3 = 'FWD')
OR ( doc_attribute4 = 'ROLES'
AND EXISTS (
SELECT 1
FROM usg_user_group_role t
WHERE t.user_id = 'JOHNSONN'
AND t.user_group_cd IN (
SELECT u.user_group_cd
FROM doc_documents x, usg_user_group_role u
WHERE x.created_by = u.user_id
AND v_indexed_docs.document_id =
x.document_id))
)
)
AND ( ( entity_name = 'CON_DODGE'
AND (pk1_value, pk2_value) IN (
SELECT (t.contract_nbr), (t.version_nbr)
FROM con_contract t
WHERE t.control_nbr = 'N8964'
AND t.control_month = 'DEC'
AND t.control_year = '2007')
)
OR ( entity_name = 'VIEW_PEND'
AND pk1_value = 'N8964'
AND pk2_value = 'DEC'
AND pk3_value = '2007'
)
OR ( entity_name = 'FIN_CONTROL'
AND pk1_value = 'N8964'
AND pk2_value = 'DEC'
AND pk3_value = '2007'
)
)
ORDER BY user_entity_name,
creation_date DESC,
pk1_value,
pk2_value,
pk3_value,
document_description
I found that its giving error for the portion in bold. The datatype of the field is varchar2(15). Even I tried increasing the size. But its not working.
The same piece of code is working in 8i. We have recently upgraded to 10g.
Can you please help me out.
Thanks & Regards

I arrived at the conclusion by commenting out portions of where cluase and then uncommenting if it ran successfully.
I also found that
- commenting the entire order by clause makes it run successfully
- commenting the following portion does not give error msg
AND (pk1_value, pk2_value) IN (
SELECT (t.contract_nbr), (t.version_nbr)
FROM con_contract t
WHERE t.control_nbr = 'N8964'
AND t.control_month = 'DEC'
AND t.control_year = '2007')
So, I am not able to figure out the exact point of error.

Create statement:
CREATE table V_INDEXED_DOCS
(
SCREEN_NAME VARCHAR2(30),
SCREEN_TYPE VARCHAR2(1),
PUBLISH_FLAG VARCHAR2(1),
CREATED_BY VARCHAR2(15),
CREATION_DATE DATE,
DOC_ATTRIBUTE1 VARCHAR2(100),
DOC_ATTRIBUTE2 VARCHAR2(100),
DOC_ATTRIBUTE3 VARCHAR2(100),
DOC_ATTRIBUTE4 VARCHAR2(100),
DOCUMENT_ID NUMBER,
DOCUMENT_DESCRIPTION VARCHAR2(255),
ENTITY_NAME VARCHAR2(40),
USER_ENTITY_NAME VARCHAR2(40),
PK1_VALUE VARCHAR2(100),
PK2_VALUE VARCHAR2(100),
PK3_VALUE VARCHAR2(100),
PK1_VALUE VARCHAR2(100)
)
create table USG_USER_GROUP_ROLE
(
USER_ID VARCHAR2(10) not null,
USER_GROUP_CD VARCHAR2(3) not null,
USER_UPD VARCHAR2(10) not null,
DATE_UPD DATE not null
)
create table CON_CONTRACT
(
CONTRACT_NBR NUMBER not null,
VERSION_NBR NUMBER not null,
BUS_DVN_CD VARCHAR2(3) not null,
CONTROL_NBR VARCHAR2(5),
CONTROL_MONTH VARCHAR2(3),
CONTROL_YEAR NUMBER(4),
CON_REF_NBR VARCHAR2(12),
CONTRACT_BILLING_STATUS VARCHAR2(3),
ORIGIN_STATUS VARCHAR2(3),
USER_UPD VARCHAR2(10) not null,
DATE_UPD DATE not null,
CANCELLABLE_IND VARCHAR2(1) default 'Y' not null,
PARENT_CONTRACT_NBR NUMBER,
PARENT_VERSION_NBR NUMBER,
INT_CON_IND VARCHAR2(1) default 'N' not null,
INT_REF_NBR VARCHAR2(12),
CONTRACT_BILL_END_DATE DATE,
)

Cut paste of query failing in sqlplus:

1 SELECT *
2 FROM v_indexed_docs
3 WHERE screen_name = DECODE (0, 1, NULL, 'ORD_F010')
4 AND screen_type = DECODE (0, 1, NULL, 'O')
5 AND (publish_flag = 'Y')
6 AND ( doc_attribute4 = 'ALL'
7 OR (doc_attribute4 = 'OWNER' AND created_by = 'JOHNSONN')
8 OR (doc_attribute4 = 'BUS_DVN' AND doc_attribute3 = 'FWD')
9 OR ( doc_attribute4 = 'ROLES'
10 AND EXISTS (
11 SELECT 1
12 FROM usg_user_group_role t
13 WHERE t.user_id = 'JOHNSONN'
14 AND t.user_group_cd IN (
15 SELECT u.user_group_cd
16 FROM doc_documents x, usg_user_group_role u
17 WHERE x.created_by = u.user_id
18 AND v_indexed_docs.document_id =
19 x.document_id))
20 )
21 )
22 AND ( ( entity_name = 'CON_DODGE'
23 AND (pk1_value, pk2_value) IN (
24 SELECT (t.contract_nbr), (t.version_nbr)
25 FROM con_contract t
26 WHERE t.control_nbr = 'N8964'
27 AND t.control_month = 'DEC'
28 AND t.control_year = '2007')
29 )
30 OR ( entity_name = 'VIEW_PEND'
31 AND pk1_value = 'N8964'
32 AND pk2_value = 'DEC'
33 AND pk3_value = '2007'
34 )
35 OR ( entity_name = 'FIN_CONTROL'
36 AND pk1_value = 'N8964'
37 AND pk2_value = 'DEC'
38 AND pk3_value = '2007'
39 )
40 )
41 ORDER BY user_entity_name,
42 creation_date DESC,
43 pk1_value,
44 pk2_value,
45 pk3_value,
46* document_description
SQL> /
AND (pk1_value, pk2_value) IN (
*
ERROR at line 23:
ORA-01722: invalid number


SQL>

and Tom said...

The problem is

you are comparing a number to a string:


  AND (pk1_value, pk2_value) IN (
              SELECT (t.contract_nbr), (t.version_nbr)
              FROM con_contract t 


pk1_value is a string, contract_nbr is a NUMBER


and you are PRESUMING that there is some order of operation in sql - which there is not

and in the other release you got LUCKY that the plan that accidentally got used did not hit this - totally by accident (we could get the same error in all releases)

this is because when you compare STRING to NUMBER, we have to TO_NUMBER(STRING) to compare.

And many of your pk1_value/pk2_values are NOT NUMBERS


therefore, the only thing that makes sense is to avoid implicit conversions, you need to convert your NUMBERS to STRINGS
  AND (pk1_value, pk2_value) IN (
              SELECT <b>to_char(t.contract_nbr), to_char(t.version_nbr)
            </b>  FROM con_contract t 



this is a bug in your logic, comparing strings to numbers and some of the strings do not contain numbers!!!


You might need a format in your to_char to make the comparision "work" right (eg: right now a pk1_value of 0, 00, 000, 0000, 00000, 00000000 are all equal to "0" - think about that for a while - your comparison is sort of flaky all around here)



this has NOTHING whatsoever to do with the predicate you isolated. as soon as you posted I knew what was wrong - I needed the create tables to tell you WHERE you went wrong, then it was easy.

That predicate has nothing to do with it - other then without that predicate - we chose a plan that for your CURRENT set of data - just happened to accidentally not get the error - add a row - and it could.

Rating

  (7 ratings)

Is this answer out of date? If it is, please let us know via a Comment

Comments

Calculate days between two different dates

Pawan Dohan, May 30, 2008 - 5:49 am UTC

when i run the query then it returns ORA.01722 invalid number

i am calculating days between sysdate and c_attribute8 .
Pls give me answer as soon as possible......



SELECT a.SERIAL_NUMBER,
a.ATTRIBUTE3 Eq_Make,
a.attribute4 Eq_Model,
a.attribute5 Eq_Sr_No,
a.attribute7 Registration_no,
TO_CHAR(TO_DATE(b.c_attribute8 ,'YYYY/MM/DD hh24:mi:ss'),'DD/MON/YY ') Local_Permit_Due_date,
b.c_attribute7 Local_Permit_Amt,
c.organization_code,
TO_CHAR(TO_DATE(SYSDATE,'DD/MON/YY hh24:mi:ss'),'DD/MM/YY')- TO_CHAR(TO_DATE(b.c_attribute8,'YYYY/MM/DD hh24:mi:ss'),'DD/MM/YY')Days
FROM apps.MTL_EAM_ASSET_NUMBERS_ALL_V a,
apps.MTL_EAM_ASSET_ATTR_VALUES_v b,
apps.org_organization_definitions c
WHERE a.serial_number=b.serial_number
AND a.current_organization_id=c.organization_id
AND b.attribute_category ='Insurance Info'
AND TO_CHAR(TO_DATE(b.c_attribute8,'YYYY/MM/DD hh24:mi:ss'),'DD/MON/YY')<= TO_DATE(TO_DATE(SYSDATE,'DD/MON/YY hh24:mi:ss'),'DD/MON/YY')



Tom Kyte
May 30, 2008 - 7:33 am UTC

... TO_CHAR(TO_DATE(SYSDATE,'DD/MON/YY hh24:mi:ss'),'DD/MM/YY')-
TO_CHAR(TO_DATE(b.c_attribute8,'YYYY/MM/DD hh24:mi:ss'),'DD/MM/YY')Days
....


can you please explain to me the thought process that brings you to a point that says "we should convert dates, which support date arithmetic, into strings to substract them"

why would you try to substract STRINGS

the default, implied conversion for 'string' - 'string' would be

to_number( 'string' ) - to_number( 'string' );



ops$tkyte%ORA11GR1> select *
  2    from dual
  3   where TO_CHAR(TO_DATE(SYSDATE,'DD/MON/YY hh24:mi:ss'),'DD/MM/YY')-TO_CHAR(TO_DATE(SYSDATE,'DD/MON/YY hh24:mi:ss'),'DD/MM/YY') = 0
  4  /

Execution Plan
----------------------------------------------------------
Plan hash value: 3752461848

---------------------------------------------------------------------------
| Id  | Operation          | Name | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |      |     1 |     2 |     2   (0)| 00:00:01 |
|*  1 |  FILTER            |      |       |       |            |          |
|   2 |   TABLE ACCESS FULL| DUAL |     1 |     2 |     2   (0)| 00:00:01 |
---------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter(TO_NUMBER(TO_CHAR(TO_DATE(TO_CHAR(SYSDATE@!),'DD/MON/YY
              hh24:mi:ss'),'DD/MM/YY'))-TO_NUMBER(TO_CHAR(TO_DATE(TO_CHAR(SYSDA
TE@!),'

              DD/MON/YY hh24:mi:ss'),'DD/MM/YY'))=0)




to get the difference in days between two dates, all one does is SUBTRACT


sysdate - b.c_attribute8


that is it, just subtract.



you do not know how much this scares me:


AND TO_CHAR(TO_DATE(b.c_attribute8,'YYYY/MM/DD hh24:mi:ss'),'DD/MON/YY')<= 
TO_DATE(TO_DATE(SYSDATE,'DD/MON/YY hh24:mi:ss'),'DD/MON/YY')




do you even understand what that does??????

Let us look at just:

TO_DATE(TO_DATE(SYSDATE,'DD/MON/YY hh24:mi:ss'),'DD/MON/YY')

first

that is really:

TO_DATE(to_char(TO_DATE( to_char(SYSDATE),'DD/MON/YY hh24:mi:ss')),'DD/MON/YY')


the only way to "to_date" something is to have a string. sysdate is a date, not a string, so we have to implicitly convert it to a string using the default date format.

then you convert back into a date using 'dd/mon/yyyy hh24:mi:ss'

well, your default date format is probably what mine is - dd-mon-yyyy, so the time component is GONE, wiped out.

Then, then you TO_DATE the DATE again - that is, you convert the date you just "broke" since you took a date, put it into a string, put it into a date - all with different formats and you convert it

BACK INTO A STRING

to convert it into a date - using the default format this time.



man, there is so much wrong there.


And then, you compare that date to...................


a string:

TO_CHAR(TO_DATE(b.c_attribute8,'YYYY/MM/DD hh24:mi:ss'),'DD/MON/YY')


come on.



dates are comparable. Please, for the love of whatever, just code:


where b.c_attribute8 <= sysdate


or if you want everything based on just the yyyy-mm-dd component (not time) then

where b.c_attribute8 < trunc(sysdate,'d')+1


but stop it, just STOP it with the to_char and to_dates! Pretend that dates are numbers in your head

you can subtract two numbers - you can subtract two dates
you can compare two numbers for equality, greater than, less then - same with dates


true, you cannot add, multiply, or divide two dates - but to that I say "so what, under what possible set of circumstances could you see yourself trying to do so"


so, just pretend they are numbers - and stop it with to_char/to_date.

what was it you were saying about certification?

graeme king, May 30, 2008 - 9:44 am UTC

great analysis tom but very sad all the same.

stop being god like

david hackett, July 01, 2008 - 9:26 am UTC

One can answer a question, explain a logical sequence without being abrasive.
Everything is easy once you know it!!!!!!!!!!!!!!


Tom Kyte
July 06, 2008 - 7:15 pm UTC

implicit conversions

to_date( a_date_already )

to_char( a_string_already )

I cannot think of how to say the obvious anymore. Really - I cannot.


Do you see this:

TO_DATE(TO_DATE(SYSDATE,'DD/MON/YY hh24:mi:ss'),'DD/MON/YY')


to_date(sysdate)

TO_DATE - of SYSDATE

take sysdate, a date, convert into a date.

I'm scared, I'm very very very scared - this is this most basic of basic and has nothing to do with Oracle at all - this is like "really basic".

And it scares the whatever out of me.

invalid number

WAWAN LENGKOANO, July 19, 2008 - 7:08 am UTC

when i running this query i get INVALID NUMBER.

update biodata_wni
set nik=getNik(trim(to_char(no_prop,'00'))|| trim(to_char(no_kab,'00'))||TRIM(to_char(no_kec,'00'))||
TRIM(decode(jenis_klmin,1,to_char(tgl_lhr,'dd'),2, to_char(tgl_lhr,'dd')+40))||
TRIM(to_char(tgl_lhr,'mmyy'))) where substr(nik,7,6) <> to_char(tgl_lhr,'ddmmyy') and jenis_klmin=1
or substr(nik,7,6) <> (to_char(tgl_lhr,'dd')+40 || to_char(tgl_lhr,'mmyy')) and jenis_klmin=2;


where the function GetNik :

(newpossible VARCHAR2) RETURN VARCHAR2 IS
gennik NUMBER(4);
query VARCHAR2(50);
s VARCHAR2(1000);
string VARCHAR2(50);
err_msg varchar2(1000);
BEGIN
string:=trim(CONCAT('NIK',newpossible));
SELECT curr_val INTO gennik FROM SEQN_MONTR WHERE sequence_name=string;
gennik:=gennik+1;
UPDATE SEQN_MONTR SET curr_val=gennik WHERE sequence_name=string;
commit;
RETURN(CONCAT(newpossible,trim(TO_CHAR(gennik,'0000'))));
EXCEPTION
WHEN OTHERS THEN
BEGIN
gennik:=1;
INSERT INTO SEQN_MONTR VALUES(string,SYSDATE,gennik);
commit;
RETURN(CONCAT(newpossible,trim(TO_CHAR(gennik,'0000'))));
EXCEPTION
WHEN OTHERS THEN
err_msg := SUBSTR (SQLERRM, 1, 800);
RETURN(err_msg);
END;
END;



thank you for your attention.
Tom Kyte
July 22, 2008 - 9:57 am UTC

EXCEPTION
            WHEN OTHERS THEN
        err_msg := SUBSTR (SQLERRM, 1, 800);
                  RETURN(err_msg); 


I give up, i just give up...

why do people do this? Can anyone - anyone give me a logical reason for doing this?


and why would you run an update inside of an update like that - this is so dangerous


Holy cow, holy molely, holy whatever you want to call it.

string:=trim(CONCAT('NIK',newpossible));
SELECT curr_val INTO gennik FROM SEQN_MONTR WHERE sequence_name=string;
gennik:=gennik+1;
UPDATE SEQN_MONTR SET curr_val=gennik WHERE sequence_name=string;
commit; 



A do it yourself sequence. OUCH

and one that does not even work!!!

ouch even more.

look at your logic.

a) read out a value (ok, ask yourself what happens when 2 people do this at the same time, they get what - the same value!)

b) then they both increment it (from say 1 to 2)

c) then they both update it to 2...


and we'll presume this is supposed to be a unique key, but bummer - it is not unique.


step #1 for you

drop function getnik;

create sequence getnik_seq start with <whatever, big number, bigger than you have>;

period, never ever use when others. just stop.

never ever.

look at your code, how does the caller differentiate between:

a) we had an error, here is 800 useless characters of junk that you cannot use, but you might try to use because you were expecting us to return to you ..... a sequence..

b) here is the data you were expecting.



THEY CANNOT


just drop this function. period, now, right away.


Is this a Bug?

Chinni, March 19, 2009 - 4:51 am UTC

Hi Tom,
am getting ORA-01722: invalid number error in 10g,but not in 9i.


In 10g
--------
SELECT COUNT(1) FROM dcsw_owner.STATIC_META_INFO
WHERE LABEL_NAME = 'APL_STATUS' AND STATUS = 'P'
AND ACCESS_KEY = 140

or

SELECT COUNT(1) FROM dcsw_owner.STATIC_META_INFO
WHERE LABEL_NAME = 'APL_STATUS' AND STATUS = 'P'
AND to_number(ACCESS_KEY) = 140

Gives ORA-01722: invalid number

SELECT COUNT(1) FROM dcsw_owner.STATIC_META_INFO
WHERE LABEL_NAME = 'APL_STATUS' AND STATUS = 'P'
AND ACCESS_KEY = '140'

No Error now.

In 9i ALL the three statements work fine. Data of this table in both 9i and 10 is same. Is this a Bug??

ACCESS_KEY is a varchar2(20) column and which has character strings also. I know that we should not compare numbers with varchar2, but my production is in 9i on which this working fine, but we have replica of production migrated to 10g on which this does not work, changing the query mean we have to change at many places. But without changing this query, can I avoid this error? when I compare access_key column as '140' it works when i compare as 140 or to_number('140') it does not work. But all the queries work just fine in 9i, data in the table is absolutely same in both the databases. Please suggest

Thank You

Tom Kyte
March 19, 2009 - 10:52 am UTC

... Is this a Bug??
...

Yes, but not the way you think. It is a bug in your developed application.

... ACCESS_KEY is a varchar2(20) column and which has character strings also. I ...

No, no no - access_key is a varchar2(20) column which holds ONLY character strings. I know that is a fact because that is the only thing a varchar2(20) can hold!!!!!!


You are assuming a where clause is processed in "some order"


Tell me, what order will this predicate be evaluated in?

WHERE LABEL_NAME = 'APL_STATUS' AND STATUS = 'P'
AND to_number(ACCESS_KEY) = 140


What part is done first? And why do you think so?

That question is quite simply not answerable - not by you, not by me, not by ANYONE.

You assume it is left to right maybe, but it isn't - it could be middle out, right to left, random - whatever we feel like.

The query was definitely subject to failure in 9i as well, that it did not was just unfortunate luck for you (you were lulled into believing it was OK)


two things

a) stop using count(1), why would you count 1's when what you obviously mean to do is COUNT ROWS. count(*) says "count rows". Count(1) says "count the rows such that 1 is not null". Which is a more correct description of what you are trying to accomplish? (pet peeve of mine...)

b) do not compare a string to a number, always explicitly convert - and compare strings to strings.



In your case, the only feasible solution would be to use CASE if you need to convert the string into a number to compare:

SELECT COUNT(*) 
  FROM dcsw_owner.STATIC_META_INFO
 WHERE LABEL_NAME = 'APL_STATUS'  
   AND STATUS = 'P' 
   AND case when LABEL_NAME = 'APL_STATUS' and STATUS = 'P'
            then to_number(ACCESS_KEY)
         end = 140





Predicate order is different..??

Chinni, March 19, 2009 - 5:27 am UTC

Hi Tom,
I just observed that the access_key is listed as first join in predicate information in 10g whereas it is listed as the last join in 9i.

--

analyze table dcsw_owner.STATIC_META_INFO delete statistics

/* Formatted on 2009/03/19 14:27 (Formatter Plus v4.8.8) */
BEGIN
DBMS_STATS.gather_table_stats
(ownname => 'DCSW_OWNER',
tabname => 'STATIC_META_INFO',
estimate_percent => DBMS_STATS.auto_sample_size,
CASCADE => TRUE,
method_opt => 'FOR ALL INDEXED COLUMNS SIZE AUTO'
);
END;

EXPLAIN PLAN FOR
/* Formatted on 2009/03/19 14:30 (Formatter Plus v4.8.8) */
SELECT access_key, value_string
FROM dcsw_owner.static_meta_info
WHERE label_name = 'APL_STATUS'
AND status = 'P' AND access_key = 140
ORDER BY value_string

SELECT * FROM TABLE(dbms_xplan.display)



9i
-----

--------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost |
--------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 63 | 11 |
| 1 | SORT ORDER BY | | 1 | 63 | 11 |
|* 2 | TABLE ACCESS FULL | STATIC_META_INFO | 1 | 63 | 7 |
--------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

2 - filter("STATIC_META_INFO"."LABEL_NAME"='APL_STATUS' AND
"STATIC_META_INFO"."STATUS"='P' AND TO_NUMBER("STATIC_META_INFO"."ACCESS_K
EY")=140)




10g
-----

---------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 63 | 8 (13)| 00:00:01 |
| 1 | SORT ORDER BY | | 1 | 63 | 8 (13)| 00:00:01 |
|* 2 | TABLE ACCESS FULL| STATIC_META_INFO | 1 | 63 | 7 (0)| 00:00:01 |
---------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

2 - filter(TO_NUMBER("ACCESS_KEY")=140 AND "LABEL_NAME"='APL_STATUS' AND
"STATUS"='P')


Can I avoid this without changing the queries? Please suggest

Thank You
Tom Kyte
March 19, 2009 - 10:53 am UTC

NO, you cannot

you have, you have always had, you will continue to have a bug in your developed code until you rewrite the query one way or another.


Thank You

Chinni, March 19, 2009 - 1:35 pm UTC

Thanks Tom for your reply. So there is no way for me now then. I have to change all the queries written like this. I wish this would come in 9i (my prod) db also.

Thank You
Tom Kyte
March 23, 2009 - 10:05 am UTC

it *can* come in your 9i database - different access plans are always possible.