How to group nodes with relationships in SQL











up vote
1
down vote

favorite












I have the following table which lists related nodes:



;WITH CTE AS
( SELECT *
FROM (VALUES (1,2)
,(2,1)
,(3,4)
,(3,5)
,(4,3)
,(4,5)
,(5,3)
,(5,4)
,(6,NULL)
,(7,NULL)
,(8,9)
,(9,8)
) AS ValuesTable(ID,RelatedID)
)
SELECT *
FROM CTE


How can I assign unique IDS (GUID or integer GroupID) to each group, So, 1 and 2 will be on the same group, 3, 4, 5 on a different group, 6 is alone in it's group and so is 7, and 8 and 9 are one more group?



My answer so far seems very cumbersome:



;WITH CTE AS
( SELECT *
FROM (VALUES (1,2)
,(2,1)
,(3,4)
,(3,5)
,(4,3)
,(4,5)
,(5,3)
,(5,4)
,(6,NULL)
,(7,NULL)
,(8,9)
,(9,8)
) AS ValuesTable(ID,RelatedID)
)
SELECT DENSE_RANK() OVER(ORDER BY CA.IDList) AS GroupID,
ID,
RelatedID
FROM CTE
CROSS APPLY (SELECT STUFF((SELECT ',' + CONVERT(NVARCHAR(255), ID)
FROM CTE AS CTEInner
WHERE CTEInner.ID = CTE.ID
OR CTEInner.ID = CTE.RelatedID
OR CTEInner.RelatedID = CTE.RelatedID
OR CTEInner.RelatedID = CTE.ID
FOR XML PATH(''),TYPE).value('(./text())[1]','NVARCHAR(MAX)'),1,1,'') AS IDList) AS CA


But it provides the correct answer:



GroupID ID  RelatedID
1 1 2
1 2 1
2 3 4
2 3 5
2 4 3
2 4 5
2 5 3
2 5 4
3 6 NULL
4 7 NULL
5 8 9
5 9 8









share|improve this question
























  • I am sure there is something more complex required than just using the NewID() function (Assumed sql server due to tsql tag) - can you show us what the expected result is at least? (A non-cycle tree walk to collate the groups?)
    – Andrew
    Nov 20 at 9:37












  • So, 1 and 2 will be on the same group, 3, 4, 5 on a different group, 6 is alone in it's group and so is 7, and 8 and 9 are one more group, is that correct?
    – Zohar Peled
    Nov 20 at 9:46










  • @Zohar, Yes, thats correct
    – Danny Rancher
    Nov 20 at 9:46










  • @Andrew i think its less complex than that. The groups are separated. Every ID in each group is Related to every other ID within the group.
    – Danny Rancher
    Nov 20 at 10:00










  • Does it have to be a guid? Because I can show you how to add a unique number for each group, but adding a guid will make it more cumbersome.
    – Zohar Peled
    Nov 20 at 10:13















up vote
1
down vote

favorite












I have the following table which lists related nodes:



;WITH CTE AS
( SELECT *
FROM (VALUES (1,2)
,(2,1)
,(3,4)
,(3,5)
,(4,3)
,(4,5)
,(5,3)
,(5,4)
,(6,NULL)
,(7,NULL)
,(8,9)
,(9,8)
) AS ValuesTable(ID,RelatedID)
)
SELECT *
FROM CTE


How can I assign unique IDS (GUID or integer GroupID) to each group, So, 1 and 2 will be on the same group, 3, 4, 5 on a different group, 6 is alone in it's group and so is 7, and 8 and 9 are one more group?



My answer so far seems very cumbersome:



;WITH CTE AS
( SELECT *
FROM (VALUES (1,2)
,(2,1)
,(3,4)
,(3,5)
,(4,3)
,(4,5)
,(5,3)
,(5,4)
,(6,NULL)
,(7,NULL)
,(8,9)
,(9,8)
) AS ValuesTable(ID,RelatedID)
)
SELECT DENSE_RANK() OVER(ORDER BY CA.IDList) AS GroupID,
ID,
RelatedID
FROM CTE
CROSS APPLY (SELECT STUFF((SELECT ',' + CONVERT(NVARCHAR(255), ID)
FROM CTE AS CTEInner
WHERE CTEInner.ID = CTE.ID
OR CTEInner.ID = CTE.RelatedID
OR CTEInner.RelatedID = CTE.RelatedID
OR CTEInner.RelatedID = CTE.ID
FOR XML PATH(''),TYPE).value('(./text())[1]','NVARCHAR(MAX)'),1,1,'') AS IDList) AS CA


But it provides the correct answer:



GroupID ID  RelatedID
1 1 2
1 2 1
2 3 4
2 3 5
2 4 3
2 4 5
2 5 3
2 5 4
3 6 NULL
4 7 NULL
5 8 9
5 9 8









