Split comma separated values in Oracle 9i - sql

In Oracle, I have columns called orderids
orderids
111,222,333
444,55,66
77,77
How can get the output as
Orderid
111
222
333
444
55
66
77
77

Try this:
WITH TT AS
(SELECT orderid COL1 FROM orders)
SELECT substr(str,
instr(str, ',', 1, LEVEL) + 1,
instr(str, ',', 1, LEVEL + 1) -
instr(str, ',', 1, LEVEL) - 1) COL1
FROM (SELECT rownum AS r,
','|| COL1||',' AS STR
FROM TT )
CONNECT BY PRIOR r = r
AND instr(str, ',', 1, LEVEL + 1) > 0
AND PRIOR dbms_random.STRING('p', 10) IS NOT NULL
;
See this SQLFiddle

This is one appraoch:
with order_table as (
select '111,222,333' as orderids from dual
union all select '444,55,66' from dual
union all select '77,77' from dual
)
select substr(orderids, instr(orderids, ',', 1, lvl) + 1, instr(orderids, ',', 1, lvl + 1) - instr(orderids, ',', 1, lvl) - 1) orderid
from
( select ',' || orderids || ',' as orderids from order_table ),
( select level as lvl from dual connect by level <= 100 )
where lvl <= length(orderids) - length(replace(orderids, ',')) - 1;
Just remove the WITH clause and replace the order_table with your real table.

This too might help you,
with t(orderid) as
(
SELECT '111,222,333' FROM dual
UNION
SELECT '444,55,66' FROM dual
UNION
SELECT '177,77' FROM dual
)
SELECT trim(x.COLUMN_VALUE.EXTRACT('e/text()')) cols
FROM t t, TABLE (xmlsequence(XMLTYPE('<e><e>' || REPLACE(t.orderid,',','</e><e>')|| '</e></e>').EXTRACT('e/e'))) x;

instr(','||NVL('972414AQ,972414AQ',I.CUSIP)||',', ','||I.CUSIP||',') > 0
This is the actual query I was looking for.

Related

How to get ids which does not exist in another listagg | Oracle 19c |

