How to sort texts with '_' in Oracle exactly like EXCEL? - oracle

In Excel When I sort the texts in ascending order, results shows as below. Text with the underscore character precedes the others. And in Excel cell, when I type in ="_" < "A", then "True" shows as expected.
C10_
C10A
C20_
C20A
But, In Oracle, when I sort in ascending order, results shows as below.
(I guess, Oracle treats '_' < 'A' False)
C10A
C10_
C20A
C20_
How can I make Oracle sort the list exactly as Excel does? I have changed ASC to DESC, but the result was not what I expect.
My sorting code is as below,
WITH DATAA AS (
SELECT *
FROM
(
SELECT 'C10_'rr FROM DUAL
UNION
SELECT 'C10A' rr FROM DUAL
UNION
SELECT 'C20_' rr FROM DUAL
UNION
SELECT 'C20A' rr FROM DUAL
)
)
SELECT *
FROM DATAA
ORDER BY rr ASC;

You can achieve this by altering the sorting method using NLS_SORT as following:
According to ORACLE Documentation:
NLS_SORT specifies the type of sort for character data. It overrides
the default value that is derived from NLS_LANGUAGE.
NLS_SORT contains either of the following values:
NLS_SORT = BINARY | sort_name
BINARY specifies a binary sort. sort_name specifies a linguistic sort
sequence.
Here is how you can achieve the result.
SQL> -- Your original query
SQL> --
SQL> WITH DATAA AS (
2 SELECT *
3 FROM
4 (
5 SELECT 'C10_'rr FROM DUAL UNION
6 SELECT 'C10A' rr FROM DUAL UNION
7 SELECT 'C20_' rr FROM DUAL UNION
8 SELECT 'C20A' rr FROM DUAL )
9 )
10 SELECT *
11 FROM DATAA
12 ORDER BY rr ASC;
RR
----
C10A
C10_
C20A
C20_
--
SQL> -- Now altering the sorting method
SQL> --
SQL> --
SQL> alter session set NLS_SORT = German;
Session altered.
SQL> --
SQL> --
SQL> -- Now see the result
SQL> --
SQL> --
SQL> WITH DATAA AS (
2 SELECT *
3 FROM
4 (
5 SELECT 'C10_'rr FROM DUAL UNION
6 SELECT 'C10A' rr FROM DUAL UNION
7 SELECT 'C20_' rr FROM DUAL UNION
8 SELECT 'C20A' rr FROM DUAL )
9 )
10 SELECT *
11 FROM DATAA
12 ORDER BY rr ASC;
RR
----
C10_
C10A
C20_
C20A
SQL>
Cheers!!

Related

PL/SQL Regex search based on both characters and digits

