how to match an integer with varchar containing digits separated by commas in oracle - oracle

I am facing a strange scenario where I need to match an integer with varchar containing digits separated by commas in oracle
Example:
Table t1:
id integer
key integer
Table t2
id integer,
keys varchar2
T1 values are:
1,111
2,201
3,301
T2 values are:
1, "111,301"
2, "111,201"
3, "201,301"
PROBLEM: Is there any way I can match or regular_expression match with key of T1 with keys of T2?

you can do a regular join without regex for this:
select *
from t1
inner join t2
on ','||t2.keys||',' like '%,'||to_char(t1.key)||',%';
eg:
SQL> create table t1(id, key)
2 as
3 select 1, 111 from dual union all
4 select 2, 201 from dual union all
5 select 3, 301 from dual;
Table created.
SQL> create table t2(id, keys)
2 as
3 select 1, '111,301' from dual union all
4 select 2, '111,201' from dual union all
5 select 3, '201,301' from dual;
Table created.
SQL> select *
2 from t1
3 inner join t2
4 on ','||t2.keys||',' like '%,'||to_char(t1.key)||',%';
ID KEY ID KEYS
---------- ---------- ---------- -------
1 111 1 111,301
1 111 2 111,201
2 201 2 111,201
2 201 3 201,301
3 301 1 111,301
3 301 3 201,301
6 rows selected.
It's not regex, just concatenation. For example lets say we wanted to compare
KEY KEYS
111 111,301
we could say
where keys like '%'||key||'%'
i.e. expanded, this is
where '111,301' like '%111%'
which matches fine. But I added some commas there too. ie I did this:
where ',111,301,' like '%,111,%'
Why? imagine instead you had this data:
KEY KEYS
111 1111,301
If we did the simple join:
where '1111,301' like '%111%'
it would incorrectly match. By injecting leading and trailing commas on both sides:
where ',1111,301,' like '%,111,%'
is no longer erroneously matches, as ,1111, isn't like ,111,.

Related

How to compare two comma separated string and insert in DB

I have following table (Users) with 2 columns and trying to update second column with first column ( first column is autoincrement and primary key field).
UserId CatId
1 10
2 78
3 99
4 89
5 80
I am finding difficult to update second column with P_uid order by (1,3,4). I tried normal Update statement but it doesn't work. I am passing following ids into SP.
P_uid varchar2(20) := '1,3,4';
P_new_cat_id varchar2(20) := '100,12,13';
Expected output
---------------
UserId CatId
1 100
2 78
3 12
4 13
5 80
Use a MERGE statement:
MERGE INTO users dst
USING (
SELECT 1 AS id, 100 AS new_catid FROM DUAL UNION ALL
SELECT 3, 12 FROM DUAL UNION ALL
SELECT 4, 13 FROM DUAL
) src
ON (src.id = dst.userid)
WHEN MATCHED THEN
UPDATE
SET catid = src.new_catid;
Which, for the sample data:
CREATE TABLE users (UserId, CatId) AS
SELECT 1, 10 FROM DUAL UNION ALL
SELECT 2, 78 FROM DUAL UNION ALL
SELECT 3, 99 FROM DUAL UNION ALL
SELECT 4, 89 FROM DUAL UNION ALL
SELECT 5, 80 FROM DUAL;
Then, after the merge, the table contains:
USERID
CATID
1
100
2
78
3
12
4
13
5
80
If you have delimited strings then (apart from finding a better solution for passing multi-row data) you can split the values into rows using a row-generator function and use the previous technique:
DECLARE
P_uid varchar2(20) := '1,3,4';
P_new_cat_id varchar2(20) := '100,12,13';
BEGIN
MERGE INTO users dst
USING (
SELECT REGEXP_SUBSTR(p_uid, '\d+', 1, LEVEL) AS id,
REGEXP_SUBSTR(p_new_cat_id, '\d+', 1, LEVEL) AS new_catid
FROM DUAL
CONNECT BY LEVEL <= LEAST(
REGEXP_COUNT(p_uid, '\d+'),
REGEXP_COUNT(p_new_cat_id, '\d+')
)
) src
ON (src.id = dst.userid)
WHEN MATCHED THEN
UPDATE
SET catid = src.new_catid;
END;
/
Which outputs the same.
db<>fiddle here

Order and filter follow different criteria in Oracle?

