问题
Supose the families bellow:

The Build Schema of this is:
create table PersonConn (child int, parent int)
insert into PersonConn values (1,2)
insert into PersonConn values (1,3)
insert into PersonConn values (5,3)
insert into PersonConn values (5,4)
insert into PersonConn values (6,7)
insert into PersonConn values (6,8)
insert into PersonConn values (2,9)
insert into PersonConn values (2,10)
insert into PersonConn values (3,11)
insert into PersonConn values (3,12)
To get the ancestors of a family member I can use recursion as showed bellow:
WITH Childs AS (
SELECT distinct Child, Parent
FROM PersonConn
WHERE Child = 1
UNION ALL
SELECT t2.Child, t2.Parent
FROM [Childs] t1
INNER JOIN PersonConn t2
ON t2.Child = t1.parent
)
SELECT PARENT FROM CHILDS
SQL Fiddle
It will take all the ancestors of selected member (ID 1 in this example), but not the brothers for example. The query goes up only in family tree.
My question is:
How to get all members of a family (sons, parents, grandfathers, uncles, cousins, etc...) starting from a single person?
UPDATE
One method to solve this is a loop that inserts a person in a temporary table. After you could join PersonConn
table with this temporary table and inserts other people. Do this until no one is inserted anymore. I am looking for a more efficient (and elegant) way. I have about 200MM records in PersonConn
table.
回答1:
The solution I found is not good at all. It gives the right answer but is very slow, even for this very small table.
DECLARE @INCLUIDOS TABLE (ID INT)
INSERT INTO @INCLUIDOS VALUES(1)
DECLARE @PAST_QUANT INT = 0
DECLARE @QUANT INT = 1
WHILE @QUANT <> @PAST_QUANT
BEGIN
SET @PAST_QUANT = @QUANT
INSERT INTO @INCLUIDOS
SELECT PARENT
FROM PERSONCONN
WHERE CHILD IN (SELECT ID FROM @INCLUIDOS)
AND PARENT NOT IN (SELECT ID FROM @INCLUIDOS)
INSERT INTO @INCLUIDOS
SELECT CHILD
FROM PERSONCONN
WHERE PARENT IN (SELECT ID FROM @INCLUIDOS)
AND CHILD NOT IN (SELECT ID FROM @INCLUIDOS)
SET @QUANT = (SELECT COUNT(*) FROM @INCLUIDOS)
END
SELECT DISTINCT ID FROM @INCLUIDOS
SQL Fiddle
回答2:
In first I suggest you that use hierarchyid column for your table.
Try following query (without hierarchyid):
DECLARE @PersonId INT = 3
;WITH Parents AS (
SELECT @PersonId AS Id
UNION ALL
SELECT child
FROM PersonConn pc
INNER JOIN Parents p ON pc.parent = p.Id
),
Childs AS (
SELECT distinct pc.Child, pc.Parent
FROM PersonConn pc
INNER JOIN Parents p ON pc.child = p.Id OR pc.parent = p.Id
UNION ALL
SELECT t2.Child, t2.Parent
FROM [Childs] t1
INNER JOIN PersonConn t2
ON t2.Child = t1.parent
)
SELECT DISTINCT CASE WHEN N.n=1 THEN parent ELSE child END
FROM CHILDS
CROSS APPLY(SELECT 1 UNION SELECT 2)N(n)
SQL Fiddle
回答3:
This should work given any node. The core CTE's only work if you begin with a root child node. So the first part finds a root child if the starting person is not one. The technique is to walk up the hierarchy, then down, then back up to get everyone in a family.
DECLARE @PersonId INT = 10
-- if id passed in is not a root child, then get one
If (SELECT Top 1 Parent FROM PersonConn WHERE Child = @PersonId) is null
WITH CHILDS AS (
SELECT Child, 0 as [level]
FROM PersonConn
WHERE Parent = @PersonId
UNION ALL
SELECT t2.Child, [level] + 1
FROM CHILDS t1
INNER JOIN PersonConn t2
ON t2.Parent = t1.Child
)
SELECT Top 1 @PersonId = Child FROM CHILDS ORDER BY [level] Desc;
WITH CHILDS AS (
SELECT Child, Parent
FROM PersonConn
WHERE Child = @PersonId
UNION ALL
SELECT t2.Child, t2.Parent
FROM CHILDS t1
INNER JOIN PersonConn t2
ON t2.Child = t1.parent
),
PARENTS AS (
SELECT Child, Parent
FROM PersonConn
WHERE Parent in (Select parent from CHILDS)
UNION ALL
SELECT t2.Child, t2.Parent
FROM PARENTS t1
INNER JOIN PersonConn t2
ON t2.parent = t1.child
),
CHILDS2 AS (
SELECT Child, Parent
FROM PersonConn
WHERE Child in(Select child From Parents)
UNION ALL
SELECT t2.Child, t2.Parent
FROM CHILDS2 t1
INNER JOIN PersonConn t2
ON t2.Child = t1.parent
)
SELECT DISTINCT Parent, Child FROM CHILDS2
I certainly don't think this is elegant, and I can't imagine it will perform well. I would be interested in knowing how well it performs on your volume of data. Not sure exactly how you are going to use this in production, but I recommend creating another field and populate it to identify entire families if this is something you have to do frequently.
回答4:
Here is a simplified version of Nizam's July 23rd answer. With the PersonConn
table properly indexed, I get very good results (however I have no way to test it with 200 million records). If a temp table were used instead of a table variable you could index ID
, but I don't think this would improve performance much because the index would need updating after each insert.
DECLARE @INCLUIDOS TABLE (ID INT)
INSERT INTO @INCLUIDOS VALUES(5)
WHILE @@ROWCOUNT <> 0
BEGIN
INSERT INTO @INCLUIDOS
SELECT CHILD
FROM PERSONCONN
WHERE PARENT IN (SELECT ID FROM @INCLUIDOS)
AND CHILD NOT IN (SELECT ID FROM @INCLUIDOS)
INSERT INTO @INCLUIDOS
SELECT PARENT
FROM PERSONCONN
WHERE CHILD IN (SELECT ID FROM @INCLUIDOS)
AND PARENT NOT IN (SELECT ID FROM @INCLUIDOS)
END
SELECT ID FROM @INCLUIDOS
来源:https://stackoverflow.com/questions/24899070/get-family-members