I have a table TA which has a column inv_ref with has data as below.
inv_ref
----------
MX/3280/20
CT/3281/20
CT/3109/20
MX/3272/20
RF/3275/20
My requirement is to fetch whereas middle 4 digit number of inv_ref between 3270 to 3299 also begin with either MX and CT.
select * from TA where regexp_like(inv_ref, 'CT/32[7-9][0-9]/20')
Above query only returns CT, how can return both CT and MX related values omitting RF?
You can use:
SELECT *
FROM TA
WHERE REGEXP_LIKE(inv_ref, '^(CT|MX)/32[7-9][0-9]/20$')
Then if you have the test data:
CREATE TABLE TA ( inv_ref, is_valid ) AS
SELECT 'MX/3280/20', 'Valid' FROM DUAL UNION ALL
SELECT 'CT/3281/20', 'Valid' FROM DUAL UNION ALL
SELECT 'CT/3109/20', 'Invalid, number too low' FROM DUAL UNION ALL
SELECT 'MX/3272/20', 'Valid' FROM DUAL UNION ALL
SELECT 'RF/3275/20', 'Invalid, wrong start' FROM DUAL UNION ALL
SELECT 'CX/3299/20', 'Invalid, wrong start' FROM DUAL UNION ALL
SELECT 'MT/3270/20', 'Invalid, wrong start' FROM DUAL UNION ALL
SELECT 'ACT/3270/20', 'Invalid, wrong start' FROM DUAL;
This outputs:
INV_REF | IS_VALID
:--------- | :-------
MX/3280/20 | Valid
CT/3281/20 | Valid
MX/3272/20 | Valid
db<>fiddle here
It would be much easier to add virtual generated columns:
alter table your_table add (
P1 generated always as (regexp_substr(inv_ref,'^[^/]+')),
P2 generated always as (to_number(regexp_substr(inv_ref,'/(\d+)/',1,1,null,1))),
P3 generated always as (regexp_substr(inv_ref,'[^/]+$'))
);
In this case you can use standard predicates on those columns. Moreover, you can even create indexes on those columns.
Full test case:
CREATE TABLE YOUR_TABLE ( inv_ref ) AS
SELECT 'MX/3280/20' FROM DUAL UNION ALL
SELECT 'CT/3281/20' FROM DUAL UNION ALL
SELECT 'CT/3109/20' FROM DUAL UNION ALL
SELECT 'MX/3272/20' FROM DUAL UNION ALL
SELECT 'RF/3275/20' FROM DUAL UNION ALL
SELECT 'CX/3299/20' FROM DUAL UNION ALL
SELECT 'MT/3270/20' FROM DUAL UNION ALL
SELECT 'ACT/3270/20' FROM DUAL;
alter table your_table add (
P1 generated always as (regexp_substr(inv_ref,'^[^/]+')),
P2 generated always as (to_number(regexp_substr(inv_ref,'/(\d+)/',1,1,null,1))),
P3 generated always as (regexp_substr(inv_ref,'[^/]+$'))
);
select * from your_table
where p1 IN ('CT', 'MX')
and p2 BETWEEN 3270 and 3299;
Result:
MX/3280/20 MX 3280 20
CT/3281/20 CT 3281 20
MX/3272/20 MX 3272 20
You could use
SQL> create table my_test ( inv_ref varchar2(100) ) ;
SQL> insert into my_test values ( 'MX/3280/20') ;
SQL> insert into my_test values ( 'CD/3281/20') ;
SQL> insert into my_test values ( 'CD/3109/20') ;
SQL> insert into my_test values ( 'MX/3272/20') ;
SQL> insert into my_test values ( 'RF/3275/20') ;
Table created.
SQL> SQL> SQL>
1 row created.
SQL> SQL>
1 row created.
SQL> SQL>
1 row created.
SQL> SQL>
1 row created.
SQL> SQL>
1 row created.
SQL> commit ;
Commit complete.
Two options ( in my case I use CD instead of CT ). This option will work as long as the strings are limited to the example. If you would have other combination it won't , as CD|MX means C or D or M or X ). See comments to the answer. #MTO, Thanks for your comments.
SQL> select * from my_test where regexp_like(inv_ref, '[CD|MX]/32[7-9][0-9]/20')
INV_REF
--------------------------------------------------------------------------------
MX/3280/20
CD/3281/20
MX/3272/20
SQL> select * from my_test where regexp_like(inv_ref, 'CD/32[7-9][0-9]/20') or
regexp_like(inv_ref, 'MX/32[7-9][0-9]/20')
INV_REF
--------------------------------------------------------------------------------
MX/3280/20
CD/3281/20
MX/3272/20
You can use the values separated by | (or) as follows:
select * from TA where regexp_like(inv_ref, '^(CT|MX)/32[7-9][0-9]/20');
db<>fiddle

How to select second split of column data from oracle database

I want to select the data from a Oracle table, whereas the table columns contains the data as , [ex : key,value] separated values; so here I want to select the second split i.e, value
table column data as below :
column_data
++++++++++++++
asper,worse
tincher,good
golder
null -- null values need to eliminate while selection
www,ewe
from the above data, desired output like below:
column_data
+++++++++++++
worse
good
golder
ewe
Please help me with the query
According to data you provided, here are two options:
result1: regular expressions one (get the 2nd word if it exists; otherwise, get the 1st one)
result2: SUBSTR + INSTR combination
SQL> with test (col) as
2 (select 'asper,worse' from dual union all
3 select 'tincher,good' from dual union all
4 select 'golder' from dual union all
5 select null from dual union all
6 select 'www,ewe' from dual
7 )
8 select col,
9 nvl(regexp_substr(col, '\w+', 1, 2), regexp_substr(col, '\w+', 1,1 )) result1,
10 --
11 nvl(substr(col, instr(col, ',') + 1), col) result2
12 from test
13 where col is not null;
COL RESULT1 RESULT2
------------ -------------------- --------------------
asper,worse worse worse
tincher,good good good
golder golder golder
www,ewe ewe ewe
SQL>

Select default in case of no value returned

