Reputation: 383
I have table like this:
ID1 | ID2
==========
A | B
C | B
A | F
G | B
G | E
J | B
X | Y
Z | Y
I have to write SQL SELECT that find(marks) separate groups. In this example I have two groups: A,B,C,F,G,E,J and X,Y,Z. It is like all that have path(without direction) between them(like Family relation).There is no matter it will be string(with LISTAGG or with path (CONNECT BY or CYCLE) for example) or new column with group identifiers like
ID1 | ID2 | GROUP
=================
A | B | 1
C | B | 1
A | F | 1
G | B | 1
G | E | 1
J | B | 1
X | Y | 2
Z | Y | 2
If there is one more row with [A,Z] then all distinct letters are one group. I think its have to something with CONNECT BY or CYCLE but I don't know how it have to looks like.
Upvotes: 0
Views: 105
Reputation: 14848
I think You could use connect_by_root
here:
select id1, listagg(root) within group (order by root) list
from (select distinct id1, root
from (select t.*, connect_by_root(id1) root
from (select id1, id2 from t union select id2, id1 from t) t
connect by nocycle prior id1 = id2))
group by id1
... gives us:
A ABCEFGJ
B ABCEFGJ
C ABCEFGJ
E ABCEFGJ
F ABCEFGJ
G ABCEFGJ
J ABCEFGJ
X XYZ
Y XYZ
Z XYZ
Rest is simple, use dense_rank()
and join with original table:
with t(id1, id2) as (
select 'A', 'B' from dual union all
select 'C', 'B' from dual union all
select 'A', 'F' from dual union all
select 'G', 'B' from dual union all
select 'G', 'E' from dual union all
select 'J', 'B' from dual union all
select 'X', 'Y' from dual union all
select 'Z', 'Y' from dual),
q as (
select id1, listagg(root) within group (order by root) list
from (select distinct id1, root
from (select t.*, connect_by_root(id1) root
from (select id1, id2 from t union select id2, id1 from t) t
connect by nocycle prior id1 = id2))
group by id1)
select id1, id2, dense_rank() over (order by list) grp
from t join q using (id1)
Result:
ID1 ID2 GRP
--- --- ----------
A F 1
A B 1
C B 1
G E 1
J B 1
G B 1
X Y 2
Z Y 2
8 rows selected
Upvotes: 1
Reputation: 168051
Adapted from my answer here:
Oracle 11g R2 Schema Setup:
CREATE TABLE ids ( ID1, ID2 ) AS
SELECT 'A', 'B' FROM DUAL UNION ALL
SELECT 'C', 'B' FROM DUAL UNION ALL
SELECT 'A', 'F' FROM DUAL UNION ALL
SELECT 'G', 'B' FROM DUAL UNION ALL
SELECT 'G', 'E' FROM DUAL UNION ALL
SELECT 'J', 'B' FROM DUAL UNION ALL
SELECT 'X', 'Y' FROM DUAL UNION ALL
SELECT 'Z', 'Y' FROM DUAL;
Query 1:
WITH indexed_ids ( id, id1, id2 ) AS (
SELECT ROWNUM, id1, id2 FROM ids
),
grouped_ids ( id, id1, id2, min_id ) AS (
SELECT i.*,
LEAST(
MIN( id ) OVER ( PARTITION BY id1 ),
MIN( id ) OVER ( PARTITION BY id2 )
) AS min_id
FROM indexed_ids i
)
SELECT id, id1, id2,
MIN( "GROUP" ) AS "GROUP"
FROM (
SELECT id, id1, id2,
CONNECT_BY_ROOT( id ) AS "GROUP"
FROM grouped_ids g
START WITH id = min_id
CONNECT BY NOCYCLE ( PRIOR id1 IN ( id1, id2 ) OR PRIOR id2 IN ( id1, id2 ) )
)
GROUP BY id, id1, id2
ORDER BY id
| ID | ID1 | ID2 | GROUP |
|----|-----|-----|-------|
| 1 | A | B | 1 |
| 2 | C | B | 1 |
| 3 | A | F | 1 |
| 4 | G | B | 1 |
| 5 | G | E | 1 |
| 6 | J | B | 1 |
| 7 | X | Y | 7 |
| 8 | Z | Y | 7 |
The GROUP
column identifies the minimum ID
of the group. If you want a sequential GROUP
then you can use the DENSE_RANK()
analytic function.
Upvotes: 1