Suggestion for the use of to_number in Oracle - sql

Below query is giving error.
select sob.set_of_books_id, orginfo.org_information1
from
gl_sets_of_books sob,
hr_organization_information orginfo
where
sob.set_of_books_id = to_number(orginfo.org_information1);
The reason is set_of_books_id is number column and org_information1 is varchar which is containing string and also numeric string. so it is type mismatch. we have to pick only those values which has numeric string in org_information1
to overcome this we used regexp_like which will pick only record which are numeric.
select sob.set_of_books_id, orginfo.org_information1
from hr_organization_information orginfo, gl_sets_of_books sob
where sob.set_of_books_id = to_number(orginfo.org_information1)
and REGEXP_LIKE(orginfo.org_information1, '^[[:digit:]]+$');
We just added this line and REGEXP_LIKE(orginfo.org_information1, '^[[:digit:]]+$'); in previous query and it is working properly.
My question is even in the last query, we are using where clause and joining same condition which was failing in first query .and where will definetly run before AND. so why it is not failing? will it fail for some records? or the query is proper?
is there any better way to use the second query.
if I am not using existing where condition then it is giving error. i dont know what is the issue with below query.
We can not use to_char on set_of_books_id because that column contains index.and we cant modify to create func based index.We have to use index in our query

where will definitely run before and
No!
The optimizer is free to rearrange the conditions in your query. There's no guarantee it processes these top-to-bottom. This can lead to surprising effects if the plan changes (e.g. because you added/removes indexes).
Assuming you're on 12.2 or higher, instead of a regex you can use the on conversion error clause to map all the non-numeric values to null:
with rws as (
select level x,
case mod ( level, 3 )
when 0 then chr ( level+64 )
else to_char ( level )
end y
from dual
connect by level <= 10
)
select y from rws
where x = to_number ( y default null on conversion error );
Y
1
2
4
5
7
8
10

Use TO_CHAR on the number column rather than TO_NUMBER on the string column:
select sob.set_of_books_id,
orginfo.org_information1
from hr_organization_information orginfo
INNER JOIN gl_sets_of_books sob
ON ( TO_CHAR(sob.set_of_books_id) = orginfo.org_information1 );
Or, from Oracle 12.2, you can use TO_NUMBER(value DEFAULT NULL ON CONVERSION ERROR):
select sob.set_of_books_id,
orginfo.org_information1
from hr_organization_information orginfo
INNER JOIN gl_sets_of_books sob
ON ( sob.set_of_books_id
= TO_NUMBER(orginfo.org_information1 DEFAULT NULL ON CONVERSION ERROR)
);

Related

How to replace characters at specific position in several words using REGEX_REPLACE

I have a query similar to this:
SELECT YEAR_CODE FROM YEAR_CODES
and it returns several records: typically 1 but sometimes 2 or 3. The returned records look like this: 2018FOO, 2019BAR
I need to get the matching previous year of the returned codes. For instance:
2018FOO becomes 2017FOO
2019BAR becomes 2018BAR
Looking for something similar to:
REGEX_REPLACE(SELECT YEAR_CODE FROM YEAR_CODES, 4th character, 4th character minus 1)
You don't need regexp_replace(), using substr() string operator with concat() function (or concatenation operators ||) is enough :
with year_codes(year_code) as
(
select '2018FOO' from dual union all
select '2019BAR' from dual
)
select concat(substr(year_code,1,4) - 1,substr(year_code,-3)) as year_code
from year_codes;
YEAR_CODE
---------
2017FOO
2018BAR
to_number() conversion is redundant, since Oracle implicitly considers a string as a number which is completely composed of digits for an arithmetic operation.
You can do use string operations:
with c as (
<your query here>
)
select
from year_code yc
where to_number(substr(yc.code, 1, 4)) = to_number(substr(c.code)) - 1 and
substr(yc.code, 5) = substr(c.code, 5)

How to use INTO and GROUP BY clause together