share|improve this question
























  • I am sure there is something more complex required than just using the NewID() function (Assumed sql server due to tsql tag) - can you show us what the expected result is at least? (A non-cycle tree walk to collate the groups?)
    – Andrew
    Nov 20 at 9:37












  • So, 1 and 2 will be on the same group, 3, 4, 5 on a different group, 6 is alone in it's group and so is 7, and 8 and 9 are one more group, is that correct?
    – Zohar Peled
    Nov 20 at 9:46










  • @Zohar, Yes, thats correct
    – Danny Rancher
    Nov 20 at 9:46










  • @Andrew i think its less complex than that. The groups are separated. Every ID in each group is Related to every other ID within the group.
    – Danny Rancher
    Nov 20 at 10:00










  • Does it have to be a guid? Because I can show you how to add a unique number for each group, but adding a guid will make it more cumbersome.
    – Zohar Peled
    Nov 20 at 10:13













up vote
1
down vote

favorite









up vote
1
down vote

favorite











I have the following table which lists related nodes:



;WITH CTE AS
( SELECT *
FROM (VALUES (1,2)
,(2,1)
,(3,4)
,(3,5)
,(4,3)
,(4,5)
,(5,3)
,(5,4)
,(6,NULL)
,(7,NULL)
,(8,9)
,(9,8)
) AS ValuesTable(ID,RelatedID)
)
SELECT *
FROM CTE


How can I assign unique IDS (GUID or integer GroupID) to each group, So, 1 and 2 will be on the same group, 3, 4, 5 on a different group, 6 is alone in it's group and so is 7, and 8 and 9 are one more group?



My answer so far seems very cumbersome:



;WITH CTE AS
( SELECT *
FROM (VALUES (1,2)
,(2,1)
,(3,4)
,(3,5)
,(4,3)
,(4,5)
,(5,3)
,(5,4)
,(6,NULL)
,(7,NULL)
,(8,9)
,(9,8)
) AS ValuesTable(ID,RelatedID)
)
SELECT DENSE_RANK() OVER(ORDER BY CA.IDList) AS GroupID,
ID,
RelatedID
FROM CTE
CROSS APPLY (SELECT STUFF((SELECT ',' + CONVERT(NVARCHAR(255), ID)
FROM CTE AS CTEInner
WHERE CTEInner.ID = CTE.ID
OR CTEInner.ID = CTE.RelatedID
OR CTEInner.RelatedID = CTE.RelatedID
OR CTEInner.RelatedID = CTE.ID
FOR XML PATH(''),TYPE).value('(./text())[1]','NVARCHAR(MAX)'),1,1,'') AS IDList) AS CA


But it provides the correct answer:



GroupID ID  RelatedID
1 1 2
1 2 1
2 3 4
2 3 5
2 4 3
2 4 5
2 5 3
2 5 4
3 6 NULL
4 7 NULL
5 8 9
5 9 8









share|improve this question















I have the following table which lists related nodes:



;WITH CTE AS
( SELECT *
FROM (VALUES (1,2)
,(2,1)
,(3,4)
,(3,5)
,(4,3)
,(4,5)
,(5,3)
,(5,4)
,(6,NULL)
,(7,NULL)
,(8,9)
,(9,8)
) AS ValuesTable(ID,RelatedID)
)
SELECT *
FROM CTE


How can I assign unique IDS (GUID or integer GroupID) to each group, So, 1 and 2 will be on the same group, 3, 4, 5 on a different group, 6 is alone in it's group and so is 7, and 8 and 9 are one more group?



My answer so far seems very cumbersome:



;WITH CTE AS
( SELECT *
FROM (VALUES (1,2)
,(2,1)
,(3,4)
,(3,5)
,(4,3)
,(4,5)
,(5,3)
,(5,4)
,(6,NULL)
,(7,NULL)
,(8,9)
,(9,8)
) AS ValuesTable(ID,RelatedID)
)
SELECT DENSE_RANK() OVER(ORDER BY CA.IDList) AS GroupID,
ID,
RelatedID
FROM CTE
CROSS APPLY (SELECT STUFF((SELECT ',' + CONVERT(NVARCHAR(255), ID)
FROM CTE AS CTEInner
WHERE CTEInner.ID = CTE.ID
OR CTEInner.ID = CTE.RelatedID
OR CTEInner.RelatedID = CTE.RelatedID
OR CTEInner.RelatedID = CTE.ID
FOR XML PATH(''),TYPE).value('(./text())[1]','NVARCHAR(MAX)'),1,1,'') AS IDList) AS CA


But it provides the correct answer:



GroupID ID  RelatedID
1 1 2
1 2 1
2 3 4
2 3 5
2 4 3
2 4 5
2 5 3
2 5 4
3 6 NULL
4 7 NULL
5 8 9
5 9 8