I have written a query to get values in comma separated format from both the table
Table 1 :
SELECT
regex_replace(xmlcast(Xmlagg(XMLELEMENT(empid, empid, ',')) AS clob), '\s*,\s*$', '') AS str1
FROM
(SELECT empid
FROM employee);
Table 2:
SELECT
regex_replace(xmlcast(Xmlagg(XMLELEMENT(depid, depid, ',')) AS clob), '\s*,\s*$', '') AS str2
FROM
(SELECT depid
FROM department);
Output of both queries:
str1 = 1,4,5,6,8
str2 = 1,5,6
How do I compare both the str1 and str2 and get ids which are in str1 but not in str2
Expected output: 4,8
You do not need to compare the delimited strings, you can simply use NOT IN (or NOT EXISTS) and compare the table values:
SELECT regexp_replace( xmlcast(Xmlagg(XMLELEMENT(empid,empid,',')) as clob),'\s*,\s*$','') AS str1
FROM employee
WHERE empid NOT IN (
SELECT depid
FROM department
);
However, you should consider whether it makes sense to compare the IDs for employees to the IDs for departments as that does not appear to make logical sense.
For the sample data:
CREATE TABLE employee (empid) AS
SELECT 1 FROM DUAL UNION ALL
SELECT 4 FROM DUAL UNION ALL
SELECT 5 FROM DUAL UNION ALL
SELECT 6 FROM DUAL UNION ALL
SELECT 8 FROM DUAL;
CREATE TABLE department (depid) AS
SELECT 1 FROM DUAL UNION ALL
SELECT 5 FROM DUAL UNION ALL
SELECT 6 FROM DUAL;
The query outputs:
STR1
4,8
db<>fiddle here
Another approach considering that both str1 and str2 are coming from different tables. Although I consider the comments more than right that this kind of comparison should not be done this way.
with x as
(
select regexp_substr(x.str1,'[^,]+',1,level) as str1_spit
from ( select '1,4,5,6,8' as str1 from dual ) x
CONNECT BY LEVEL <=REGEXP_COUNT(x.str1 ,'[,]') + 1
),
y as
( select regexp_substr(y.str2,'[^,]+',1,level) as str2_spit
from ( select '1,5,6' as str2 from dual ) y
CONNECT BY LEVEL <=REGEXP_COUNT(y.str2 ,'[,]') + 1
)
select LISTAGG(str1_spit, ',') WITHIN GROUP (order by str1_spit) as final_value
from
(
select x.str1_spit , y.str2_spit
from x left join y on x.str1_spit = y.str2_spit
where y.str2_spit is null
order by x.str1_spit
)
Demo
SQL> with x as
2 (
select regexp_substr(x.str1,'[^,]+',1,level) as str1_spit
from ( select '1,4,5,6,8' as str1 from dual ) x
CONNECT BY LEVEL <=REGEXP_COUNT(x.str1 ,'[,]') + 1
),
y as
( select regexp_substr(y.str2,'[^,]+',1,level) as str2_spit
from ( select '1,5,6' as str2 from dual ) y
CONNECT BY LEVEL <=REGEXP_COUNT(y.str2 ,'[,]') + 1
)
select LISTAGG(str1_spit, ',') WITHIN GROUP (order by str1_spit) as final_value
from
(
select x.str1_spit , y.str2_spit
from x left join y on x.str1_spit = y.str2_spit
where y.str2_spit is null
order by x.str1_spit
) 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 ;
FINAL_VALUE
--------------------------------------------------------------------------------
4,8
SQL>
If you must do it by comparing strings (don't, use NOT IN or NOT EXISTS and compare the tables) then you can do it by only splitting one of the two strings and using simple string functions (rather than regular expressions, which are an order-of-magnitude slower):
WITH data (str1, str2) AS (
SELECT TO_CLOB('1,4,5,6,8'),
TO_CLOB('1,5,6')
FROM DUAL
),
bounds (str1, str2, spos, epos) AS (
SELECT str1,
str2,
1,
INSTR(str1, ',', 1)
FROM data
UNION ALL
SELECT str1,
str2,
epos + 1,
INSTR(str1, ',', epos + 1)
FROM bounds
WHERE epos > 0
),
items (item, str2) AS (
SELECT CASE epos
WHEN 0
THEN SUBSTR(str1, spos)
ELSE SUBSTR(str1, spos, epos - spos)
END,
',' || str2 || ','
FROM bounds
ORDER BY spos
)
SELECT regexp_replace(
xmlcast(Xmlagg(XMLELEMENT(item,item,',')) as clob),
'\s*,\s*$'
) AS str3
FROM items
WHERE str2 NOT LIKE '%,' || item || ',%';
Which outputs:
STR3
4,8
db<>fiddle here

How to count data based on specific condition in Oracle

Let's say I have a table of persons with ids and names as follows
[Person]
ID NAME
================
1 Michael
2 Michelle
3 Emma
4 Evan
5 Ellen
6 Gary
I want to count the number of persons based on the first characters of their names.
Here's the output I expect
NUMBER_OF_PERSONS
=================
2 //M = Michael and Michelle
3 //E = Emma, Evan and Ellen
1 //G = Gary
How do I achieve this in Oracle?
And here's my query
select count(id) as number_of_person
from person
where substr(name) in (select distinct substr(name,1,1) from person);
You can acheive that purpose using below solution.
with Person (ID, NAME ) as (
select 1, 'Michael' from dual union all
select 2, 'Michelle' from dual union all
select 3, 'Emma' from dual union all
select 4, 'Evan' from dual union all
select 5, 'Ellen' from dual union all
select 6, 'Gary' from dual
)
select count(*) || ' //' || substr(NAME, 1, 1) || ' = ' ||
case
when regexp_count( listagg(NAME, ' and ') within group ( order by ID ), ' and ') > 1
then regexp_replace( listagg(NAME, ', ') within group ( order by ID ), ', ([^,]+)$', ' and \1 ', 1, 1 )
else listagg(NAME, ' and ') within group ( order by ID )
end NUMBER_OF_PERSONS
from Person
group by substr(NAME, 1, 1)
order by substr(NAME, 1, 1)
;
db<>fiddle
If you just want the count, you would use group by:
select substr(name, 1, 1) as first_letter,
count(*) as number_of_person
from person
group by substr(name, 1, 1) ;
If, in addition, you actually wanted the list of names, you could put that in another column, assuming there are not too many:
select substr(name, 1, 1) as first_letter,
count(*) as number_of_person,
listagg(name, ', ') within group (order by name) as names
from person
group by substr(name, 1, 1) ;
This is my solution to it:
WITH tbl AS (
SELECT 1 AS ID, 'Michael' AS NAME FROM dual UNION
SELECT 2, 'Michelle' FROM dual UNION
SELECT 3, 'Emma' FROM dual UNION
SELECT 4, 'Evan' FROM dual UNION
SELECT 5, 'Ellen' FROM dual UNION
SELECT 6, 'Gary' FROM dual
)
SELECT COUNT(1)
, SUBSTR(names.name,1,1)
, REGEXP_REPLACE((listagg(names.name,', ') WITHIN GROUP (ORDER BY names.name)), ',([^,]*)$', ' and \1')
FROM tbl names
GROUP BY SUBSTR(names.name,1,1);

ERROR: syntax error at or near "WITH" LINE 2: WITH RECURSIVE cte AS

Kindly let me know where I am making mistake as i am unable to create procedure:
CREATE OR REPLACE PROCEDURE dpk_mc_tr.dpr_mc_tr_cmo_med_form_arc_inf ( in_usercode text, in_usersess text, in_compcode text, in_stardate text, in_stopdate text, in_sericate text, in_matccate text, in_seritycd text, in_matycode text, in_venucode text, in_teamcode text, in_drsmflag text, in_daynight text, in_telecast text, in_progname text, out_code INOUT numeric ) AS $body$
WITH RECURSIVE cte AS (
DECLARE
c01 CURSOR FOR
SELECT DISTINCT REPLACE(SUBSTR((SELECT array_to_string(a, '') FROM regexp_matches(in_sericate, '[^,]+', 'g') AS foo(a) LIMIT 1 OFFSET (LEVEL
- 1)),
2,
6
),
']',
''
) sericate
(SELECT array_to_string(a, '') FROM regexp_matches(in_sericate, '[^,]+', 'g') AS foo(a) LIMIT 1 OFFSET (LEVEL - 1)) IS NOT NULL UNION ALL
DECLARE
c01 CURSOR FOR
SELECT DISTINCT REPLACE(SUBSTR((SELECT array_to_string(a, '') FROM regexp_matches(in_sericate, '[^,]+', 'g') AS foo(a) LIMIT 1 OFFSET (LEVEL
- 1)),
2,
6
),
']',
''
) sericate
(SELECT array_to_string(a, '') FROM regexp_matches(in_sericate, '[^,]+', 'g') AS foo(a) LIMIT 1 OFFSET (LEVEL - 1)) IS NOT NULL JOIN cte c ON ()
) SELECT * FROM cte;
;
m01 CURSOR FOR
SELECT sysofcde sericate
FROM sycodmas
WHERE compcode = '001' AND modlcode = 'MM' AND syhrdcde = 'SRC'
UNION ALL
SELECT 'X' sericate
;WITH RECURSIVE cte AS (
c02 CURSOR FOR
SELECT DISTINCT REPLACE(SUBSTR((SELECT array_to_string(a, '') FROM regexp_matches(in_matccate, '[^,]+', 'g') AS foo(a) LIMIT 1 OFFSET (LEVEL
- 1)),
2,
6
),
']',
''
) matccate
(SELECT array_to_string(a, '') FROM regexp_matches(in_matccate, '[^,]+', 'g') AS foo(a) LIMIT 1 OFFSET (LEVEL - 1)) IS NOT NULL UNION ALL
c02 CURSOR FOR
SELECT DISTINCT REPLACE(SUBSTR((SELECT array_to_string(a, '') FROM regexp_matches(in_matccate, '[^,]+', 'g') AS foo(a) LIMIT 1 OFFSET (LEVEL
- 1)),
2,
6
),
']',
''
) matccate
(SELECT array_to_string(a, '') FROM regexp_matches(in_matccate, '[^,]+', 'g') AS foo(a) LIMIT 1 OFFSET (LEVEL - 1)) IS NOT NULL JOIN cte c ON ()
) SELECT * FROM cte;
;
m02 CURSOR FOR
SELECT sysofcde matccate
FROM sycodmas
WHERE compcode = '001' AND modlcode = 'MM' AND syhrdcde = 'MAC'
UNION ALL
SELECT 'X' matccate
;WITH RECURSIVE cte AS (
c03 CURSOR FOR
SELECT DISTINCT REPLACE(SUBSTR((SELECT array_to_string(a, '') FROM regexp_matches(in_seritycd, '[^,]+', 'g') AS foo(a) LIMIT 1 OFFSET (LEVEL
- 1)),
2,
6
),
']',
''
) seritycd
(SELECT array_to_string(a, '') FROM regexp_matches(in_seritycd, '[^,]+', 'g') AS foo(a) LIMIT 1 OFFSET (LEVEL - 1)) IS NOT NULL UNION ALL
c03 CURSOR FOR
SELECT DISTINCT REPLACE(SUBSTR((SELECT array_to_string(a, '') FROM regexp_matches(in_seritycd, '[^,]+', 'g') AS foo(a) LIMIT 1 OFFSET (LEVEL
- 1)),
2,
6
),
']',
''
) seritycd
(SELECT array_to_string(a, '') FROM regexp_matches(in_seritycd, '[^,]+', 'g') AS foo(a) LIMIT 1 OFFSET (LEVEL - 1)) IS NOT NULL JOIN cte c ON ()
) SELECT * FROM cte;
;
ERROR: syntax error at or near "WITH"
LINE 2: WITH RECURSIVE cte AS (**
Thanks
Your query has actually several errors.
semicolon in: SELECT 'X' sericate ;WITH ...
You cannot use cursors inside a recursive CTE
Recursive CTE's are actually quite limited. You cannot outer join with cte inside recursive part, you cannot use grouping clause, cannot use cte's name inside a subquery...
I'm not sure what you are trying to achieve, but using with recursive is not a good way to go here... Also, remember that regexp matching is very slow...

Oracle joining tables using WITH clause with SPLIT [duplicate]

This question already has answers here:
How to split a varchar column as multiple values in SQL?
(2 answers)
Closed 1 year ago.
I have this table that is not linked to another table, because they don't have any same column. Now they want a report that will link both tables.
But the problem is the only common column to them is the WAFER_INFO column which has multiple values separated by a comma that is why I need to split them to make multiple records but different WAFER_INFO.
First Table
select wafer_info
from bondertab_g3
where tha_reel_id='TGDT349028H'
order by insert_dm,tha_reel_id,processlk_ky
Results for the query above
TGK343067-22,TGK343067-25,TGK343067-24,TGK343067-23
Second Table
select hp_part_nr,wafer_id,good_cnt,total_rej_cnt,processlk_ky,toollk_ky,toolnrlk_ky,materiallk_ky
from sawinsptab
where wafer_id ='TGK343067-22';
select hp_part_nr,wafer_id,good_cnt,total_rej_cnt,processlk_ky,toollk_ky,toolnrlk_ky,materiallk_ky
from sawinsptab
where wafer_id ='TGK343067-25';
select hp_part_nr,wafer_id,good_cnt,total_rej_cnt,processlk_ky,toollk_ky,toolnrlk_ky,materiallk_ky
from sawinsptab
where wafer_id ='TGK343067-24';
select hp_part_nr,wafer_id,good_cnt,total_rej_cnt,processlk_ky,toollk_ky,toolnrlk_ky,materiallk_ky
from sawinsptab
where wafer_id ='TGK343067-23';
Basically just all of them in the first table
I already achieve on how to split all those records using this code
With DATA AS (
select tha_reel_id, wafer_info str
from bondertab_g3
where tha_reel_id='TGDT349028H'
)
SELECT A.tha_reel_id, trim(regexp_substr(A.str, '[^,]+', 1, LEVEL)) WAFERID FROM DATA A
CONNECT BY instr(str, ',', 1, LEVEL - 1) > 0
Now my question is how can I connect my above query and connect it to the Second Table when the only column same is the WAFERID
Find where the sawinsptab.wafer_id (wrapped in your comma delimiters) is a sub-string of bondertab_g3.wafer_info (wrapped in your comma delimiters):
select hp_part_nr,
wafer_id,
good_cnt,
total_rej_cnt,
processlk_ky,
toollk_ky,
toolnrlk_ky,
materiallk_ky
from sawinsptab s
where EXISTS (
select 1
from bondertab_g3 b
where b.tha_reel_id='TGDT349028H'
and ','||b.wafer_info||',' LIKE '%,'||s.wafer_id||',%'
);
or
select s.hp_part_nr,
s.wafer_id,
s.good_cnt,
s.total_rej_cnt,
s.processlk_ky,
s.toollk_ky,
s.toolnrlk_ky,
s.materiallk_ky,
b.other_column
from sawinsptab s
INNER JOIN bondertab_g3 b
ON ( ','||b.wafer_info||',' LIKE '%,'||s.wafer_id||',%' )
where b.tha_reel_id='TGDT349028H';
or, if you need to use an index on wafer_id and want to split the delimited string then, you can do it with a recursive sub-query factoring clause and simple string functions (rather than slow regular expressions):
select hp_part_nr,
wafer_id,
good_cnt,
total_rej_cnt,
processlk_ky,
toollk_ky,
toolnrlk_ky,
materiallk_ky
from sawinsptab s
where wafer_id IN (
WITH delimiter_bounds ( wafer_info, startidx, endidx ) AS (
SELECT wafer_info,
1,
INSTR( wafer_info, ',', 1 )
FROM bondertab_g3
WHERE tha_reel_id='TGDT349028H'
UNION ALL
SELECT wafer_info,
endidx + 1,
INSTR( wafer_info, ',', endidx + 1 )
FROM delimiter_bounds
WHERE endidx > 0
)
SELECT CASE
WHEN endidx = 0
THEN SUBSTR( wafer_info, startidx )
ELSE SUBSTR( wafer_info, startidx, endidx - startidx )
END
from delimiter_bounds
);
or
WITH delimiter_bounds ( wafer_info, other_column, startidx, endidx ) AS (
SELECT wafer_info,
other_column,
1,
INSTR( wafer_info, ',', 1 )
FROM bondertab_g3
WHERE tha_reel_id='TGDT349028H'
UNION ALL
SELECT wafer_info,
other_column,
endidx + 1,
INSTR( wafer_info, ',', endidx + 1 )
FROM delimiter_bounds
WHERE endidx > 0
)
select s.hp_part_nr,
s.wafer_id,
s.good_cnt,
s.total_rej_cnt,
s.processlk_ky,
s.toollk_ky,
s.toolnrlk_ky,
s.materiallk_ky,
b.other_column
from sawinsptab s
INNER JOIN (
SELECT CASE
WHEN endidx = 0
THEN SUBSTR( wafer_info, startidx )
ELSE SUBSTR( wafer_info, startidx, endidx - startidx )
END AS wafer_id,
other_column
FROM delimiter_bounds
) b
ON ( s.wafer_id = b.wafer_id )
You can change the first query to a CTE and then use that directly in your query:
with data as (
select tha_reel_id, wafer_info str
from bondertab_g3
where tha_reel_id = 'TGDT349028H'
),
wafers as (
select d.tha_reel_id, trim(regexp_substr(d.str, '[^,]+', 1, LEVEL)) as waferid
from data d
connect by instr(str, ',', 1, LEVEL - 1) > 0
)
select s.*
from sawinsptab s
where wafer_id in (select w.waferid from wafers);

Oracle's SQL to extract values between two chars in a list

I have a list of value such as:
'aaa:data1!x,bbb:data2!y,cc:data3!z'
I need to extract values between ':' and '!' such as data1 data2 data3
I tried this:
SELECT regexp_substr('aaa:data1!x,bbb:data2!y,cc:data3!z',
'\:([^!]+)\!',
1,
1,
NULL,
1) AS output
FROM dual;
But it returns me only data1
You're on the right way, just need to send an iterated integers to the 4th argument of regexp_substr function
This gives vertical result :
with t(str) as
(
select 'aaa:data1!x,bbb:data2!y,cc:data3!z' from dual
)
select regexp_substr(str,
':([^!]+)',
1,
level,
null,
1) as output
from t
connect by level <= regexp_count(str,'!');
OUTPUT
------
data1
data2
data3
This gives horizontal result :
with t(str) as
(
select 'aaa:data1!x,bbb:data2!y,cc:data3!z' from dual
), t2 as
(
select regexp_substr(str,
':([^!]+)',
1,
level,
null,
1) as output
from t
connect by level <= regexp_count(str,'!')
)
select listagg(output,' ') within group (order by output) as output from t2
OUTPUT
-----------------
data1 data2 data3
Good, old SUBSTR + INSTR does the job:
SQL> with test (col) as
2 (select 'aaa:data1!x,bbb:data2!y,cc:data3!z' from dual)
3 select substr(col, instr(col, ':', 1, level) + 1,
4 instr(col, '!', 1, level) - instr(col, ':', 1, level) - 1
5 ) result
6 from test
7 connect by level <= regexp_count(col, ':');
RESULT
----------------------------------
data1
data2
data3
SQL>