I have a solution, though it's probably really costly for large data sets.
It's also hard to explain, but I'll try...
First, look at how we can combine groups base on "person" alone:
PersonA is a member of Groups {1, 2, 3}
Group2 can therefore be combined in to Group1
AND
Group3 can therefore be combined in to Group1
PersonB is a member of Groups {2, 3, 4}
Group3 can therefore be combined in to Group2
AND
Group4 can therefore be combined in to Group2
PersonC is a member of Groups {3, 4, 5}
Group4 can therefore be combined in to Group3
AND
Group5 can therefore be combined in to Group3
This gives us...
From PersonA : (G2 → G1) and (G3 → G1)
From PersonB : (G3 → G2) and (G4 → G2)
From PersonC : (G4 → G3) and (G5 → G3)
Then look at it "vertically", such that if a group can be combined in to more than one other group, take the lowest option.
Overall : (G2 → G1) and (G3 → G1) and (G4 → G2) and (G5 → G3)
If we apply those "combining of groups" to the original data we get this...
| person | original groups | revised groups |
+--------+-----------------+------------------------------+
| A | {1, 2, 3} | {1, 1, 1} => {1} |
| B | {2, 3, 4} | {1, 1, 2} => {1, 2} |
| C | {3, 4, 5} | {1, 2, 3} => {1, 2, 3} |
So, we've gone from 5 distinct groups, to three distinct groups.
If we repeat that process again, starting from the revised groups, all three people end up as members of just Group1.
The number of times you need to repeat this depends on how many groups overlap each other and in what way.
The following code should be able to apply that process as many times as is necessary. It keeps going until each person is a member of just one group. There may still be more than one group at the end, they're the groups which don't overlap with each other.
That may take many iterations, dealing with every person every time, which may be slow, especially on larger datasets.
But, I think, it works for all cases. (Even though you probably shouldn't do this in SQL, at least now you can.)
Demo including test data : dbfiddle.uk demo
WITH
RECURSIVE "membership"
AS
(
SELECT
"name",
"group_id",
(SELECT COUNT(DISTINCT "name") FROM "name_group_links") AS unique_names,
0 AS current_depth,
COUNT(*) OVER () AS current_links
FROM
"name_group_links"
UNION ALL
SELECT
*,
COUNT(*) OVER () AS "current_links"
FROM
(
SELECT DISTINCT
"name",
MIN("min_group_by_name") OVER (PARTITION BY "group_id") AS "group",
"unique_names",
"current_depth" + 1 AS "current_depth"
FROM
(
SELECT
*,
MIN("group_id") OVER (PARTITION BY "name") AS "min_group_by_name"
FROM
"membership"
WHERE
"current_links" > "unique_names"
)
AS "collapse_groups_by_name"
)
AS "collapse_groups_by_group"
)
SELECT
"current_depth",
"name",
"group_id"
FROM
"membership"
WHERE
"current_links" = "unique_names"
ORDER BY
"current_depth",
"name",
"group_id"
;