I am trying to get some default value in my resultset if query does not return anything. I am trying nvl for the same but it is not returning the expected default value. To simulate, Consider following query,
select nvl(null, '10') from dual where 1=0;
I want to get 10 in case of given condition is not true and query does not return any value. However above query not returning any row.
Your query returns zero rows. NVL() isn't going to change that (*).
The correct solution is for the program which executes the query to handle NO_DATA_FOUND exception rather than fiddling the query.
However, you need a workaround so here is one using two sub-queries, one for your actual query, one to for the default.
When your_query returns an empty set you get this:
SQL> with your_qry as
2 ( select col1 from t42 where 1=0 )
3 , dflt as
4 ( select 10 as col1 from dual )
5 select col1
6 from your_qry
7 union all
8 select col1
9 from dflt
10 where not exists (select * from your_qry );
COL1
----------
10
SQL>
And when it returns a row you get this:
SQL> with your_qry as
2 ( select col1 from t42 )
3 , dflt as
4 ( select 10 as col1 from dual )
5 select col1
6 from your_qry
7 union all
8 select col1
9 from dflt
10 where not exists (select * from your_qry );
COL1
----------
12
13
SQL>
The WITH clause is optional here, it just makes it easier to write the query without duplication. This would have the same outcome:
select col1
from t42
where col0 is null
union all
select 10
from dual
where not exists (select col1
from t42
where col0 is null)
;
(*) Okay, there are solutions which use NVL() or COALESCE() with aggregations to do this. They work with single column projections in a single row as this question poses, but break down when the real query has more than one row and/or more than one column. Aggregations change the results.
So this looks alright ...
SQL> with cte as (
2 select 'Z' as col0, 12 as col1 from dual where 1=0 union all
3 select 'X' as col0, 13 as col1 from dual where 1=0 )
4 select
5 nvl(max(col0), 'Y') as col0, nvl(max( col1), 10) as col1
6 from cte;
COL0 COL1
---------- ----------
Y 10
SQL>
... but this not so much:
SQL> with cte as (
2 select 'Z' as col0, 12 as col1 from dual union all
3 select 'X' as col0, 13 as col1 from dual )
4 select
5 nvl(max(col0), 'Y') as col0, nvl(max( col1), 10) as col1
6 from cte;
COL0 COL1
---------- ----------
Z 13
SQL>
May be something like this is what you need
You could change WHERE clause (in this case WHERE COL > 1) similarly in both places.
WITH T(COL) AS(
SELECT 1 FROM DUAL UNION ALL
SELECT 2 FROM DUAL UNION ALL
SELECT 3 FROM DUAL
)
SELECT COL FROM T WHERE COL > 1
UNION ALL
SELECT 10 AS COL FROM DUAL WHERE NOT EXISTS( SELECT 1 FROM T WHERE COL > 1)
You can use aggregation. An aggregation query always returns one row:
select coalesce(max(null), '10')
from dual
where 1 = 0;
I prefer coalesce() to nvl() because coalesce() is the ANSI standard function. But, nvl() would work here just as well.

remove a varchar2 string from the middle of table data values