I'm having a problem while trying to apply a filter in an Oracle (12.2.0.1.0) query.
When I query for the values of a column in a table using order by that same column, I get
75A0000000
7597953181
7597953182
But then, when I try to filter the rows with the column between the first and the last value I'm not getting results at all.
This works perfectly fine if the column has only numeric values, but it gets it all wrong when there is some alphabetic value in the middle.
It seems like Oracle is following the criteria A<0 when ordering a result but A>0 when filtering that result.
This can be reproduced using this query:
select * from (
WITH DATA AS
(SELECT '1, A, 2' str FROM dual)
SELECT trim(regexp_substr(str, '[^,]+', 1, LEVEL)) str
FROM DATA
CONNECT BY instr(str, ',', 1, LEVEL - 1) > 0)
where str>='1' and str<='A'
order by str
The result of this query is
A
1
2
that should mean A is less than 1, but the filter applied to make it return some values is just the opposite : str values greater or equal than 1 and less or equal than A.
Anybody knows why? Thanks in advance.
To me, it looks as if it is about binary or linguistic sort.
Sample data:
SQL> select * from test;
C
-
1
A
2
Sort by col, as is:
SQL> select col, ascii(col)
2 from test
3 order by col;
C ASCII(COL)
- ----------
A 65
1 49
2 50
Or, sort by col's ASCII code:
SQL> select col, ascii(col)
2 from test
3 order by ascii(col);
C ASCII(COL)
- ----------
1 49
2 50
A 65
Explicitly stating binary or linguistic sort:
SQL> select col
2 from test
3 order by nlssort(col, 'nls_sort=binary'); --> binary
C
-
1
2
A
SQL> select col
2 from test
3 order by nlssort(col, 'nls_sort=croatian'); --> linguistic
C
-
A
1
2
SQL>
Or, alter session and set nls_sort you want:
SQL> alter session set nls_sort='binary';
Session altered.
SQL> select * From test order by col;
C
-
1
2
A
SQL>
If you get unexpected result, check what's nls_sort set in your session:
SQL> select * from nls_session_parameters where parameter = 'NLS_SORT';
PARAMETER VALUE
------------------------------ ----------------------------------------
NLS_SORT BINARY
SQL>
Using your query:
SQL> select * from (
2 WITH DATA AS
3 (SELECT '1, A, 2' str FROM dual)
4 SELECT trim(regexp_substr(str, '[^,]+', 1, LEVEL)) str
5 FROM DATA
6 CONNECT BY instr(str, ',', 1, LEVEL - 1) > 0)
7 where str>='1' and str<='A'
8 order by nlssort(str, 'nls_sort=binary'); --> this
STR
-------
1
2
A
SQL>

Specific Pattern Matching in Oracle

I have a close to 1000 records which have first two characters as alphabets and then an number of characters.
eg.
- BE123
- QT12124
- ST1000
- XY12345
and similar data....
I have a table X in Oracle having a column 'Serial Number' which will be having similar data , but it is of standard length 7 and also start with first two characters as alphabets.
I want to do a pattern matching on the column Serial Number where i can use LIKE and '%' matching on the characters after first two characters in the column for eg
if the column has a data
- BE00123 , it should start give me BE123 as matched data
- QT12124 , it is matched data
- ST11001 , unmatched data
- XY12345, matched data
Well, this returns what you asked. See if it helps.
SQL> with t_one (col) as
2 (select 'BE123' from dual union all
3 select 'QT12124' from dual union all
4 select 'ST1000' from dual union all
5 select 'XY12345' from dual
6 ),
7 t_two (col) as
8 (select 'BE00123' from dual union all
9 select 'QT12124' from dual union all
10 select 'ST11001' from dual union all
11 select 'XY12345' from dual
12 )
13 select o.col
14 from t_one o join t_two t on substr(o.col, 1, 2) = substr(t.col, 1, 2)
15 and instr(t.col, substr(o.col, 3)) > 0;
COL
-------
BE123
QT12124
XY12345
SQL>
Line 14: match first two characters
Line 15: check whether characters from T_ONE table, starting from position 3, are contained in the T_TWO table's column value

Compare table content