sql tsql grouping






share|improve this question















share|improve this question













share|improve this question




share|improve this question








edited Nov 20 at 10:52

























asked Nov 20 at 9:34









Danny Rancher

86321431




86321431












  • I am sure there is something more complex required than just using the NewID() function (Assumed sql server due to tsql tag) - can you show us what the expected result is at least? (A non-cycle tree walk to collate the groups?)
    – Andrew
    Nov 20 at 9:37












  • So, 1 and 2 will be on the same group, 3, 4, 5 on a different group, 6 is alone in it's group and so is 7, and 8 and 9 are one more group, is that correct?
    – Zohar Peled
    Nov 20 at 9:46










  • @Zohar, Yes, thats correct
    – Danny Rancher
    Nov 20 at 9:46










  • @Andrew i think its less complex than that. The groups are separated. Every ID in each group is Related to every other ID within the group.
    – Danny Rancher
    Nov 20 at 10:00










  • Does it have to be a guid? Because I can show you how to add a unique number for each group, but adding a guid will make it more cumbersome.
    – Zohar Peled
    Nov 20 at 10:13


















  • I am sure there is something more complex required than just using the NewID() function (Assumed sql server due to tsql tag) - can you show us what the expected result is at least? (A non-cycle tree walk to collate the groups?)
    – Andrew
    Nov 20 at 9:37












  • So, 1 and 2 will be on the same group, 3, 4, 5 on a different group, 6 is alone in it's group and so is 7, and 8 and 9 are one more group, is that correct?
    – Zohar Peled
    Nov 20 at 9:46










  • @Zohar, Yes, thats correct
    – Danny Rancher
    Nov 20 at 9:46










  • @Andrew i think its less complex than that. The groups are separated. Every ID in each group is Related to every other ID within the group.
    – Danny Rancher
    Nov 20 at 10:00










  • Does it have to be a guid? Because I can show you how to add a unique number for each group, but adding a guid will make it more cumbersome.
    – Zohar Peled
    Nov 20 at 10:13
















I am sure there is something more complex required than just using the NewID() function (Assumed sql server due to tsql tag) - can you show us what the expected result is at least? (A non-cycle tree walk to collate the groups?)
– Andrew
Nov 20 at 9:37






I am sure there is something more complex required than just using the NewID() function (Assumed sql server due to tsql tag) - can you show us what the expected result is at least? (A non-cycle tree walk to collate the groups?)
– Andrew
Nov 20 at 9:37














So, 1 and 2 will be on the same group, 3, 4, 5 on a different group, 6 is alone in it's group and so is 7, and 8 and 9 are one more group, is that correct?
– Zohar Peled
Nov 20 at 9:46




So, 1 and 2 will be on the same group, 3, 4, 5 on a different group, 6 is alone in it's group and so is 7, and 8 and 9 are one more group, is that correct?
– Zohar Peled
Nov 20 at 9:46












@Zohar, Yes, thats correct
– Danny Rancher
Nov 20 at 9:46




@Zohar, Yes, thats correct
– Danny Rancher
Nov 20 at 9:46












@Andrew i think its less complex than that. The groups are separated. Every ID in each group is Related to every other ID within the group.
– Danny Rancher
Nov 20 at 10:00




@Andrew i think its less complex than that. The groups are separated. Every ID in each group is Related to every other ID within the group.
– Danny Rancher
Nov 20 at 10:00












Does it have to be a guid? Because I can show you how to add a unique number for each group, but adding a guid will make it more cumbersome.
– Zohar Peled
Nov 20 at 10:13




Does it have to be a guid? Because I can show you how to add a unique number for each group, but adding a guid will make it more cumbersome.
– Zohar Peled
Nov 20 at 10:13












2 Answers
2






active

oldest

votes

















up vote
1
down vote













Adding a unique number for each group is not hard but it does require a few steps.



The first step would be to select unique values for the groups - so for instance the group with (1, 2) and (2, 1) will contain only a single record - (1, 2).



The next step is to get rid of the records that creates multiple paths for the same relationship - in this case - (3, 4), (4, 5), (3, 5) - means that 5 is the related to both 3 and 4, but for the recursive cte to work, we only need a single relationship path - so either (3, 4), (4, 5) or (3, 4), (3, 5) but not both.



The next step is to create a recursive cte based on these unique values, so that each group can get it's unique number.



After that, you can select from the original cte joined to the recursive cte and get the unique group numbers:



;WITH CTE AS
( SELECT *
FROM (VALUES (1,2)
,(2,1)
,(3,4)
,(3,5)
,(4,3)
,(4,5)
,(5,3)
,(5,4)
,(6,NULL)
,(7,NULL)
,(8,9)
,(9,8)
) AS ValuesTable(ID,RelatedID)
)
, PreUniqueValues AS
(
SELECT MIN(ID) AS ID,
MAX(RelatedID) As RelatedID
FROM CTE AS B
GROUP BY (ID + ISNULL(RelatedID, 0)) + (ID * ISNULL(RelatedID, 0))
)
, UniqueValues AS
(
SELECT ID, MIN(RelatedID) As RelatedID
FROM PreUniqueValues
GROUP BY ID
)
, Recursive AS
(
SELECT ID, RelatedId, DENSE_RANK() OVER(ORDER BY ID) As GroupID
FROM UniqueValues AS T0
WHERE NOT EXISTS
(
SELECT 1
FROM UniqueValues AS T1
WHERE T1.ID = T0.RelatedID
)
UNION ALL
SELECT UV.ID, UV.RelatedID, GroupID
FROM UniqueValues As UV
JOIN Recursive As Re
ON UV.ID = Re.RelatedId
)

SELECT CTE.ID, CTE.RelatedID, GroupID
FROM CTE
JOIN Recursive
ON CTE.ID = Recursive.ID OR CTE.ID = ISNULL(Recursive.RelatedID, 0)
ORDER BY ID


Results:



ID  RelatedID   GroupID
1 2 1
2 1 1
4 3 2
4 5 2
5 3 2
5 4 2
6 NULL 3
7 NULL 4
8 9 5
9 8 5





share|improve this answer























  • Thanks for answering, but shouldn't there only be 5 groups (1,2), (3,4,5), (6), (7), (8,9)
    – Danny Rancher
    Nov 20 at 10:41










  • The problem is that RelatedID 5 appears both with 3 and with 4 as it's anchor. Back to the drawing board...
    – Zohar Peled
    Nov 20 at 11:12










  • @DannyRancher I've edited my answer with a correct solution.
    – Zohar Peled
    Nov 20 at 11:47


















up vote
1
down vote













This is a graph-walking problem and you would seem to need recursive CTEs. The logic looks like this:



WITH t AS (
SELECT *
FROM (VALUES (1,2)
,(2,1)
,(3,4)
,(3,5)
,(4,3)
,(4,5)
,(5,3)
,(5,4)
,(6,NULL)
,(7,NULL)
,(8,9)
,(9,8)
) AS ValuesTable(ID,RelatedID)
),
cte as (
select distinct id, id as relatedId, ',' + convert(varchar(max), id) + ',' as relatedIds
from t
union all
select cte.id, t.relatedId, cte.relatedIds + convert(varchar(max), t.relatedId) + ','
from cte join
t
on cte.relatedId = t.id
where cte.relatedId is not null and
cte.relatedIds not like '%,' + convert(varchar(max), t.relatedId) + ',%'
)
SELECT id, min(relatedId) as grp,
dense_rank() over (order by min(relatedId)) as grp_number
FROM cte
GROUP BY id;


Here is a db<>fiddle.