Data in the file_name field of the generation table should be an assigned number, then _01, _02, or _03, etc. and then .pdf (example 82617_01.pdf).
Somewhere, the program is putting a state name and sometimes a date/time stamp, between the assigned number and the 01, 02, etc. (82617_ALABAMA_01.pdf or 19998_MAINE_07-31-2010_11-05-59_AM.pdf or 5485325_OREGON_01.pdf for example).
We would like to develop a SQL statement to find the bad file names and fix them. In theory it seems rather simple to find file names that include a varchar2 data type and remove it, but putting the statement together is beyond me.
Any help or suggestions appreciated.
Something like:
UPDATE GENERATION
SET FILE_NAME (?)
WHERE FILE_NAME (?...LIKE '%STRING%');?
You can find the problem rows like this:
select *
from Files
where length(FILE_NAME) - length(replace(FILE_NAME, '_', '')) > 1
You can fix them like this:
update Files
set FILE_NAME = SUBSTR(FILE_NAME, 1, instr(FILE_NAME, '_') -1) ||
SUBSTR(FILE_NAME, instr(FILE_NAME, '_', 1, 2))
where length(FILE_NAME) - length(replace(FILE_NAME, '_', '')) > 1
SQL Fiddle Example
You can also use Regexp_replace function:
SQL> with t1(col) as(
2 select '82617_mm_01.pdf' from dual union all
3 select '456546_khkjh_89kjh_67_01.pdf' from dual union all
4 select '19998_MAINE_07-31-2010_11-05-59_AM.pdf' from dual union all
5 select '5485325_OREGON_01.pdf' from dual
6 )
7 select col
8 , regexp_replace(col, '^([0-9]+)_(.*)_(\d{2}\.pdf)$', '\1_\3') res
9 from t1;
COL RES
-------------------------------------- -----------------------------------------
82617_mm_01.pdf 82617_01.pdf
456546_khkjh_89kjh_67_01.pdf 456546_01.pdf
19998_MAINE_07-31-2010_11-05-59_AM.pdf 19998_MAINE_07-31-2010_11-05-59_AM.pdf
5485325_OREGON_01.pdf 5485325_01.pdf
To display good or bad data regexp_like function will come in handy:
SQL> with t1(col) as(
2 select '826170_01.pdf' from dual union all
3 select '456546_01.pdf' from dual union all
4 select '19998_MAINE_07-31-2010_11-05-59_AM.pdf' from dual union all
5 select '5485325_OREGON_01.pdf' from dual
6 )
7 select col bad_data
8 from t1
9 where not regexp_like(col, '^[0-9]+_\d{2}\.pdf$');
BAD_DATA
--------------------------------------
19998_MAINE_07-31-2010_11-05-59_AM.pdf
5485325_OREGON_01.pdf
SQL> with t1(col) as(
2 select '826170_01.pdf' from dual union all
3 select '456546_01.pdf' from dual union all
4 select '19998_MAINE_07-31-2010_11-05-59_AM.pdf' from dual union all
5 select '5485325_OREGON_01.pdf' from dual
6 )
7 select col good_data
8 from t1
9 where regexp_like(col, '^[0-9]+_\d{2}\.pdf$');
GOOD_DATA
--------------------------------------
826170_01.pdf
456546_01.pdf
To that end your update statement might look like this:
update your_table
set col = regexp_replace(col, '^([0-9]+)_(.*)_(\d{2}\.pdf)$', '\1_\3');
--where clause if needed

Why does Oracle's varchar sort order not match the behavior of varchar comparison?

An SQL statement like:
select * from (
select '000000000000' as x from dual
union
select '978123456789' as x from dual
union
select 'B002AACD0A' as x from dual
) /*where x>'000000000000'*/ order by x;
Yields:
B002AACD0A
000000000000
978123456789
After uncommenting the WHERE-restriction, the result is:
B002AACD0A
978123456789
I would have expected the result to be just 978123456789 since B002AACD0A is returned before 000000000000 when running the query without restriction.
How can this behavior be explained? And how am I supposed to sort and compare varchars so that they can work together like I can do with integers?
Funny enough, when changing the restriction to x>'B002AACD0A', the result is empty. Changing it tox>978123456789 returns B002AACD0A.
I.e. when comparing:
B002AACD0A > 978123456789 > 000000000000
But when sorting:
978123456789 > 000000000000 > B002AACD0A
When using binary sort explicitely (order by NLSSORT(x,'NLS_SORT=BINARY_AI')), the result is B002AACD0A>978123456789>000000000000 and matches the behavior of comparison. But I still do not know why this is happening.
Peter,
the behaviour of the sorting is regulated by the NLS_SORT session parameter, whereas the behaviour for comparisons is dependent upon the NLS_COMP parameter. You must have a mismatch.
I obtain the same result as you do with the following parameters:
SQL> SELECT *
2 FROM nls_session_parameters
3 WHERE parameter IN ('NLS_COMP', 'NLS_SORT');
PARAMETER VALUE
------------------------------ ----------------------------------------
NLS_SORT FRENCH
NLS_COMP BINARY
However when the two are matched the result is consistent:
SQL> alter session set nls_comp=LINGUISTIC;
Session altered
SQL> select * from (
2 select '000000000000' as x from dual
3 union
4 select '978123456789' as x from dual
5 union
6 select 'B002AACD0A' as x from dual
7 ) /*where x>'000000000000'*/ order by x;
X
------------
B002AACD0A
000000000000
978123456789
SQL> select * from (
2 select '000000000000' as x from dual
3 union
4 select '978123456789' as x from dual
5 union
6 select 'B002AACD0A' as x from dual
7 ) where x > '000000000000' order by x;
X
------------
978123456789

Resources