Convert VARCHAR to NUMBER with decimal on Oracle - 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.

Related

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.

Why invalid number error when working with string functions and string data?

Using LPAD requirement, all of my expressions are characters.
Why am I getting an ORA-01722: invalid number message?
I want to left pad my varchar2 value with leading zeroes.
All my airpor_codes are up to three bytes and I want to left pad with leading zeros, if necessary.
In this example, I want 0777 and 0LAX.
/* This works */
with numeric_airport_code
as (select '777' as airport from dual)
select airport,
to_char(airport,'0000') as to_char_padding,
lpad(airport,4,'000') as lpadding
from numeric_airport_code;
/* This gives invalid number error */
with alpha_airport_code
as (select 'LAX' as airport from dual)
select airport,
to_char(airport,'000') as to_char_padding,
lpad(airport,4,'000') as lpadding
from alpha_airport_code;
The lpad isn't the problem, it's the to_char(). In the second query you're doing:
to_char('LAX', '000')
which is trying to to an implicit conversion to number first:
to_char(to_number('LAX'), '000')
... and to_number('LAX') of course gets ORA-01722. It doesn't make sense to call to_char() for something that is already a string. At best you'll get unnecessary conversions, but often - as here - those are implicit and/or will fail.
If you only use lpad it does what you said you want:
with airport_code as (
select '777' as airport from dual
union all select 'LAX' as airport from dual
)
select airport,
lpad(airport, 4, '0') as lpadding
from airport_code;
AIR LPAD
--- ----
777 0777
LAX 0LAX
Also notice that you only need a single zero in the third argument.

Cannot SUM(TO_NUMBER(varchar2 field)) :ORA 01722 [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.

oracle sorting with text as number

My NLS settings:
NLS_SORT POLISH
NLS_COMP BINARY
Simple test query:
select * from (
select '11117' as x from dual
union
select '12988' as x from dual
union
select '14659' as x from dual
union
select '1532' as x from dual
union
select '18017' as x from dual
) order by x;
Actual result:
x
-----
11117
12988
14659
1532
18017
Desired result:
x
-----
1532
11117
12988
14659
18017
Question:
Is there a NLS setting that will help me achive desired result? I know I can do order by to_number(x) or, even better, order by lpad(x, 5), but it's not good in this case - I need a system-wide solution that won't require query change.
What I tried:
order by nlssort(x, 'nls_sort=binary');
alter session set nls_sort='binary';
Maybe a somewhat odd solution, but ...
Rename your TABLE_SOMETHING to TABLE_OTHER.
Create a view TABLE_SOMETHING on top of the TABLE_OTHER which will TO_NUMBER() the troubled string column of yours to the same column name.
If the table is, application-wide, being modified, then create INSTEAD OF triggers over the view TABLE_SOMETHING.
Recompile possibly invalidated packages.
Won't be particularly performant when it comes to modifying large quantities of records, won't allow for truncating the table, but it might solve the ordering problem.

Oracle lag function, can it accept a column alias?

I am trying to use the lag function so I can compare one column to the last without using a cursor.
However the column I need to compare against has to go by an alias as I am using 3 unions).
Here is an example of what I am up to.
SELECT
'Y' AS paid,
lag(paid,1) over (ORDER BY salary) AS prev_paid
FROM pay
UNION
SELECT
'N' as paid,
lag(paid,1) over (ORDER BY salary) AS prev_paid
FROM not_paid
I keep getting the Error: PL/SQL: ORA-00904: "paid": invalid identifier
I suspect you want something more like this:
SELECT paid, lag(paid,1) over (ORDER BY salary) AS prev_paid
FROM
(
SELECT 'Y' as paid, salary
FROM pay
UNION
SELECT 'N' as paid, salary
FROM not_paid
)
The general answer is no: in Oracle you can never use a column alias at the level where it is defined, except in order by clauses.
However, your query has other issues, since you're getting the lag value of a constant. #Tony Andrew's query seems like what you actually want.

Resources