share|improve this answer





















    Your Answer






    StackExchange.ifUsing("editor", function () {
    StackExchange.using("externalEditor", function () {
    StackExchange.using("snippets", function () {
    StackExchange.snippets.init();
    });
    });
    }, "code-snippets");

    StackExchange.ready(function() {
    var channelOptions = {
    tags: "".split(" "),
    id: "1"
    };
    initTagRenderer("".split(" "), "".split(" "), channelOptions);

    StackExchange.using("externalEditor", function() {
    // Have to fire editor after snippets, if snippets enabled
    if (StackExchange.settings.snippets.snippetsEnabled) {
    StackExchange.using("snippets", function() {
    createEditor();
    });
    }
    else {
    createEditor();
    }
    });

    function createEditor() {
    StackExchange.prepareEditor({
    heartbeatType: 'answer',
    convertImagesToLinks: true,
    noModals: true,
    showLowRepImageUploadWarning: true,
    reputationToPostImages: 10,
    bindNavPrevention: true,
    postfix: "",
    imageUploader: {
    brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
    contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
    allowUrls: true
    },
    onDemand: true,
    discardSelector: ".discard-answer"
    ,immediatelyShowMarkdownHelp:true
    });


    }
    });














    draft saved

    draft discarded


















    StackExchange.ready(
    function () {
    StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53389995%2fhow-to-group-nodes-with-relationships-in-sql%23new-answer', 'question_page');
    }
    );

    Post as a guest















    Required, but never shown

























    2 Answers
    2






    active

    oldest

    votes








    2 Answers
    2






    active

    oldest

    votes









    active

    oldest

    votes






    active

    oldest

    votes








    up vote
    1
    down vote













    Adding a unique number for each group is not hard but it does require a few steps.



    The first step would be to select unique values for the groups - so for instance the group with (1, 2) and (2, 1) will contain only a single record - (1, 2).



    The next step is to get rid of the records that creates multiple paths for the same relationship - in this case - (3, 4), (4, 5), (3, 5) - means that 5 is the related to both 3 and 4, but for the recursive cte to work, we only need a single relationship path - so either (3, 4), (4, 5) or (3, 4), (3, 5) but not both.



    The next step is to create a recursive cte based on these unique values, so that each group can get it's unique number.



    After that, you can select from the original cte joined to the recursive cte and get the unique group numbers:



    ;WITH CTE AS
    ( SELECT *
    FROM (VALUES (1,2)
    ,(2,1)
    ,(3,4)
    ,(3,5)
    ,(4,3)
    ,(4,5)
    ,(5,3)
    ,(5,4)
    ,(6,NULL)
    ,(7,NULL)
    ,(8,9)
    ,(9,8)
    ) AS ValuesTable(ID,RelatedID)
    )
    , PreUniqueValues AS
    (
    SELECT MIN(ID) AS ID,
    MAX(RelatedID) As RelatedID
    FROM CTE AS B
    GROUP BY (ID + ISNULL(RelatedID, 0)) + (ID * ISNULL(RelatedID, 0))
    )
    , UniqueValues AS
    (
    SELECT ID, MIN(RelatedID) As RelatedID
    FROM PreUniqueValues
    GROUP BY ID
    )
    , Recursive AS
    (
    SELECT ID, RelatedId, DENSE_RANK() OVER(ORDER BY ID) As GroupID
    FROM UniqueValues AS T0
    WHERE NOT EXISTS
    (
    SELECT 1
    FROM UniqueValues AS T1
    WHERE T1.ID = T0.RelatedID
    )
    UNION ALL
    SELECT UV.ID, UV.RelatedID, GroupID
    FROM UniqueValues As UV
    JOIN Recursive As Re
    ON UV.ID = Re.RelatedId
    )

    SELECT CTE.ID, CTE.RelatedID, GroupID
    FROM CTE
    JOIN Recursive
    ON CTE.ID = Recursive.ID OR CTE.ID = ISNULL(Recursive.RelatedID, 0)
    ORDER BY ID


    Results:



    ID  RelatedID   GroupID
    1 2 1
    2 1 1
    4 3 2
    4 5 2
    5 3 2
    5 4 2
    6 NULL 3
    7 NULL 4
    8 9 5
    9 8 5





    share|improve this answer























    • Thanks for answering, but shouldn't there only be 5 groups (1,2), (3,4,5), (6), (7), (8,9)
      – Danny Rancher
      Nov 20 at 10:41










    • The problem is that RelatedID 5 appears both with 3 and with 4 as it's anchor. Back to the drawing board...
      – Zohar Peled
      Nov 20 at 11:12










    • @DannyRancher I've edited my answer with a correct solution.
      – Zohar Peled
      Nov 20 at 11:47















    up vote
    1
    down vote













    Adding a unique number for each group is not hard but it does require a few steps.



    The first step would be to select unique values for the groups - so for instance the group with (1, 2) and (2, 1) will contain only a single record - (1, 2).



    The next step is to get rid of the records that creates multiple paths for the same relationship - in this case - (3, 4), (4, 5), (3, 5) - means that 5 is the related to both 3 and 4, but for the recursive cte to work, we only need a single relationship path - so either (3, 4), (4, 5) or (3, 4), (3, 5) but not both.



    The next step is to create a recursive cte based on these unique values, so that each group can get it's unique number.



    After that, you can select from the original cte joined to the recursive cte and get the unique group numbers:



    ;WITH CTE AS
    ( SELECT *
    FROM (VALUES (1,2)
    ,(2,1)
    ,(3,4)
    ,(3,5)
    ,(4,3)
    ,(4,5)
    ,(5,3)
    ,(5,4)
    ,(6,NULL)
    ,(7,NULL)
    ,(8,9)
    ,(9,8)
    ) AS ValuesTable(ID,RelatedID)
    )
    , PreUniqueValues AS
    (
    SELECT MIN(ID) AS ID,
    MAX(RelatedID) As RelatedID
    FROM CTE AS B
    GROUP BY (ID + ISNULL(RelatedID, 0)) + (ID * ISNULL(RelatedID, 0))
    )
    , UniqueValues AS
    (
    SELECT ID, MIN(RelatedID) As RelatedID
    FROM PreUniqueValues
    GROUP BY ID
    )
    , Recursive AS
    (
    SELECT ID, RelatedId, DENSE_RANK() OVER(ORDER BY ID) As GroupID
    FROM UniqueValues AS T0
    WHERE NOT EXISTS
    (
    SELECT 1
    FROM UniqueValues AS T1
    WHERE T1.ID = T0.RelatedID
    )
    UNION ALL
    SELECT UV.ID, UV.RelatedID, GroupID
    FROM UniqueValues As UV
    JOIN Recursive As Re
    ON UV.ID = Re.RelatedId
    )

    SELECT CTE.ID, CTE.RelatedID, GroupID
    FROM CTE
    JOIN Recursive
    ON CTE.ID = Recursive.ID OR CTE.ID = ISNULL(Recursive.RelatedID, 0)
    ORDER BY ID


    Results:



    ID  RelatedID   GroupID
    1 2 1
    2 1 1
    4 3 2
    4 5 2
    5 3 2
    5 4 2
    6 NULL 3
    7 NULL 4
    8 9 5
    9 8 5





    share|improve this answer























    • Thanks for answering, but shouldn't there only be 5 groups (1,2), (3,4,5), (6), (7), (8,9)
      – Danny Rancher
      Nov 20 at 10:41










    • The problem is that RelatedID 5 appears both with 3 and with 4 as it's anchor. Back to the drawing board...
      – Zohar Peled
      Nov 20 at 11:12










    • @DannyRancher I've edited my answer with a correct solution.
      – Zohar Peled
      Nov 20 at 11:47













    up vote
    1
    down vote










    up vote
    1
    down vote









    Adding a unique number for each group is not hard but it does require a few steps.



    The first step would be to select unique values for the groups - so for instance the group with (1, 2) and (2, 1) will contain only a single record - (1, 2).



    The next step is to get rid of the records that creates multiple paths for the same relationship - in this case - (3, 4), (4, 5), (3, 5) - means that 5 is the related to both 3 and 4, but for the recursive cte to work, we only need a single relationship path - so either (3, 4), (4, 5) or (3, 4), (3, 5) but not both.



    The next step is to create a recursive cte based on these unique values, so that each group can get it's unique number.



    After that, you can select from the original cte joined to the recursive cte and get the unique group numbers:



    ;WITH CTE AS
    ( SELECT *
    FROM (VALUES (1,2)
    ,(2,1)
    ,(3,4)
    ,(3,5)
    ,(4,3)
    ,(4,5)
    ,(5,3)
    ,(5,4)
    ,(6,NULL)
    ,(7,NULL)
    ,(8,9)
    ,(9,8)
    ) AS ValuesTable(ID,RelatedID)
    )
    , PreUniqueValues AS
    (
    SELECT MIN(ID) AS ID,
    MAX(RelatedID) As RelatedID
    FROM CTE AS B
    GROUP BY (ID + ISNULL(RelatedID, 0)) + (ID * ISNULL(RelatedID, 0))
    )
    , UniqueValues AS
    (
    SELECT ID, MIN(RelatedID) As RelatedID
    FROM PreUniqueValues
    GROUP BY ID
    )
    , Recursive AS
    (
    SELECT ID, RelatedId, DENSE_RANK() OVER(ORDER BY ID) As GroupID
    FROM UniqueValues AS T0
    WHERE NOT EXISTS
    (
    SELECT 1
    FROM UniqueValues AS T1
    WHERE T1.ID = T0.RelatedID
    )
    UNION ALL
    SELECT UV.ID, UV.RelatedID, GroupID
    FROM UniqueValues As UV
    JOIN Recursive As Re
    ON UV.ID = Re.RelatedId
    )

    SELECT CTE.ID, CTE.RelatedID, GroupID
    FROM CTE
    JOIN Recursive
    ON CTE.ID = Recursive.ID OR CTE.ID = ISNULL(Recursive.RelatedID, 0)
    ORDER BY ID


    Results:



    ID  RelatedID   GroupID
    1 2 1
    2 1 1
    4 3 2
    4 5 2
    5 3 2
    5 4 2
    6 NULL 3
    7 NULL 4
    8 9 5
    9 8 5





    share|improve this answer














    Adding a unique number for each group is not hard but it does require a few steps.



    The first step would be to select unique values for the groups - so for instance the group with (1, 2) and (2, 1) will contain only a single record - (1, 2).



    The next step is to get rid of the records that creates multiple paths for the same relationship - in this case - (3, 4), (4, 5), (3, 5) - means that 5 is the related to both 3 and 4, but for the recursive cte to work, we only need a single relationship path - so either (3, 4), (4, 5) or (3, 4), (3, 5) but not both.



    The next step is to create a recursive cte based on these unique values, so that each group can get it's unique number.



    After that, you can select from the original cte joined to the recursive cte and get the unique group numbers:



    ;WITH CTE AS
    ( SELECT *
    FROM (VALUES (1,2)
    ,(2,1)
    ,(3,4)
    ,(3,5)
    ,(4,3)
    ,(4,5)
    ,(5,3)
    ,(5,4)
    ,(6,NULL)
    ,(7,NULL)
    ,(8,9)
    ,(9,8)
    ) AS ValuesTable(ID,RelatedID)
    )
    , PreUniqueValues AS
    (
    SELECT MIN(ID) AS ID,
    MAX(RelatedID) As RelatedID
    FROM CTE AS B
    GROUP BY (ID + ISNULL(RelatedID, 0)) + (ID * ISNULL(RelatedID, 0))
    )
    , UniqueValues AS
    (
    SELECT ID, MIN(RelatedID) As RelatedID
    FROM PreUniqueValues
    GROUP BY ID
    )
    , Recursive AS
    (
    SELECT ID, RelatedId, DENSE_RANK() OVER(ORDER BY ID) As GroupID
    FROM UniqueValues AS T0
    WHERE NOT EXISTS
    (
    SELECT 1
    FROM UniqueValues AS T1
    WHERE T1.ID = T0.RelatedID
    )
    UNION ALL
    SELECT UV.ID, UV.RelatedID, GroupID
    FROM UniqueValues As UV
    JOIN Recursive As Re
    ON UV.ID = Re.RelatedId
    )

    SELECT CTE.ID, CTE.RelatedID, GroupID
    FROM CTE
    JOIN Recursive
    ON CTE.ID = Recursive.ID OR CTE.ID = ISNULL(Recursive.RelatedID, 0)
    ORDER BY ID


    Results:



    ID  RelatedID   GroupID
    1 2 1
    2 1 1
    4 3 2
    4 5 2
    5 3 2
    5 4 2
    6 NULL 3
    7 NULL 4
    8 9 5
    9 8 5






    share|improve this answer














    share|improve this answer



    share|improve this answer








    edited Nov 20 at 11:47

























    answered Nov 20 at 10:29









    Zohar Peled

    52.1k73273




    52.1k73273












    • Thanks for answering, but shouldn't there only be 5 groups (1,2), (3,4,5), (6), (7), (8,9)
      – Danny Rancher
      Nov 20 at 10:41










    • The problem is that RelatedID 5 appears both with 3 and with 4 as it's anchor. Back to the drawing board...
      – Zohar Peled
      Nov 20 at 11:12










    • @DannyRancher I've edited my answer with a correct solution.
      – Zohar Peled
      Nov 20 at 11:47


















    • Thanks for answering, but shouldn't there only be 5 groups (1,2), (3,4,5), (6), (7), (8,9)
      – Danny Rancher
      Nov 20 at 10:41










    • The problem is that RelatedID 5 appears both with 3 and with 4 as it's anchor. Back to the drawing board...
      – Zohar Peled
      Nov 20 at 11:12










    • @DannyRancher I've edited my answer with a correct solution.
      – Zohar Peled
      Nov 20 at 11:47
















    Thanks for answering, but shouldn't there only be 5 groups (1,2), (3,4,5), (6), (7), (8,9)
    – Danny Rancher
    Nov 20 at 10:41




    Thanks for answering, but shouldn't there only be 5 groups (1,2), (3,4,5), (6), (7), (8,9)
    – Danny Rancher
    Nov 20 at 10:41












    The problem is that RelatedID 5 appears both with 3 and with 4 as it's anchor. Back to the drawing board...
    – Zohar Peled
    Nov 20 at 11:12




    The problem is that RelatedID 5 appears both with 3 and with 4 as it's anchor. Back to the drawing board...
    – Zohar Peled
    Nov 20 at 11:12












    @DannyRancher I've edited my answer with a correct solution.
    – Zohar Peled
    Nov 20 at 11:47




    @DannyRancher I've edited my answer with a correct solution.
    – Zohar Peled
    Nov 20 at 11:47












    up vote
    1
    down vote













    This is a graph-walking problem and you would seem to need recursive CTEs. The logic looks like this:



    WITH t AS (
    SELECT *
    FROM (VALUES (1,2)
    ,(2,1)
    ,(3,4)
    ,(3,5)
    ,(4,3)
    ,(4,5)
    ,(5,3)
    ,(5,4)
    ,(6,NULL)
    ,(7,NULL)
    ,(8,9)
    ,(9,8)
    ) AS ValuesTable(ID,RelatedID)
    ),
    cte as (
    select distinct id, id as relatedId, ',' + convert(varchar(max), id) + ',' as relatedIds
    from t
    union all
    select cte.id, t.relatedId, cte.relatedIds + convert(varchar(max), t.relatedId) + ','
    from cte join
    t
    on cte.relatedId = t.id
    where cte.relatedId is not null and
    cte.relatedIds not like '%,' + convert(varchar(max), t.relatedId) + ',%'
    )
    SELECT id, min(relatedId) as grp,
    dense_rank() over (order by min(relatedId)) as grp_number
    FROM cte
    GROUP BY id;


    Here is a db<>fiddle.






    share|improve this answer

























      up vote
      1
      down vote













      This is a graph-walking problem and you would seem to need recursive CTEs. The logic looks like this:



      WITH t AS (
      SELECT *
      FROM (VALUES (1,2)
      ,(2,1)
      ,(3,4)
      ,(3,5)
      ,(4,3)
      ,(4,5)
      ,(5,3)
      ,(5,4)
      ,(6,NULL)
      ,(7,NULL)
      ,(8,9)
      ,(9,8)
      ) AS ValuesTable(ID,RelatedID)
      ),
      cte as (
      select distinct id, id as relatedId, ',' + convert(varchar(max), id) + ',' as relatedIds
      from t
      union all
      select cte.id, t.relatedId, cte.relatedIds + convert(varchar(max), t.relatedId) + ','
      from cte join
      t
      on cte.relatedId = t.id
      where cte.relatedId is not null and
      cte.relatedIds not like '%,' + convert(varchar(max), t.relatedId) + ',%'
      )
      SELECT id, min(relatedId) as grp,
      dense_rank() over (order by min(relatedId)) as grp_number
      FROM cte
      GROUP BY id;


      Here is a db<>fiddle.






      share|improve this answer























        up vote
        1
        down vote










        up vote
        1
        down vote









        This is a graph-walking problem and you would seem to need recursive CTEs. The logic looks like this:



        WITH t AS (
        SELECT *
        FROM (VALUES (1,2)
        ,(2,1)
        ,(3,4)
        ,(3,5)
        ,(4,3)
        ,(4,5)
        ,(5,3)
        ,(5,4)
        ,(6,NULL)
        ,(7,NULL)
        ,(8,9)
        ,(9,8)
        ) AS ValuesTable(ID,RelatedID)
        ),
        cte as (
        select distinct id, id as relatedId, ',' + convert(varchar(max), id) + ',' as relatedIds
        from t
        union all
        select cte.id, t.relatedId, cte.relatedIds + convert(varchar(max), t.relatedId) + ','
        from cte join
        t
        on cte.relatedId = t.id
        where cte.relatedId is not null and
        cte.relatedIds not like '%,' + convert(varchar(max), t.relatedId) + ',%'
        )
        SELECT id, min(relatedId) as grp,
        dense_rank() over (order by min(relatedId)) as grp_number
        FROM cte
        GROUP BY id;


        Here is a db<>fiddle.






        share|improve this answer












        This is a graph-walking problem and you would seem to need recursive CTEs. The logic looks like this:



        WITH t AS (
        SELECT *
        FROM (VALUES (1,2)
        ,(2,1)
        ,(3,4)
        ,(3,5)
        ,(4,3)
        ,(4,5)
        ,(5,3)
        ,(5,4)
        ,(6,NULL)
        ,(7,NULL)
        ,(8,9)
        ,(9,8)
        ) AS ValuesTable(ID,RelatedID)
        ),
        cte as (
        select distinct id, id as relatedId, ',' + convert(varchar(max), id) + ',' as relatedIds
        from t
        union all
        select cte.id, t.relatedId, cte.relatedIds + convert(varchar(max), t.relatedId) + ','
        from cte join
        t
        on cte.relatedId = t.id
        where cte.relatedId is not null and
        cte.relatedIds not like '%,' + convert(varchar(max), t.relatedId) + ',%'
        )
        SELECT id, min(relatedId) as grp,
        dense_rank() over (order by min(relatedId)) as grp_number
        FROM cte
        GROUP BY id;


        Here is a db<>fiddle.







        share|improve this answer












        share|improve this answer



        share|improve this answer










        answered Nov 20 at 12:06









        Gordon Linoff

        752k34286395




        752k34286395






























            draft saved

            draft discarded




















































            Thanks for contributing an answer to Stack Overflow!


            • Please be sure to answer the question. Provide details and share your research!

            But avoid



            • Asking for help, clarification, or responding to other answers.

            • Making statements based on opinion; back them up with references or personal experience.


            To learn more, see our tips on writing great answers.





            Some of your past answers have not been well-received, and you're in danger of being blocked from answering.


            Please pay close attention to the following guidance:


            • Please be sure to answer the question. Provide details and share your research!

            But avoid



            • Asking for help, clarification, or responding to other answers.

            • Making statements based on opinion; back them up with references or personal experience.


            To learn more, see our tips on writing great answers.




            draft saved


            draft discarded














            StackExchange.ready(
            function () {
            StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53389995%2fhow-to-group-nodes-with-relationships-in-sql%23new-answer', 'question_page');
            }
            );

            Post as a guest















            Required, but never shown





















































            Required, but never shown














            Required, but never shown












            Required, but never shown







            Required, but never shown

































            Required, but never shown














            Required, but never shown












            Required, but never shown







            Required, but never shown







            Popular posts from this blog

            Tonle Sap (See)

            I get strange results when I access the Sqlitedatabase with Unity C# via XAMPP

            Guatemaltekische Davis-Cup-Mannschaft