How to group nodes with relationships in SQL












1















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 '18 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 '18 at 9:46











  • @Zohar, Yes, thats correct

    – Danny Rancher
    Nov 20 '18 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 '18 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 '18 at 10:13
















1















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 '18 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 '18 at 9:46











  • @Zohar, Yes, thats correct

    – Danny Rancher
    Nov 20 '18 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 '18 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 '18 at 10:13














1












1








1


0






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 '18 at 10:52







Danny Rancher

















asked Nov 20 '18 at 9:34









Danny RancherDanny Rancher

88821432




88821432













  • 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 '18 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 '18 at 9:46











  • @Zohar, Yes, thats correct

    – Danny Rancher
    Nov 20 '18 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 '18 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 '18 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 '18 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 '18 at 9:46











  • @Zohar, Yes, thats correct

    – Danny Rancher
    Nov 20 '18 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 '18 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 '18 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 '18 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 '18 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 '18 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 '18 at 9:46













@Zohar, Yes, thats correct

– Danny Rancher
Nov 20 '18 at 9:46





@Zohar, Yes, thats correct

– Danny Rancher
Nov 20 '18 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 '18 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 '18 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 '18 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 '18 at 10:13












2 Answers
2






active

oldest

votes


















1














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 '18 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 '18 at 11:12











  • @DannyRancher I've edited my answer with a correct solution.

    – Zohar Peled
    Nov 20 '18 at 11:47



















1














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',
    autoActivateHeartbeat: false,
    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









    1














    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 '18 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 '18 at 11:12











    • @DannyRancher I've edited my answer with a correct solution.

      – Zohar Peled
      Nov 20 '18 at 11:47
















    1














    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 '18 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 '18 at 11:12











    • @DannyRancher I've edited my answer with a correct solution.

      – Zohar Peled
      Nov 20 '18 at 11:47














    1












    1








    1







    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 '18 at 11:47

























    answered Nov 20 '18 at 10:29









    Zohar PeledZohar Peled

    54.1k73273




    54.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 '18 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 '18 at 11:12











    • @DannyRancher I've edited my answer with a correct solution.

      – Zohar Peled
      Nov 20 '18 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 '18 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 '18 at 11:12











    • @DannyRancher I've edited my answer with a correct solution.

      – Zohar Peled
      Nov 20 '18 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 '18 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 '18 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 '18 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 '18 at 11:12













    @DannyRancher I've edited my answer with a correct solution.

    – Zohar Peled
    Nov 20 '18 at 11:47





    @DannyRancher I've edited my answer with a correct solution.

    – Zohar Peled
    Nov 20 '18 at 11:47













    1














    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




























      1














      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


























        1












        1








        1







        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 '18 at 12:06









        Gordon LinoffGordon Linoff

        774k35306408




        774k35306408






























            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.




            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

            Biblatex bibliography style without URLs when DOI exists (in Overleaf with Zotero bibliography)

            ComboBox Display Member on multiple fields

            Is it possible to collect Nectar points via Trainline?