How to group nodes with relationships in SQL
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
|
show 1 more comment
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
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
|
show 1 more comment
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
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
sql tsql grouping
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
|
show 1 more comment
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
|
show 1 more comment
2 Answers
2
active
oldest
votes
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
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
add a comment |
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.
add a comment |
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
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
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
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
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
add a comment |
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
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
add a comment |
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
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
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
add a comment |
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
add a comment |
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.
add a comment |
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.
add a comment |
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.
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.
answered Nov 20 '18 at 12:06
Gordon LinoffGordon Linoff
774k35306408
774k35306408
add a comment |
add a comment |
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.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
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
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
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
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