Oracle joining tables using WITH clause with SPLIT [duplicate] - sql

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);

Related

How to include the code into a REPLACE function in oracle?

User #psaraj12 helped me with a ticket here about finding ascii character in a string in my DB with the following code:
with test (col) as (
select
'L.A.D'
from
dual
union all
select
'L.􀈉.D.'
from
dual
)
select
col,
case when max(ascii_of_one_character) >= 65535 then 'NOT OK' else 'OK' end result
from
(
select
col,
substr(col, column_value, 1) one_character,
ascii(
substr(col, column_value, 1)
) ascii_of_one_character
from
test cross
join table(
cast(
multiset(
select
level
from
dual connect by level <= length(col)
) as sys.odcinumberlist
)
)
)
group by
col
having max(ascii_of_one_character) >= 4000000000;
The script looks for characters of a certain range GROUPs them and marks displays them.
Is it possible to include this in a REPLACE statement of a similar sort:
REPLACE(table.column, max(ascii_of_one_character) >= 4000000000, '')
EDIT: As per #flyaround answer this is the code I use changed a little bit:
with test (col) as (
select skunden.name1
from skunden
)
select col
, REGEXP_REPLACE(col, 'max(ascii_of_one_character)>=4000000000', '') as cleaned
, CASE WHEN REGEXP_COUNT(col, 'max(ascii_of_one_character)>=4000000000') > 0 THEN 0 ELSE 1 END as isOk
from test;
Coming back to your original code, because my suggested REGEX_REPLACE is not working sufficient with high surrogates. Your approach is already very effective, so I jumped into it to have a solution here.
MERGE
INTO skunden
USING (
select
id as innerId,
name as innerName,
case when max(ascii_of_one_character) >= 65535 then 0 else 1 end isOk,
listagg(case when ascii_of_one_character <65535 then one_character end , '') within group (order by rn) as cleaned
from
(
select
id,
name,
substr(name, column_value, 1) one_character,
ascii(
substr(name, column_value, 1)
) ascii_of_one_character
, rownum as rn
from
skunden cross
join table(
cast(
multiset(
select
level
from
dual connect by level <= length(name)
) as sys.odcinumberlist
)
)
)
group by
id, name
having max(ascii_of_one_character) >= 4000000000
)
ON (skunden.id = innerId)
WHEN MATCHED THEN
UPDATE
SET name = cleaned
;
On MERGE you can't use the referencing column for an update. Therefore you should use the unique key (I used 'id' in my example) of your table.
The resulting value will be 'L..D' for your example value of 'L.􀈉.D.'
If I got your question correctly you would like to remove characters with a higher decimal representation of characters than specified.
You could check to use REGEXP_REPLACE for this, like:
with test (col) as (
select
'L.A.D'
from
dual
union all
select
'L.􀈉.D.'
from
dual
)
select col
, REGEXP_REPLACE(col, '[^\u00010000-\u0010FFFF]+$', '') as cleaned
, CASE WHEN REGEXP_COUNT(col, '[^\u00010000-\u0010FFFF]+$') > 0 THEN 0 ELSE 1 END as isOk
from test;

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...

Regular Expression

