Vấn đề với nỗ lực của bạn là lọc khi bắt đầu. Nếu tôi đúng, bạn muốn nhóm dữ liệu của mình (nhóm tất cả chúng lại với nhau) theo các mối quan hệ của chúng, con cháu hoặc con cháu, hoặc kết hợp chúng. Ví dụ:ID 100
có con 101
, có một con khác 102
, nhưng 102
có cha mẹ 103
và bạn muốn kết quả là bốn (100, 101, 102, 103
) cho bất kỳ đầu vào nào trong tập hợp đó. Đây là lý do tại sao bạn không thể lọc ngay từ đầu, vì bạn không có bất kỳ phương tiện nào để biết mối quan hệ nào sẽ được xâu chuỗi trong suốt mối quan hệ khác.
Việc giải quyết vấn đề này không đơn giản như bạn tưởng tượng và bạn sẽ không thể giải nó chỉ với 1 lần đệ quy.
Sau đây là một giải pháp tôi đã thực hiện từ lâu để nhóm tất cả các mối quan hệ này lại với nhau. Hãy nhớ rằng, đối với các tập dữ liệu lớn (hơn 100 nghìn), có thể mất một lúc để tính toán, vì nó phải xác định tất cả các nhóm trước và chọn kết quả sau cùng.
CREATE PROCEDURE GetAncestors(@thingID INT)
AS
BEGIN
SET NOCOUNT ON
-- Load your data
IF OBJECT_ID('tempdb..#TreeRelationship') IS NOT NULL
DROP TABLE #TreeRelationship
CREATE TABLE #TreeRelationship (
RelationID INT IDENTITY(1,1) PRIMARY KEY NONCLUSTERED,
Parent INT,
Child INT,
GroupID INT)
INSERT INTO #TreeRelationship (
Parent,
Child)
SELECT
Parent = D.Parent,
Child = D.Child
FROM
Example AS D
UNION -- Data has to be loaded in both ways (direct and reverse) for algorithm to work correctly
SELECT
Parent = D.Child,
Child = D.Parent
FROM
Example AS D
-- Start algorithm
IF OBJECT_ID('tempdb..#FirstWork') IS NOT NULL
DROP TABLE #FirstWork
CREATE TABLE #FirstWork (
Parent INT,
Child INT,
ComponentID INT)
CREATE CLUSTERED INDEX CI_FirstWork ON #FirstWork (Parent, Child)
INSERT INTO #FirstWork (
Parent,
Child,
ComponentID)
SELECT DISTINCT
Parent = T.Parent,
Child = T.Child,
ComponentID = ROW_NUMBER() OVER (ORDER BY T.Parent, T.Child)
FROM
#TreeRelationship AS T
IF OBJECT_ID('tempdb..#SecondWork') IS NOT NULL
DROP TABLE #SecondWork
CREATE TABLE #SecondWork (
Component1 INT,
Component2 INT)
CREATE CLUSTERED INDEX CI_SecondWork ON #SecondWork (Component1)
DECLARE @v_CurrentDepthLevel INT = 0
WHILE @v_CurrentDepthLevel < 100 -- Relationships depth level can be controlled with this value
BEGIN
SET @v_CurrentDepthLevel = @v_CurrentDepthLevel + 1
TRUNCATE TABLE #SecondWork
INSERT INTO #SecondWork (
Component1,
Component2)
SELECT DISTINCT
Component1 = t1.ComponentID,
Component2 = t2.ComponentID
FROM
#FirstWork t1
INNER JOIN #FirstWork t2 on
t1.child = t2.parent OR
t1.parent = t2.parent
WHERE
t1.ComponentID <> t2.ComponentID
IF (SELECT COUNT(*) FROM #SecondWork) = 0
BREAK
UPDATE #FirstWork SET
ComponentID = CASE WHEN items.ComponentID < target THEN items.ComponentID ELSE target END
FROM
#FirstWork items
INNER JOIN (
SELECT
Source = Component1,
Target = MIN(Component2)
FROM
#SecondWork
GROUP BY
Component1
) new_components on new_components.source = ComponentID
UPDATE #FirstWork SET
ComponentID = target
FROM #FirstWork items
INNER JOIN(
SELECT
source = component1,
target = MIN(component2)
FROM
#SecondWork
GROUP BY
component1
) new_components ON new_components.source = ComponentID
END
;WITH Groupings AS
(
SELECT
parent,
child,
group_id = DENSE_RANK() OVER (ORDER BY ComponentID DESC)
FROM
#FirstWork
)
UPDATE FG SET
GroupID = IT.group_id
FROM
#TreeRelationship FG
INNER JOIN Groupings IT ON
IT.parent = FG.parent AND
IT.child = FG.child
-- Select the proper result
;WITH IdentifiedGroup AS
(
SELECT TOP 1
T.GroupID
FROM
#TreeRelationship AS T
WHERE
T.Parent = @thingID
)
SELECT DISTINCT
Result = T.Parent
FROM
#TreeRelationship AS T
INNER JOIN IdentifiedGroup AS I ON T.GroupID = I.GroupID
END
Bạn sẽ thấy điều đó cho @thingID
của giá trị 100
, 101
, 102
và 103
kết quả là bốn giá trị này và cho các giá trị 200
, 201
và 202
kết quả là ba thứ này.
Tôi khá chắc rằng đây không phải là một giải pháp tối ưu, nhưng nó cho đầu ra chính xác và tôi không bao giờ cần phải điều chỉnh nó vì nó hoạt động nhanh theo yêu cầu của tôi.