I have 2 tables and I need to do a table compare:
TABLE A
LABEL
VALUE
TABLE B
LABEL
VALUE
Basically I want:
Records in where the values are not equal on matching labels
Records in TABLE A that are not in TABLE B
Records in TABLE B that are not in TABLE A
With that information, I can record the proper historical data I need to. It will show me where the value has changed, or where a label was added or deleted......you can say TABLE A is the "new" set of data, and TABLE B is the "old" set of data. So I can see what is being added, what was deleted, and what was changed.
Been trying with UNION & MINUS, but no luck yet.
Something like:
A LABEL A VALUE B LABEL B VALUE
---------------------------------------
XXX 5 XXX 3
YYY 2
ZZZ 4
WWW 7 WWW 8
If the labels and values are the same, I do not need them in the result set.
Here is one way (and possibly the most efficient way) to solve this problem. The main part is the subquery that does a UNION ALL and GROUP BY on the result, keeping only groups consisting of a single row. (The groups with two rows are those where the same row exists in both tables.) This method was invented by Marco Stefanetti - first discussed on the AskTom discussion board. The benefit of this approach - over the more common "symmetric difference" approach - is that each base table is read just once, not twice.
Then, to put the result in the desired format, I use a PIVOT operation (available since Oracle 11.1); in earlier versions of Oracle, the same can be done with a standard aggregate outer query.
Note that I modified the inputs to show the handling of NULL in the VALUE column also.
Important: This solution assumes LABEL is primary key in both tables; if not, it's not clear how the required output would even make sense.
with
table_a ( label, value ) as (
select 'AAA', 3 from dual
union all select 'CCC', null from dual
union all select 'XXX', 5 from dual
union all select 'WWW', 7 from dual
union all select 'YYY', 2 from dual
union all select 'HHH', null from dual
),
table_b ( label, value ) as (
select 'ZZZ', 4 from dual
union all select 'AAA', 3 from dual
union all select 'HHH', null from dual
union all select 'WWW', 8 from dual
union all select 'XXX', 3 from dual
union all select 'CCC', 1 from dual
)
-- End of test data (NOT PART OF THE SOLUTION!) SQL query begins below this line.
select a_label, a_value, b_label, b_value
from (
select max(source) as source, label as lbl, label, value
from (
select 'A' as source, label, value
from table_a
union all
select 'B' as source, label, value
from table_b
)
group by label, value
having count(*) = 1
)
pivot ( max(label) as label, max(value) as value for source in ('A' as a, 'B' as b) )
;
Output:
A_LABEL A_VALUE B_LABEL B_VALUE
------- ------- ------- -------
YYY 2
CCC CCC 1
WWW 7 WWW 8
ZZZ 4
XXX 5 XXX 3

How to create id with AUTO_INCREMENT on a view in Oracle

can any one help to create a AUTO_INCREMENT column on a view in oracle 11g.
Thanks
While it's not possible to return a single unique identity column for a view whose underlying data does not have any single unique identifier, it is possible to return composite values that uniquely identify the data. For example given a table of CSV Data with a unique ID on each row:
create table sample (id number primary key, csv varchar2(4000));
where the CSV column contains a string of comma separated values:
insert into sample
select 1, 'a' from dual union all
select 2, 'b,c' from dual union all
select 3, 'd,"e",f' from dual union all
select 4, ',h,' from dual union all
select 5, 'j,"",l' from dual union all
select 6, 'm,,o' from dual;
The following query will unpivot the csv data and the composite values (ID, SEQ) will uniquely identify each VALue, The ID column idetifies the record the data came from, and SEQ uniquely identifies the position in the CSV:
WITH pvt(id, seq, csv, val, nxt) as (
SELECT id -- Parse out individual list items
, 1 -- separated by commas and
, csv -- optionally enclosed by quotes
, REGEXP_SUBSTR(csv,'(["]?)([^,]*)\1',1,1,null,2)
, REGEXP_INSTR(csv, ',', 1, 1)
FROM sample
UNION ALL
SELECT id
, seq+1
, csv
, REGEXP_SUBSTR(csv,'(["]?)([^,]*)\1',nxt+1,1,null,2)
, REGEXP_INSTR(csv, ',', nxt+1, 1)
FROM pvt
where nxt > 0
)
select * from pvt order by id, seq;
ID SEQ CSV VAL NXT
---------- ---------- ---------- ---------- ----------
1 1 a a 0
2 1 b,c b 2
2 2 b,c c 0
3 1 d,"e",f d 2
3 2 d,"e",f e 6
3 3 d,"e",f f 0
4 1 ,h, [NULL] 1
4 2 ,h, h 3
4 3 ,h, [NULL] 0
5 1 j,"",l j 2
5 2 j,"",l [NULL] 5
5 3 j,"",l l 0
6 1 m,,o m 2
6 2 m,,o [NULL] 3
6 3 m,,o o 0
15 rows selected.

Resources