Hierarchical table merge itself many times - sql

CREATE TABLE b( ID VARCHAR2(50 BYTE),
PARENT_ID VARCHAR2(50 BYTE),
NAME NVARCHAR2(200)
);
https://community.oracle.com/thread/3513540?
Above link i explained everything

Any reason you don't like the answer posted there? Does the SQL Fiddle which illustrates the answer in question not as you've described your needed results to be?
Edit:
An SQL Fiddle using the technique Frank Kulash suggested. This is the first Oracle based query I have written since 2002 ( on 9iAS ).
SELECT REGEXP_SUBSTR( a.PIDPath, '[^,]+', 1, 1 ) Header1,
REGEXP_SUBSTR( a.IDPath, '[^,]+', 1, 1 ) Header2,
REGEXP_SUBSTR( a.NamePath, '[^,]+', 1, 1 ) Header3,
REGEXP_SUBSTR( a.PIDPath, '[^,]+', 1, 2 ) Header4,
REGEXP_SUBSTR( a.IDPath, '[^,]+', 1, 2 ) Header5,
REGEXP_SUBSTR( a.NamePath, '[^,]+', 1, 2 ) Header6,
REGEXP_SUBSTR( a.PIDPath, '[^,]+', 1, 3 ) Header7,
REGEXP_SUBSTR( a.IDPath, '[^,]+', 1, 3 ) Header8,
REGEXP_SUBSTR( a.NamePath, '[^,]+', 1, 3 ) Header9,
REGEXP_SUBSTR( a.PIDPath, '[^,]+', 1, 4 ) Header10,
REGEXP_SUBSTR( a.IDPath, '[^,]+', 1, 4 ) Header11,
REGEXP_SUBSTR( a.NamePath, '[^,]+', 1, 4 ) Header12,
REGEXP_SUBSTR( a.PIDPath, '[^,]+', 1, 5 ) Header13,
REGEXP_SUBSTR( a.IDPath, '[^,]+', 1, 5 ) Header14,
REGEXP_SUBSTR( a.NamePath, '[^,]+', 1, 5 ) Header15
--,REGEXP_SUBSTR( a.PIDPath, '[^,]+', 1, n )
--,REGEXP_SUBSTR( a.IDPath, '[^,]+', 1, n )
--,REGEXP_SUBSTR( a.NamePath, '[^,]+', 1, n )
FROM ( SELECT LEVEL RootLvl,
b.ID RootID,
SYS_CONNECT_BY_PATH( b.PARENT_ID, ',' )
|| ',' PIDPath,
SYS_CONNECT_BY_PATH( b.ID, ',' )
|| ',' IDPath,
SYS_CONNECT_BY_PATH( b.NAME, ',' )
|| ',' NamePath
FROM t b
START WITH b.PARENT_ID = '1'
CONNECT BY NOCYCLE PRIOR b.ID = b.PARENT_ID ) a
ORDER BY a.RootLvl, a.RootID;
Frank Kulash points out:
The number of columns in the result set has to be hard-coded into the query. You can code something today that produces 30 columns (that is, enough for 10 levels in the hierarchy), but if you change the data later so that there are 11 or more levels, then your query will start to lose results.
I may be back to try a PIVOT thing later. Oracle is craziness.
Edit:
But manageable enough. A static PIVOT works just fine. I can't get CROSS APPLY working like I think it should with VALUES ( MSSQL style ), so I've given up and substituted it for a passable UNION ALL. There's potential for some dynamic SQL work with this one, so it actually might be able to do what you need without hard coding the columns.
;
WITH c ( RID, ID, PARENT_ID, NAME ) AS (
SELECT ROW_NUMBER() OVER (
ORDER BY PARENT_ID ) RID,
ID, PARENT_ID, NAME
FROM t
UNION ALL
SELECT b.RID, a.ID, a.PARENT_ID, a.NAME
FROM t a,
c b
WHERE a.ID = b.PARENT_ID
)
SELECT p."'ID_1'" Header1,
p."'PARENT_ID_1'" Header2,
p."'NAME_1'" Header3,
p."'ID_2'" Header4,
p."'PARENT_ID_2'" Header5,
p."'NAME_2'" Header6,
p."'ID_3'" Header7,
p."'PARENT_ID_3'" Header8,
p."'NAME_3'" Header9,
p."'ID_4'" Header10,
p."'PARENT_ID_4'" Header11,
p."'NAME_4'" Header12,
p."'ID_5'" Header13,
p."'PARENT_ID_5'" Header14,
p."'NAME_5'" Header15
FROM ( SELECT RID,
'ID_' || ROW_NUMBER() OVER (
PARTITION BY RID
ORDER BY ID ) KeyName,
ID KeyValue
FROM c
UNION ALL
SELECT RID,
'PARENT_ID_' || ROW_NUMBER() OVER (
PARTITION BY RID
ORDER BY ID ) KeyName,
PARENT_ID KeyValue
FROM c
UNION ALL
SELECT RID,
'NAME_' || ROW_NUMBER() OVER (
PARTITION BY RID
ORDER BY ID ) KeyName,
CAST( NAME AS VARCHAR2( 200 ) ) KeyValue
FROM c ) s
PIVOT ( MAX( KeyValue ) FOR KeyName IN (
'ID_1', 'PARENT_ID_1', 'NAME_1',
'ID_2', 'PARENT_ID_2', 'NAME_2',
'ID_3', 'PARENT_ID_3', 'NAME_3',
'ID_4', 'PARENT_ID_4', 'NAME_4',
'ID_5', 'PARENT_ID_5', 'NAME_5' ) ) p
ORDER BY p.RID;

