Here is a start that may give some ideas on the approach. A recursive query starts with a_sno each record, and then tries to follow the b_sno path until it reaches the end or forms a loop. The path is represented by an array of sno integers.
The unnest function splits an array into strings, so the sno value sno mapped to an array of paths, for example:
4, {6, 5, 4}
will be converted to a string for each value in the array:
4, 6 4, 5 4, 4
Then array_agg cancels the operation, aggregating the values ββback to the path, but getting rid of duplicates and ordering.
Now each a_sno is associated with a path, and the path forms a grouping. dense_rank can be used to match groupings (clusters) to numbers.
SELECT array_agg(DISTINCT map ORDER BY map) AS cluster ,sno FROM ( WITH RECURSIVE x(sno, path, cycle) AS ( SELECT a_sno, ARRAY[a_sno], false FROM data UNION ALL SELECT b_sno, path || b_sno, b_sno = ANY(path) FROM data, x WHERE a_sno = x.sno AND NOT cycle ) SELECT sno, unnest(path) AS map FROM x ORDER BY 1 ) y GROUP BY sno ORDER BY 1, 2
Conclusion:
cluster | sno --------------+----- {4,5,6,7} | 4 {4,5,6,7} | 5 {4,5,6,7} | 6 {4,5,6,7} | 7 {9,10,13,14} | 9 {9,10,13,14} | 10 {9,10,13,14} | 13 {9,10,13,14} | 14 {11,15} | 11 {11,15} | 15 (10 rows)
Wrap it again for ranking:
SELECT dense_rank() OVER(order by cluster) AS rank ,sno FROM ( SELECT array_agg(DISTINCT map ORDER BY map) AS cluster ,sno FROM ( WITH RECURSIVE x(sno, path, cycle) AS ( SELECT a_sno, ARRAY[a_sno], false FROM data UNION ALL SELECT b_sno, path || b_sno, b_sno = ANY(path) FROM data, x WHERE a_sno = x.sno AND NOT cycle ) SELECT sno, unnest(path) AS map FROM x ORDER BY 1 ) y GROUP BY sno ORDER BY 1, 2 ) z
Conclusion:
rank | sno ------+----- 1 | 4 1 | 5 1 | 6 1 | 7 2 | 9 2 | 10 2 | 13 2 | 14 3 | 11 3 | 15 (10 rows)