I have data like this:
A:123, A:983, A:122, B:232, B:392, C:921, D:221, D:121, D:838
And I want to have my result like
A:123, 983, 122, B:232, 392, C:921, D:221, 121, 838
Can anyone please suggest?
You can use regexp_substr() and listagg() functions
with connect by level <= regexp_count(':') as
with t(str) as
(
select 'A:123, A:983, A:122, B:232, B:392, C:921, D:221, D:121, D:838' from dual
), t2 as
(
select level as rn,
regexp_substr(str,'([[:alpha:]]+)',1,level) as letter,
regexp_substr(str,'(\d)+',1,level) as num
from t
connect by level <= regexp_count(str,':')
), t3 as
(
select letter||':'||listagg(num,',') within group (order by rn) as str
from t2
group by letter
)
select listagg(str,',') within group (order by substr(str,1,1)) as str
from t3;
STR
-------------------------------------------
A:123,983,122,B:232,392,C:921,D:221,121,838
Demo
You do not need regular expressions as it can be done with standard string functions:
Oracle Setup:
CREATE TABLE test_data ( value ) AS
SELECT 'A:123, A:983, A:122, B:232, B:392, C:921, D:221, D:121, D:838' FROM DUAL
Query:
WITH rsqfc ( id, value, spos, sep, epos ) AS (
SELECT ROWNUM,
value,
1,
INSTR( value, ':', 1 ),
INSTR( value, ', ', 1 )
FROM test_data
UNION ALL
SELECT id,
value,
epos + 2,
INSTR( value, ':', epos + 2 ),
INSTR( value, ', ', epos + 2 )
FROM rsqfc
WHERE epos > 0
),
items ( id, prefix, value ) AS (
SELECT id,
SUBSTR( value, spos, sep - spos ),
CASE
WHEN epos > 0
THEN SUBSTR( value, sep + 1, epos - sep - 1 )
ELSE SUBSTR( value, sep + 1 )
END
FROM rsqfc
),
item_groups ( id, prefix, grouped_value ) AS (
SELECT id,
prefix,
LISTAGG( value, ',' ) WITHIN GROUP ( ORDER BY ROWNUM )
FROM items
GROUP BY id, prefix
)
SELECT LISTAGG( prefix || ':' || grouped_value, ', ' )
WITHIN GROUP ( ORDER BY prefix ) AS value
FROM item_groups
GROUP BY id
Output:
| VALUE |
| :--------------------------------------------- |
| A:123,983,122, B:232,392, C:921, D:221,121,838 |
db<>fiddle here

Oracle split by regex and aggregate again

I have a table from where I need to get only some part of record with comma after one part of record.
for example I have
ABCD [1000-1987] BCD[101928-876] adgs[10987-786]
I want to get the record like :
1000-1987,101928-876,10987-786
Can you please help me out to get the record as mentioned.
If you don't use 11g and do not want to use wm_concat:
WITH
my_data AS (
SELECT 'ABCD [1000-1987] BCD[101928-876] adgs[10987-786]' AS val FROM dual
)
SELECT
ltrim(
MAX(
sys_connect_by_path(
rtrim(ltrim(regexp_substr(val, '\[[0-9-]*\]', 1, level, NULL), '['), ']'),
',')
),
',') AS val_part
FROM my_data
CONNECT BY regexp_substr(val, '\[[0-9-]*\]', 1, level, NULL) IS NOT NULL
;
If using wm_concat is ok for you:
WITH
my_data AS (
SELECT 'ABCD [1000-1987] BCD[101928-876] adgs[10987-786]' AS val FROM dual
)
SELECT
wm_concat(rtrim(ltrim(regexp_substr(val, '\[[0-9-]*\]', 1, level, NULL), '['), ']')) AS val_part
FROM my_data
CONNECT BY regexp_substr(val, '\[[0-9-]*\]', 1, level, NULL) IS NOT NULL
;
If you use 11g:
WITH
my_data AS (
SELECT 'ABCD [1000-1987] BCD[101928-876] adgs[10987-786]' AS val FROM dual
)
SELECT
listagg(regexp_substr(val, '[a-b ]*\[([0-9-]*)\] ?', 1, level, 'i', 1), ',') WITHIN GROUP (ORDER BY 1) AS val_part
FROM my_data
CONNECT BY regexp_substr(val, '[a-b ]*\[([0-9-]*)\] ?', 1, level, 'i', 1) IS NOT NULL
;
Read more about string aggregation techniques: Tim Hall about aggregation techniques
Read more about regexp_substr: regexp_substr - Oracle Documentation - 10g
Read more about regexp_substr: regexp_substr - Oracle Documentation - 11g
You don't have to split and then aggregate it. You can use regexp_replace to keep only those characters within square brackets, then replace the square brackets by comma.
WITH my_data
AS (SELECT 'ABCD [1000-1987] BCD[101928-876] adgs[10987-786]' AS val
FROM DUAL)
SELECT RTRIM (
REPLACE (
REGEXP_REPLACE (val, '(\[)(.*?\])|(.)', '\2'),
']', ','),
',')
FROM my_data;

Split comma separated values in Oracle 9i

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.