Related

Remove duplicate from strings in sql [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 3 months ago.
Improve this question
I have 2 cols
ID Value
ab^bc^ab^de
mn^mn^op
I want the output as
ID Value
ab^bc^de
mn^op
Can someone please help me in this.✋ I have around 500 rows in the table.
I tried using stuff and other ways but errors are popping up.
You can use a recursive query and simple string functions (which is much faster than regular expressions, but a little more to type) to split the string and then, in later Oracle versions, can re-aggregate it using LISTAGG(DISTINCT ...:
WITH bounds ( rid, value, spos, epos ) AS (
SELECT ROWID, value, 1, INSTR(value, '^', 1)
FROM table_name
UNION ALL
SELECT rid, value, epos + 1, INSTR(value, '^', epos + 1)
FROM bounds
WHERE epos > 0
)
SELECT LISTAGG(
DISTINCT
CASE epos
WHEN 0
THEN SUBSTR(value, spos)
ELSE SUBSTR(value, spos, epos - spos)
END,
'^'
) WITHIN GROUP (ORDER BY spos) AS unique_values
FROM bounds
GROUP BY rid;
Which, for the sample data:
CREATE TABLE table_name (value) AS
SELECT 'ab^bc^ab^de' FROM DUAL UNION ALL
SELECT 'mn^mn^op' FROM DUAL UNION ALL
SELECT 'ab^bc^ab^de' FROM DUAL UNION ALL
SELECT 'one^two^three^one^two^one^four' FROM DUAL;
Outputs:
UNIQUE_VALUES
ab^bc^de
mn^op
ab^bc^de
one^two^three^four
If you are using earlier versions or Oracle that do not support DISTINCT in the LISTAGG then you can aggregate twice:
WITH bounds ( rid, value, spos, epos ) AS (
SELECT ROWID, value, 1, INSTR(value, '^', 1)
FROM table_name
UNION ALL
SELECT rid, value, epos + 1, INSTR(value, '^', epos + 1)
FROM bounds
WHERE epos > 0
),
words (rid, word, spos) AS (
SELECT rid,
CASE epos
WHEN 0
THEN SUBSTR(value, spos)
ELSE SUBSTR(value, spos, epos - spos)
END,
spos
FROM bounds
),
unique_words ( rid, word, spos ) AS (
SELECT rid,
word,
MIN(spos)
FROM words
GROUP BY rid, word
)
SELECT LISTAGG(word, '^') WITHIN GROUP (ORDER BY spos) AS unique_values
FROM unique_words
GROUP BY rid;
Which gives the same output.
fiddle
For example:
Sample data:
SQL> with
2 test (col) as
3 (select 'ab^bc^ab^de' from dual union all
4 select 'mn^mn^op' from dual
5 ),
Split values into rows:
6 temp as
7 (select
8 col,
9 regexp_substr(col, '[^\^]+', 1, column_value) val,
10 column_value lvl
11 from test cross join
12 table(cast(multiset(select level from dual
13 connect by level <= regexp_count(col, '\^') + 1
14 ) as sys.odcinumberlist))
15 )
Aggregate them back, using only distinct values:
16 select col,
17 listagg(val, '^') within group (order by lvl) as result
18 from (select col, val, min(lvl) lvl
19 from temp
20 group by col, val
21 )
22 group by col;
COL RESULT
----------- --------------------
ab^bc^ab^de ab^bc^de
mn^mn^op mn^op
SQL>
Other solutions if your ORACLE version is recent enough to have LISTAGG DISTINCT:
with data(s) as (
select 'ab^bc^ab^de' from dual union all
select 'mn^mn^op' from dual
),
splitted(s, l, r) as (
select s, level, regexp_substr(s,'[^\^]+',1,level) from data
connect by regexp_substr(s,'[^\^]+',1,level) is not null and s = prior s and prior sys_guid() is not null
)
select s, listagg(distinct r, '^') within group(order by l) as r from splitted
group by s
;
And better if you have a PK, use it:
with data(id, s) as (
select 1, 'ab^bc^ab^de' from dual union all
select 2, 'mn^mn^op' from dual
),
splitted(id, l, r) as (
select id, level, regexp_substr(s,'[^\^]+',1,level) from data
connect by regexp_substr(s,'[^\^]+',1,level) is not null and id = prior id and prior sys_guid() is not null
)
select id, listagg(distinct r, '^') within group(order by l) as r from splitted
group by id
;
For the fun, using XML:
with data(s) as (
select 'ab^bc^ab^de' from dual union all
select 'mn^mn^op' from dual
)
select *
from data,
xmltable(
q'{string-join( for $atom in distinct-values((ora:tokenize($X,"\^"))) order by $atom return $atom, "^" )}'
passing s as "X"
columns
column_value varchar2(64) path '.'
)
;
(or fn:tokenize, depending on the DB version)

pl sql regular expression get specific occurrence

I have a string like this: aa;bb;cc
Number of chars of each block could be different.
; is the delimiter.
I need to take values seperately. For example: I want to take only the occurrence in the second position (bb).
I tried this:
SELECT trim(regexp_substr('aa;bb;cc', '[^;]+', 1, LEVEL)) str
FROM dual
CONNECT BY regexp_substr('aa;bb;cc', '[^;]+', 1, LEVEL) IS NOT NULL;
But if I do:
SELECT * FROM (SELECT trim(regexp_substr('aa;bb;cc', '[^;]+', 1, LEVEL)) str
FROM dual
CONNECT BY regexp_substr('aa;bb;cc', '[^;]+', 1, LEVEL) IS NOT NULL)
WHERE ROWNUM = 2;
It doesn't work.
Why don't you simply write this?
select trim(regexp_substr('aa;bb;cc', '[^;]+', 1, 2)) str from dual
If you want use recursive query use rownum with alias in inner query or use level pseudocolumn:
select str
from (
select level lvl, trim(regexp_substr('aa;bb;cc', '[^;]+', 1, 2)) str
from dual
connect by regexp_substr('aa;bb;cc', '[^;]+', 1, level) is not null)
where lvl = 2
WHERE ROWNUM = 2 will never return any result, as the rownum is calculated from the resultset of the query. But as there never is a first row, ROWNUM =2 will never be reached.
Easiest is to use OFFSET and LIMIT instead:
SELECT * FROM (
SELECT trim(regexp_substr('aa;bb;cc', '[^;]+', 1, LEVEL)) str
FROM dual
CONNECT BY regexp_substr('aa;bb;cc', '[^;]+', 1, LEVEL) IS NOT NULL
)
OFFSET 1 ROW FETCH NEXT 1 ROWS ONLY
You can use
with t(str) as
(
select 'aa;bb;cc' from dual
), t2 as
(
select trim(regexp_substr(str, '[^;]+', 1, level)) str,
level as lvl
from t
connect by regexp_substr(str, '[^;]+', 1, level) is not null
)
select str
from t2
where lvl = 2;
STR
---
bb
Demo
I don't suggest you use rownum as much as possible, especially queries with subqueries and order by clauses. In your case, WHERE ROWNUM = 1 returns a value ( and the result is untrustable, I mean may be other than you want for real values derived from tables) but for the other equalities ROWNUM = 2 or ROWNUM = 3 even do not return a value.

How can I eliminate duplicate data in multiple columns query

I just asked the question about how I eliminate duplicate data in a column
How can I eliminate duplicate data in column
this code below can delete duplicates in a column
with data as
(
select 'apple, apple, apple, apple' col from dual
)
select listagg(col, ',') within group(order by 1) col
from (
select distinct regexp_substr(col, '[^,]+', 1, level) col
from data
connect by level <= regexp_count(col, ',')
)
next question is
now I do not know how to eliminate data in multiple columns
select 'apple, apple, apple' as col1,
'prince,prince,princess' as col2,
'dog, cat, cat' as col3
from dual;
I would like to show
COL1 COL2 COL3
----- ---------------- --------
apple prince, princess dog, cat
You may use such a combination :
select
(
select listagg(str,',') within group (order by 0)
from
(
select distinct trim(regexp_substr('apple, apple, apple','[^,]+', 1, level)) as str
from dual
connect by level <= regexp_count('apple, apple, apple',',') + 1
)
) as str1,
(
select listagg(str,',') within group (order by 0)
from
(
select distinct trim(regexp_substr('prince,prince,princess','[^,]+', 1, level)) as str
from dual
connect by level <= regexp_count('prince,prince,princess',',') + 1
)
) as str2,
(
select listagg(str,',') within group (order by 0)
from
(
select distinct trim(regexp_substr('dog, cat, cat','[^,]+', 1, level)) as str
from dual
connect by level <= regexp_count('dog, cat, cat',',') + 1
)
) as str3
from dual;
STR1 STR2 STR3
------ --------------- --------
apple prince,princess cat,dog
Rextester Demo

Oracle view with application context using "group by" returns no result without a strange hack

I have created a view in Oracle which intends to load parts of the data in a table, and limited by the parameters set in the application context:
CREATE OR REPLACE VIEW VW_SALT_RAW_SUMMARY AS
(
select
VENDOR_ID, PRD_CATEGORY, LOGIN_NAME, CURRENCY /*, Some more aggregated fields*/
from
VENDOR_RECORD
where
(
order_time between SYS_CONTEXT('CTX_ALERT', 'rptBeginTime') and SYS_CONTEXT('CTX_ALERT', 'rptEndTime')
AND order_time between SYS_CONTEXT('CTX_ALERT', 'RESTRICT_BEGIN_TIME') AND SYS_CONTEXT('CTX_ALERT', 'RESTRICT_END_TIME')
)
and
(
(
SYS_CONTEXT('CTX_ALERT', 'rptVENDOR_ID') = '*'
or
(
VENDOR_ID in
(
select
regexp_substr(SYS_CONTEXT('CTX_ALERT', 'rptVENDOR_ID'), '[^, ]+',1, rownum) str
from
dual connect by level <= regexp_count (SYS_CONTEXT('CTX_ALERT', 'rptVENDOR_ID'), '[^, ]+')
)
)
)
AND
(
SYS_CONTEXT('CTX_ALERT', 'RESTRICT_VENDOR_ID') = '*'
or
(
VENDOR_ID in
(
select
regexp_substr(SYS_CONTEXT('CTX_ALERT', 'RESTRICT_VENDOR_ID'), '[^, ]+',1, rownum) str
from
dual connect by level <= regexp_count (SYS_CONTEXT('CTX_ALERT', 'RESTRICT_VENDOR_ID'), '[^, ]+')
)
)
)
)
and
(
(
SYS_CONTEXT('CTX_ALERT', 'rptPRD_CATEGORY') = '*'
or
(
PRD_CATEGORY in
(
select
regexp_substr(SYS_CONTEXT('CTX_ALERT', 'rptPRD_CATEGORY'), '[^, ]+',1, rownum) str
from
dual connect by level <= regexp_count (SYS_CONTEXT('CTX_ALERT', 'rptPRD_CATEGORY'), '[^, ]+')
)
)
)
AND
(
SYS_CONTEXT('CTX_ALERT', 'RESTRICT_PRD_CATEGORY') = '*'
or
(
PRD_CATEGORY in
(
select
regexp_substr(SYS_CONTEXT('CTX_ALERT', 'RESTRICT_PRD_CATEGORY'), '[^, ]+',1, rownum) str
from
dual connect by level <= regexp_count (SYS_CONTEXT('CTX_ALERT', 'RESTRICT_PRD_CATEGORY'), '[^, ]+')
)
)
)
)
and
(
(
SYS_CONTEXT('CTX_ALERT', 'rptLOGIN_NAME') = '*'
or
(
LOGIN_NAME in
(
select
regexp_substr(SYS_CONTEXT('CTX_ALERT', 'rptLOGIN_NAME'), '[^, ]+',1, rownum) str
from
dual connect by level <= regexp_count (SYS_CONTEXT('CTX_ALERT', 'rptLOGIN_NAME'), '[^, ]+')
)
)
)
AND
(
SYS_CONTEXT('CTX_ALERT', 'RESTRICT_PRD_CATEGORY') = '*'
or
(
LOGIN_NAME in
(
select
regexp_substr(SYS_CONTEXT('CTX_ALERT', 'RESTRICT_PRD_CATEGORY'), '[^, ]+',1, rownum) str
from
dual connect by level <= regexp_count (SYS_CONTEXT('CTX_ALERT', 'RESTRICT_PRD_CATEGORY'), '[^, ]+')
)
)
)
)
group by
VENDOR_ID, PRD_CATEGORY, LOGIN_NAME, CURRENCY
);
However, although I set the parameters properly within the application context, the query always returns no result. I have tried the same query without the group by and the aggregated fields, then I can get the data (of course without any grouping).
If I want to get the "group by" data property, I have to add the dummy statement "rownum >= 0" hack as the last statement in the where clause, i.e.:
CREATE OR REPLACE VIEW VW_SALT_RAW_SUMMARY AS
(
select
VENDOR_ID, PRD_CATEGORY, LOGIN_NAME, CURRENCY /*, Some more aggregated fields*/
from
VENDOR_RECORD
where
(
order_time between SYS_CONTEXT('CTX_ALERT', 'rptBeginTime') and SYS_CONTEXT('CTX_ALERT', 'rptEndTime')
AND order_time between SYS_CONTEXT('CTX_ALERT', 'RESTRICT_BEGIN_TIME') AND SYS_CONTEXT('CTX_ALERT', 'RESTRICT_END_TIME')
)
and
(
(
SYS_CONTEXT('CTX_ALERT', 'rptVENDOR_ID') = '*'
or
(
VENDOR_ID in
(
select
regexp_substr(SYS_CONTEXT('CTX_ALERT', 'rptVENDOR_ID'), '[^, ]+',1, rownum) str
from
dual connect by level <= regexp_count (SYS_CONTEXT('CTX_ALERT', 'rptVENDOR_ID'), '[^, ]+')
)
)
)
AND
(
SYS_CONTEXT('CTX_ALERT', 'RESTRICT_VENDOR_ID') = '*'
or
(
VENDOR_ID in
(
select
regexp_substr(SYS_CONTEXT('CTX_ALERT', 'RESTRICT_VENDOR_ID'), '[^, ]+',1, rownum) str
from
dual connect by level <= regexp_count (SYS_CONTEXT('CTX_ALERT', 'RESTRICT_VENDOR_ID'), '[^, ]+')
)
)
)
)
and
(
(
SYS_CONTEXT('CTX_ALERT', 'rptPRD_CATEGORY') = '*'
or
(
PRD_CATEGORY in
(
select
regexp_substr(SYS_CONTEXT('CTX_ALERT', 'rptPRD_CATEGORY'), '[^, ]+',1, rownum) str
from
dual connect by level <= regexp_count (SYS_CONTEXT('CTX_ALERT', 'rptPRD_CATEGORY'), '[^, ]+')
)
)
)
AND
(
SYS_CONTEXT('CTX_ALERT', 'RESTRICT_PRD_CATEGORY') = '*'
or
(
PRD_CATEGORY in
(
select
regexp_substr(SYS_CONTEXT('CTX_ALERT', 'RESTRICT_PRD_CATEGORY'), '[^, ]+',1, rownum) str
from
dual connect by level <= regexp_count (SYS_CONTEXT('CTX_ALERT', 'RESTRICT_PRD_CATEGORY'), '[^, ]+')
)
)
)
)
and
(
(
SYS_CONTEXT('CTX_ALERT', 'rptLOGIN_NAME') = '*'
or
(
LOGIN_NAME in
(
select
regexp_substr(SYS_CONTEXT('CTX_ALERT', 'rptLOGIN_NAME'), '[^, ]+',1, rownum) str
from
dual connect by level <= regexp_count (SYS_CONTEXT('CTX_ALERT', 'rptLOGIN_NAME'), '[^, ]+')
)
)
)
AND
(
SYS_CONTEXT('CTX_ALERT', 'RESTRICT_PRD_CATEGORY') = '*'
or
(
LOGIN_NAME in
(
select
regexp_substr(SYS_CONTEXT('CTX_ALERT', 'RESTRICT_PRD_CATEGORY'), '[^, ]+',1, rownum) str
from
dual connect by level <= regexp_count (SYS_CONTEXT('CTX_ALERT', 'RESTRICT_PRD_CATEGORY'), '[^, ]+')
)
)
)
)
-- A DIRTY HACK TO MAKE THE QUERY RETURNS DATA
AND ROWNUM >= 0
group by
VENDOR_ID, PRD_CATEGORY, LOGIN_NAME, CURRENCY
)
;
This problem makes me crazy and I would like to ask for the reason behind, thanks.
EDITED:
Some more information about VENDOR_RECORD
Oracle Version: 11g (11.2.0.4.0)
It is a extremely huge archive table (> 10M records per date), and partitioned by "order_time"
It is purposed to be an analytical DB but not a real time transaction DB, hence very real time response are not necessary. On the other hand, the accuracy is important.
Looks weird, but you can debug your code by making some conditions in your view as comment, I believe you will figure out what line causing the problem. Adding "AND rownum > 0" is not enough to debug it, if the query looks complex make it simply first.

split string into several rows

I have a table with a string which contains several delimited values, e.g. a;b;c.
I need to split this string and use its values in a query. For example I have following table:
str
a;b;c
b;c;d
a;c;d
I need to group by a single value from str column to get following result:
str count(*)
a 1
b 2
c 3
d 2
Is it possible to implement using single select query? I can not create temporary tables to extract values there and query against that temporary table.
From your comment to #PrzemyslawKruglej answer
Main problem is with internal query with connect by, it generates astonishing amount of rows
The amount of rows generated can be reduced with the following approach:
/* test table populated with sample data from your question */
SQL> create table t1(str) as(
2 select 'a;b;c' from dual union all
3 select 'b;c;d' from dual union all
4 select 'a;c;d' from dual
5 );
Table created
-- number of rows generated will solely depend on the most longest
-- string.
-- If (say) the longest string contains 3 words (wont count separator `;`)
-- and we have 100 rows in our table, then we will end up with 300 rows
-- for further processing , no more.
with occurrence(ocr) as(
select level
from ( select max(regexp_count(str, '[^;]+')) as mx_t
from t1 ) t
connect by level <= mx_t
)
select count(regexp_substr(t1.str, '[^;]+', 1, o.ocr)) as generated_for_3_rows
from t1
cross join occurrence o;
Result: For three rows where the longest one is made up of three words, we will generate 9 rows:
GENERATED_FOR_3_ROWS
--------------------
9
Final query:
with occurrence(ocr) as(
select level
from ( select max(regexp_count(str, '[^;]+')) as mx_t
from t1 ) t
connect by level <= mx_t
)
select res
, count(res) as cnt
from (select regexp_substr(t1.str, '[^;]+', 1, o.ocr) as res
from t1
cross join occurrence o)
where res is not null
group by res
order by res;
Result:
RES CNT
----- ----------
a 2
b 2
c 3
d 2
SQLFIddle Demo
Find out more about regexp_count()(11g and up) and regexp_substr() regular expression functions.
Note: Regular expression functions relatively expensive to compute, and when it comes to processing a very large amount of data, it might be worth considering to switch to a plain PL/SQL. Here is an example.
This is ugly, but seems to work. The problem with the CONNECT BY splitting is that it returns duplicate rows. I managed to get rid of them, but you'll have to test it:
WITH
data AS (
SELECT 'a;b;c' AS val FROM dual
UNION ALL SELECT 'b;c;d' AS val FROM dual
UNION ALL SELECT 'a;c;d' AS val FROM dual
)
SELECT token, COUNT(1)
FROM (
SELECT DISTINCT token, lvl, val, p_val
FROM (
SELECT
regexp_substr(val, '[^;]+', 1, level) AS token,
level AS lvl,
val,
NVL(prior val, val) p_val
FROM data
CONNECT BY regexp_substr(val, '[^;]+', 1, level) IS NOT NULL
)
WHERE val = p_val
)
GROUP BY token;
TOKEN COUNT(1)
-------------------- ----------
d 2
b 2
a 2
c 3
SELECT NAME,COUNT(NAME) FROM ( SELECT NAME FROM ( (SELECT rownum as ID, REGEXP_SUBSTR('a;b;c', '[^;]+', 1, LEVEL ) NAME
FROM dual CONNECT BY REGEXP_SUBSTR('a;b;c', '[^;]+', 1, LEVEL) IS NOT NULL))
UNION ALL (SELECT NAME FROM ( (SELECT rownum as ID, REGEXP_SUBSTR('b;c;d', '[^;]+', 1, LEVEL ) NAME
FROM dual CONNECT BY REGEXP_SUBSTR('b;c;d', '[^;]+', 1, LEVEL) IS NOT NULL)))
UNION ALL
(SELECT NAME FROM ( (SELECT rownum as ID, REGEXP_SUBSTR('a;c;d', '[^;]+', 1, LEVEL ) NAME
FROM dual CONNECT BY REGEXP_SUBSTR('a;c;d', '[^;]+', 1, LEVEL) IS NOT NULL)))) GROUP BY NAME
NAME COUNT(NAME)
----- -----------
d 2
a 2
b 2
c 3