SELECT cast ( SUBSTRING ( CAST ("ProcessingDate" AS text), 5, 2 ) as integer),
COUNT(*) INTO resultValue1,resultValue2
FROM "DemoLogs"."Project"
WHERE "Addr" = 'Y' AND "ProcessingDate" >= 20160110
GROUP BY 1
ORDER BY 1;
In my database, the ProcessingDate is stored as YYYYMMDD. So, I am extracting its month from it.
This query is working fine if we remove the INTO clause but, I want to store the result to use it further.
So what should be the datatype of the variable resultValue1 and resultValue2 how to store the data(because data will be multiple).
As I am new to PostgreSQL I don't know how to do this can anybody help me out.
Here resultValue1 & resultValue2 will be tables not variables. you can group by using the column names.
Give some column alias names for both columns and group by using them.
You probably want this.
SELECT cast ( SUBSTRING ( cast ("ProcessingDate" as text),5 , 2 ) as
integer) AS resultValue1, COUNT(*) AS resultValue2
INTO <NewTable> --NewTable will be created with those two columns
FROM "DemoLogs"."Project"
-- conditions
Group By 1
-- other contitions/clauses
;
Kindly refer this INTO Documentation.
Hope this helps.
Try this:
SELECT cast ( SUBSTRING ( cast ("ProcessingDate" as text),5 , 2 ) as integer)resultValue1,
COUNT(*)resultValue2
INTO <Table Name>
FROM "DemoLogs"."Project"
WHERE "Addr" = 'Y'
AND "ProcessingDate" >= '20160110'
Group By 1
Order By 1;
Store the above query in the variable and remove that INTO clause from it.
So, query will be now :
query = SELECT cast ( SUBSTRING ( CAST ("ProcessingDate" AS text), 5, 2 ) as
integer),
COUNT(*)
FROM "DemoLogs"."Project"
WHERE "Addr" = 'Y' AND "ProcessingDate" >= 20160110
GROUP BY 1
ORDER BY 1;
Now declare result_data of type record and use in the following manner:
We are looping over result_data because it gives number of rows as output
and I have declared resultset of type text to store the result (as I needed further)
FOR result_data IN EXECUTE query
LOOP
RAISE NOTICE 'result : %',to_json(result_data);
resultset = resultset || to_json(result_data);
END LOOP;

how to convert the output of sub query into numeric

select rptName
from RptTable
where rpt_id in (
select LEFT(Reports, NULLIF(LEN(Reports)-1,-1))
from repoAccess1
where uid = 'VIKRAM'
)
this is my sql query In which i have use the sub query to access selected field
in this sub query returns
select LEFT(Reports, NULLIF(LEN(Reports)-1,-1))
from repoAccess1
where uid = 'VIKRAM'
Returns
1,2
that means the query should be like
select rptName
from RptTable where rpt_id in (1,2)
But i m getting this error
Msg 8114, Level 16, State 5, Line 1
Error converting data type nvarchar to numeric.
could anyone tell me ow to modify to get exact ans
It's a little hard to tell without the concrete table definitions, but I'm pretty sure you're trying to compare different data types to each other. If this is the case you can make use of the CAST or the CONVERT function, for example:
SELECT
[rptName]
FROM [RptTable]
WHERE [rpt_id] IN
(
SELECT
CONVERT(int, LEFT([Reports], NULLIF(LEN([Reports]) - 1, -1)))
FROM [repoAccess1]
WHERE [uid] = 'VIKRAM'
)
UPDATE: Since you have updated your question: The LEFT function returns results of either varchar or nvarchar data type. So the resulting query would be
SELECT
[rptName]
FROM [RptTable]
WHERE [rpt_id] IN('1', '2')
Please note the apostrophes (is this the correct term?) around the values. Since [rpt_id] seems to be of data type int the values cannot implicitly be converted. And that's where the aforementioned CAST or CONVERT come into play.
If I understand correctly, the subquery is returning a single row with a value of '1,2'. This is not a number, hence the error.
Before continuing, let me emphasize that storing values in comma delimited string is not the SQL-way of doing things. You should have one row per id, with proper types and foreign keys defined.
That said, sometimes we are stuck with other people's really bad design decisions. If this is the case, you can use LIKE:
select rptName
from RptTable r
where exists (select 1
from repoAccess1 a
where a.uid = 'VIKRAM' and
',' + a.reports + ',' like '%,' + cast(r.rpt_id as varchar(255)) + ',%'
);
select rptName
from RptTable
where rpt_id in (
select CAST(LEFT(Reports, NULLIF(LEN(Reports)-1,-1)) AS INT) as Val
from repoAccess1
where uid = 'VIKRAM'
)
Your query would work fine when (LEFT(Reports, NULLIF(LEN(Reports)-1,-1)) ) returns either 1 or 2 since SQL Server implicitly converts the varchar value to numeric.
It seems there might be a data issue. One of the data returned by LEFT function is non-numeric. In order to find that particular record you can use isnumeric function. Try like this,
SELECT rptName
FROM RptTable
WHERE rpt_id IN (
SELECT LEFT(Reports, NULLIF(LEN(Reports) - 1, - 1))
FROM repoAccess1
WHERE uid = 'VIKRAM'
AND ISNUMERIC(LEFT(Reports, NULLIF(LEN(Reports) - 1, - 1))) = 1
)

