Cannot SUM(TO_NUMBER(varchar2 field)) :ORA 01722 [ORACLE] - oracle

I have myfield as varchar2 type and I try to sum this field by using sum(to_number(myfield)) but the result is ORA-01722 invalid number.
before this error occured I used SUM(TO_NUMBER(REGEXP_REPLACE(BIKOU,'[[:alpha:]]', ''))) and it works but last week I put some decimal value in myfield so this code not work anymore.
Here is my example of data in myfield
10,12,13.5,NULL

If you're getting that error from a string like 13.5 then your session's NLS_NUMERIC_CHARACTERS seems to be set to use a comma as the decimal separator:
alter session set nls_numeric_characters=',.';
with your_table (bikou) as (
select '10' from dual
union all select '12' from dual
union all select '13.5' from dual
union all select null from dual
)
select SUM(TO_NUMBER(REGEXP_REPLACE(BIKOU,'[[:alpha:]]', '')))
from your_table;
SQL Error: ORA-01722: invalid number
You can either explicitly set the session to use a period as the decimal separator, or provide a format mask that uses a period:
select SUM(TO_NUMBER(REGEXP_REPLACE(BIKOU,'[[:alpha:]]', ''), '99999999.99999'))
from your_table;
SUM(TO_NUMBER(REGEXP_REPLACE(BIKOU,'[[:
---------------------------------------
35,5
Or use the decimal separator marker in the model and override the session's NLS setting:
select SUM(TO_NUMBER(REGEXP_REPLACE(BIKOU,'[[:alpha:]]', ''),
'99999999D99999', 'nls_numeric_characters=''.,'''))
from your_table;
SUM(TO_NUMBER(REGEXP_REPLACE(BIKOU,'[[:
---------------------------------------
35,5
The mask obviously has to be suitable for all the values you expect back from your regex; what I've used may not be quite right for your data.
This kind of issue is why you should not store numbers or dates as strings. Use the correct data type for your columns.

Related

Formatting a decimal column into euro currency

I need to format a decimal value into a string with the format ###.###,##.
I've already tried:
SELECT to_char (11230.3423, '999,990.00') FROM DUAL
I'll get 11,230.34 where i want 11.230,34.
If i change the format as:
SELECT to_char (11230.3423, '999.990,00') FROM DUAL
I'll get an error.
Note: I need to format Euro(€) values so decimal separator is ','.
Thanks.
Use:
SELECT to_char (11230.3423, 'FM999G990D00', 'NLS_NUMERIC_CHARACTERS = '',.''') FROM DUAL

Convert VARCHAR to NUMBER with decimal on Oracle

I'm having some trouble to convert VARCHAR2 informations, like:
-111.21
11.11
-51.146610399175472
to NUMBER. I want to store these numbers on a NUMBER(19,16) column. Mostly of these values are coordinates (latitude and longitude).
I already tried different commands with different values:
select cast('-111.21' as NUMBER) from dual
select cast('-111.21' as decimal) from dual
select cast('111.21' as decimal) from dual
select to_number('-1.1') from dual
select to_decimal('-1.1') from dual
But I always receive the error:
The specified number was invalid
This SQL:
select to_number('-134.33','099.99') from dual;
Works, but any change on the number (like change to '-34.33') return the same error.
What I'm doing wrong here? Obviously I'm missing something here but I can't figure out what.
I found the problem. I need to pass a mask as parameter to the to_number function. Like '999.999999999999999'
So:
select to_number('90.79493','999.999999999999999') from dual;
select to_number('90.146610399175472','999.999900000000000') from dual;
select to_number('90.34234324','999.999999999999999') from dual;
works for different size of numbers.

Oracle. CAST COLLECT for date datatype

Consider the types:
CREATE OR REPLACE TYPE date_array AS TABLE OF DATE;
CREATE OR REPLACE TYPE number_array AS TABLE OF NUMBER;
CREATE OR REPLACE TYPE char_array AS TABLE OF VARCHAR2(80);
Queries:
WITH q AS
(SELECT LEVEL ID,
TRUNC(SYSDATE) + LEVEL MyDate,
to_char(LEVEL) STRING
FROM dual
CONNECT BY LEVEL < 5)
SELECT CAST(COLLECT(ID) AS number_array)
FROM q;
return collection of numbers
WITH q AS
(SELECT LEVEL ID,
TRUNC(SYSDATE) + LEVEL MyDate,
to_char(LEVEL) STRING
FROM dual
CONNECT BY LEVEL < 5)
SELECT CAST(COLLECT(STRING) AS char_array)
FROM q;
return collection of strings
WITH q AS
(SELECT LEVEL ID,
TRUNC(SYSDATE) + LEVEL MyDate,
to_char(LEVEL) STRING
FROM dual
CONNECT BY LEVEL < 5)
SELECT CAST(COLLECT(MyDate) AS date_array)
FROM q
return error invalid datatype.
Can anyone explain why the Date datatype behaves differently?
Here are my findings... it seems you are facing a bug caused by the fact that calculated dates seem to have a different internal representation from "database" dates. I did find a workaround, so keep on reading.
On my oracle dev installation (Oracle 11g Enterprise Edition Release 11.2.0.4.0 - 64bit Production) I am experiencing your same problem.
BUT... If I create a physical table containing your test data:
create table test_data as
SELECT LEVEL ID,
TRUNC(SYSDATE) + LEVEL MyDate,
to_char(LEVEL) STRING
FROM dual
CONNECT BY LEVEL < 5
and then run the "cast collect" operator on this physical table, it works as expected:
-- this one works perfectly
SELECT CAST(COLLECT(MyDate) AS date_array) from test_data
but all these examples still do NOT work:
-- here I just added 1 .. and it doesn't work
SELECT CAST(COLLECT(MyDate + 1) AS date_array)
from test_data
-- here I am extracting sysdate, instead of a physical column... and it doesn't work
SELECT CAST(COLLECT(sysdate) AS date_array)
from test_data
It feels like oracle doesn't think that calculated dates are the same thing of physical dates
So, I tried to "persuade" oracle that the data I am providing is actually a normal DATE value, using an explicit cast... and EUREKA! this does the job correctly:
WITH q AS
(SELECT LEVEL ID,
-- this apparently unnecessary cast does the trick
CAST( TRUNC(SYSDATE) + LEVEL AS DATE) MyDate,
to_char(LEVEL) STRING
FROM dual
CONNECT BY LEVEL < 5)
SELECT CAST(COLLECT(MyDate) AS date_array)
FROM q
Yes... but why??
It really seems that these two values are not exactly the same thing, even if the values we see are actually the same:
select sysdate, cast (sysdate as date) from dual
So I dug in the internal representation of the two values applying the "dump" function to both of them:
select dump(sysdate), dump(cast (sysdate as date)) from dual
and these are the results I got:
DUMP(SYSDATE ) -> Typ=13 Len=8: 226,7,11,9,19,20,47,0
DUMP(CAST(SYSDATEASDATE) as DUAL) -> Typ=12 Len=7: 120,118,11,9,20,21,48
Internally they look like two totally different data types! one is type 12 and the other is type 13... and they have different length and representation.
Anyway I discovered something more.. it seems someone else has noticed this: https://community.oracle.com/thread/4122627
The question has an answer pointing to this document: http://psoug.org/reference/datatypes.html
which contains a lengthy note about dates... an excerpt of it reads:
"What happened? Is the information above incorrect or does the DUMP()
function not handle DATE values? No, you have to look at the "Typ="
values to understand why we are seeing these results. ". The datatype
returned is 13 and not 12, the external DATE datatype. This occurs
because we rely on the TO_DATE function! External datatype 13 is an
internal c-structure whose length varies depending on how the
c-compiler represents the structure. Note that the "Len=" value is 8
and not 7. Type 13 is not a part of the published 3GL interfaces for
Oracle and is used for date calculations mainly within PL/SQL
operations. Note that the same result can be seen when DUMPing the
value SYSDATE."
Anyway, I repeat: I think this is a bug, but at least I found a workaround: use an explicit cast to DATE.

Convert String to Date field for SQL Oracle

I need to convert a string to a date field. The field stores 30 characters. Dates, when present, are formatted as 'yyyymmdd' (20170202). In all cases, dates have 22 spaces after. I need to format this field as a date field like this: dd-mm-yyyy.
I've tried several formulas:
TO_CHAR(PERSACTION.NEW_VALUE_02, 'dd-mm-yyyy') ,TO_CHAR(PERSACTION.NEW_VALUE_02, 'yyyymmdd'), trim(TO_CHAR(PERSACTION.NEW_VALUE_02, 'yyyymmdd')) with error message: invalid number format model. Your expertise is welcome and appreciated.
to_char(to_date( rtrim(new_value_02), 'yyyymmdd'), 'dd-mm-yyyy')
Should do the trick. rtrim removes spaces on right side of string. Then I convert it to date using the date format specified, and then convert it to a string again in the desired format.
Did tried to convert to date format and then to char again?
TO_CHAR(TO_DATE(PERSACTION.NEW_VALUE_02,'yyyymmdd'),'dd-mm-yyyy')
Please, please, please do not store DATEs and CHARACTER datatypes. This will only lead to issues that can be avoided when using the DATE datatype.
If you want to change the string 20170202 to another string and not actually a date (which would have no intrinsic formatted text representation), you could optionally use a regular expression to transform it, instead of converting to a date and back:
select regexp_replace('20170202 ', '^(\d{4})(\d{2})(\d{2}) +$', '\3-\2-\1')
from dual;
REGEXP_REPLACE(
---------------
02-02-2017
Or you could use substr instead of regexp_substr, which may perform better even if you have to call it three times; using a CTE just to avoid repeating the value:
with t(str) as (
select '20170202 ' from dual
)
select substr(str, 7, 2) ||'-'|| substr(str, 5, 2) ||'-'|| substr(str, 1, 4)
from t;
SUBSTR(STR
----------
02-02-2017
If you do convert to a date and back you would uncover any values which cannot be converted, as they will cause an exception to be thrown. That would imply you have bad data; which would have been avoided by using the right data type in the first place, of course. These will convert any old rubbish, with varying results depending on how far the strings stray from the pattern you expect - but including strings like '20170231' which represent an invalid date. And null value or strings of just spaces will be converted to odd things with the substr version, but you could filter those out.
You can see the kind of variation you would get with some sample data that doesn't match your expectations:
with t(str) as (
select '20170202 ' from dual
union all select '20170231 ' from dual
union all select '2017020c ' from dual
union all select '2017020 ' from dual
union all select '201702021 ' from dual
union all select ' ' from dual
union all select null from dual
)
select str,
regexp_replace(str, '^(\d{4})(\d{2})(\d{2}) +$', '\3-\2-\1') as reg,
substr(str, 7, 2) ||'-'|| substr(str, 5, 2) ||'-'|| substr(str, 1, 4) as sub
from t;
STR REG SUB
------------- ------------- -------------
20170202 02-02-2017 02-02-2017
20170231 31-02-2017 31-02-2017
2017020c 2017020c 0c-02-2017
2017020 2017020 0 -02-2017
201702021 201702021 02-02-2017
- -
--
With the anchors and whitespace expectation, the regular expression doesn't modify anything that doesn't consist entirely of 8 numeric characters. But it can still form invalid 'dates'.

encountered an invalid number error calling the below oracle stored procedure

SELECT Ticket, ETRs, "Last ETR","Last ETR Time Change","STAR Restore Time","Restore Time - Last ETR"
FROM(
SELECT e.xsystemjob Ticket,
a.eventkey Event,
(select count(generatedtime) from obvwh.ops_ertchangelog_fact where eventkey = a.eventkey) ETRs,
to_char(a.ERT, 'MM/DD/YYYY HH24:MI:SS') "Last ETR", --GENERATEDTIME,
to_char(generatedtime, 'MM/DD/YYYY HH24:MI:SS') as "Last ETR Time Change",
to_char(e.restdate,'MM/DD/YYYY HH24:MI:SS') as "STAR Restore Time",
round(((e.restdate - a.generatedtime) * 1440),0) as "Restore Time - Last ETR"
FROM obvwh.ops_ertchangelog_fact a
join obvwh.ops_event_dim e
on a.eventkey = e.eventkey
where a.generatedtime = (select max(generatedtime) from obvwh.ops_ertchangelog_fact where eventkey = a.eventkey)
)
WHERE Substr(Ticket,0,1) = region
AND to_char("Last ETR", 'MM/DD/YYYY') between to_char(start_date,'MM/DD/YYYY') and to_char(end_date,'MM/DD/YYYY');
From you inner query, Last ETR is a string, representing the column value in format MM/DD/YYYY HH24:MI:SS. You're trying to convert that string to a string, passing a single format mask, and that is throwing the error.
You can see the same thing with a simpler demo:
select to_char('07/18/2016 12:13:14', 'MM/DD/YYYY') from dual;
Error report -
SQL Error: ORA-01722: invalid number
You could explicitly convert the string to a date and back again:
select to_char(to_date('07/18/2016 12:13:14', 'MM/DD/YYYY HH24:MI:SS'), 'MM/DD/YYYY') from dual;
TO_CHAR(TO
----------
07/18/2016
... but in the context of your comparison that doesn't really make sense anyway if the range you're comparing with can span a year end - the format mask you're using doesn't allow for simple comparison. Assuming start_date and end_date are dates (with their times set to midnight) you could do:
AND to_date("Last ETR", 'MM/DD/YYYY HH24:MI:SS') between start_date and end_date;
or even simpler, use the original raw ERT value (which is presumably already a date), and convert it to a string - if that is actually the right thing to do - in the outermost select list.
I'm not quite sure why you have an inline view here at all though, or why you're using a subquery to get the count since you're already querying ops_ertchangelog_fact - maybe you want to be using analytic functions here.

Resources