Oracle "Select Level from dual" does not work as expected with to_number result

Why does
select *
from (
SELECT LEVEL as VAL
FROM DUAL
CONNECT BY LEVEL <= 1000
ORDER BY LEVEL
) n
left outer join (select to_number(trim(alphanumeric_column)) as nr from my_table
where NOT regexp_like (trim(alphanumeric_column),'[^[:digit:]]')) d
on n.VAL = d.nr
where d.nr is null
and n.VAL >= 100
throw a ORA-01722 invalid number (reason is the last row, n.VAL), whereas the similar version with numeric columns im my_table works fine:
select *
from (
SELECT LEVEL as VAL
FROM DUAL
CONNECT BY LEVEL <= 1000
ORDER BY LEVEL
) n
left outer join (select numeric_column as nr from my_table) d
on n.VAL = d.nr
where d.nr is null
and n.VAL >= 100
given that numeric_column is of type number and alphanumeric_column of type nvarchar_2. Note that the upper example works fine without the numerical comparison (n.VAL >= 100).
Does anybody know?
This problem was driving me crazy. I narrowed the problem to a simpler query
SELECT *
FROM (SELECT TO_NUMBER(TRIM (alphanumeric_column)) AS nr
FROM my_table
WHERE NOT REGEXP_LIKE (TRIM (alphanumeric_column), '[^[:digit:]]')) d
WHERE d.nr > 1
With alphanumeric_colum values of ('100','200','XXXX'); Running the above statement gave the "invalid number" error. I then made a slight change to the query to use the CAST function instead of TO_NUMBER:
SELECT *
FROM (SELECT CAST (TRIM (alphanumeric_column) AS NUMBER) AS nr
FROM my_table
WHERE NOT REGEXP_LIKE (TRIM (alphanumeric_column), '[^[:digit:]]')) d
WHERE d.nr > 1
And this correctly returned - 100, 200. I would think that those functions would be similar in behavior. It almost appears as though oracle is trying to evaluate the d.nr > 1 constraint before the view is constructed, which makes no sense. If anyone can shed light on why this is happening, I would be grateful. See SQLFiddle example
UPDATE: I did some more digging, because I don't like not knowing why something just works. I ran EXPLAIN PLAN on both queries and got some interesting results.
For the query that failed, the predicate information looks like this:
1 - filter(TO_NUMBER(TRIM("ALPHANUMERIC_COLUMN"))>1 AND NOT
REGEXP_LIKE (TRIM("ALPHANUMERIC_COLUMN"),'[^[:digit:]]'))
You will notice that the TO_NUMBER function is called first in the AND condition, then
the regexp to exclude alpha values. I am thinking oracle maybe does a short-circuit evaluation with the AND condition, and since it is executing TO_NUMBER first, it fails.
However, when we use the CAST function, the evaluation order is swapped, and the
regexp exclusion is evaluated first. Since for the alpha values, it is false, then the
second part of the AND clause is not evaluated, and the query works.
1 - filter( NOT REGEXP_LIKE (TRIM("ALPHANUMERIC_COLUMN"),'[^[:digit:]
]') AND CAST(TRIM("ALPHANUMERIC_COLUMN") AS NUMBER)>1)
Oracle can be strange sometimes.
I believe when it comes to the Predicate (where) clause, Oracle can/will reorder the entire plan as it sees fit. So with regard to the predicate, it will short-circuit (as OldProgrammer noted) the evaluation however it wants, and you wont be able to guarantee the exact order it occurs.
In your current SQL, you are depending on the predicate to remove non numbers. One option would be to not use "WHERE NOT regexp_like ..." and instead use regexp_substr with coalesce. For example:
create table t_tab2
(
col varchar2(10)
);
create index t_tab2_idx on t_tab2(col);
insert into t_tab2
select level from dual
connect by level <= 100;
insert into t_tab2 values ('123ABC456');
commit;
-- select values > 95 (96->100 exclude non numbers)
select d.* from
(
select COALESCE(TO_NUMBER(REGEXP_SUBSTR(trim(col), '^\d+$')), 0) as nr
from t_tab2
) d
where d.nr > 95;
This should run without throwing invalid number error. Note that the coalesce will return the number 0 for any non numbers coming from the data, you may want to change that based on your needs and data.

Finding rows that don't contain numeric data in Oracle

I am trying to locate some problematic records in a very large Oracle table. The column should contain all numeric data even though it is a varchar2 column. I need to find the records which don't contain numeric data (The to_number(col_name) function throws an error when I try to call it on this column).
I was thinking you could use a regexp_like condition and use the regular expression to find any non-numerics. I hope this might help?!
SELECT * FROM table_with_column_to_search WHERE REGEXP_LIKE(varchar_col_with_non_numerics, '[^0-9]+');
To get an indicator:
DECODE( TRANSLATE(your_number,' 0123456789',' ')
e.g.
SQL> select DECODE( TRANSLATE('12345zzz_not_numberee',' 0123456789',' '), NULL, 'number','contains char')
2 from dual
3 /
"contains char"
and
SQL> select DECODE( TRANSLATE('12345',' 0123456789',' '), NULL, 'number','contains char')
2 from dual
3 /
"number"
and
SQL> select DECODE( TRANSLATE('123405',' 0123456789',' '), NULL, 'number','contains char')
2 from dual
3 /
"number"
Oracle 11g has regular expressions so you could use this to get the actual number:
SQL> SELECT colA
2 FROM t1
3 WHERE REGEXP_LIKE(colA, '[[:digit:]]');
COL1
----------
47845
48543
12
...
If there is a non-numeric value like '23g' it will just be ignored.
In contrast to SGB's answer, I prefer doing the regexp defining the actual format of my data and negating that. This allows me to define values like $DDD,DDD,DDD.DD
In the OPs simple scenario, it would look like
SELECT *
FROM table_with_column_to_search
WHERE NOT REGEXP_LIKE(varchar_col_with_non_numerics, '^[0-9]+$');
which finds all non-positive integers. If you wau accept negatiuve integers also, it's an easy change, just add an optional leading minus.
SELECT *
FROM table_with_column_to_search
WHERE NOT REGEXP_LIKE(varchar_col_with_non_numerics, '^-?[0-9]+$');
accepting floating points...
SELECT *
FROM table_with_column_to_search
WHERE NOT REGEXP_LIKE(varchar_col_with_non_numerics, '^-?[0-9]+(\.[0-9]+)?$');
Same goes further with any format. Basically, you will generally already have the formats to validate input data, so when you will desire to find data that does not match that format ... it's simpler to negate that format than come up with another one; which in case of SGB's approach would be a bit tricky to do if you want more than just positive integers.
Use this
SELECT *
FROM TableToSearch
WHERE NOT REGEXP_LIKE(ColumnToSearch, '^-?[0-9]+(\.[0-9]+)?$');
After doing some testing, i came up with this solution, let me know in case it helps.
Add this below 2 conditions in your query and it will find the records which don't contain numeric data
and REGEXP_LIKE(<column_name>, '\D') -- this selects non numeric data
and not REGEXP_LIKE(column_name,'^[-]{1}\d{1}') -- this filters out negative(-) values
Starting with Oracle 12.2 the function to_number has an option ON CONVERSION ERROR clause, that can catch the exception and provide default value.
This can be used for the test of number values. Simple set NULL when the conversion fails and filer all not NULL values.
Example
with num as (
select '123' vc_col from dual union all
select '1,23' from dual union all
select 'RV12P2000' from dual union all
select null from dual)
select
vc_col
from num
where /* filter numbers */
vc_col is not null and
to_number(vc_col DEFAULT NULL ON CONVERSION ERROR) is not null
;
VC_COL
---------
123
1,23
From http://www.dba-oracle.com/t_isnumeric.htm
LENGTH(TRIM(TRANSLATE(, ' +-.0123456789', ' '))) is null
If there is anything left in the string after the TRIM it must be non-numeric characters.
I've found this useful:
select translate('your string','_0123456789','_') from dual
If the result is NULL, it's numeric (ignoring floating point numbers.)
However, I'm a bit baffled why the underscore is needed. Without it the following also returns null:
select translate('s123','0123456789', '') from dual
There is also one of my favorite tricks - not perfect if the string contains stuff like "*" or "#":
SELECT 'is a number' FROM dual WHERE UPPER('123') = LOWER('123')
After doing some testing, building upon the suggestions in the previous answers, there seem to be two usable solutions.
Method 1 is fastest, but less powerful in terms of matching more complex patterns.
Method 2 is more flexible, but slower.
Method 1 - fastest
I've tested this method on a table with 1 million rows.
It seems to be 3.8 times faster than the regex solutions.
The 0-replacement solves the issue that 0 is mapped to a space, and does not seem to slow down the query.
SELECT *
FROM <table>
WHERE TRANSLATE(replace(<char_column>,'0',''),'0123456789',' ') IS NOT NULL;
Method 2 - slower, but more flexible
I've compared the speed of putting the negation inside or outside the regex statement. Both are equally slower than the translate-solution. As a result, #ciuly's approach seems most sensible when using regex.
SELECT *
FROM <table>
WHERE NOT REGEXP_LIKE(<char_column>, '^[0-9]+$');
You can use this one check:
create or replace function to_n(c varchar2) return number is
begin return to_number(c);
exception when others then return -123456;
end;
select id, n from t where to_n(n) = -123456;
I tray order by with problematic column and i find rows with column.
SELECT
D.UNIT_CODE,
D.CUATM,
D.CAPITOL,
D.RIND,
D.COL1 AS COL1
FROM
VW_DATA_ALL_GC D
WHERE
(D.PERIOADA IN (:pPERIOADA)) AND
(D.FORM = 62)
AND D.COL1 IS NOT NULL
-- AND REGEXP_LIKE (D.COL1, '\[\[:alpha:\]\]')
-- AND REGEXP_LIKE(D.COL1, '\[\[:digit:\]\]')
--AND REGEXP_LIKE(TO_CHAR(D.COL1), '\[^0-9\]+')
GROUP BY
D.UNIT_CODE,
D.CUATM,
D.CAPITOL,
D.RIND ,
D.COL1
ORDER BY
D.COL1