Het probleem met uw poging is het filteren aan het begin. Als ik het goed heb, wil je je gegevens clusteren (allemaal groeperen) op basis van hun relaties, oplopend of aflopend, of een combinatie daarvan. Bijvoorbeeld ID 100
heeft kind 101
, die een ander kind heeft 102
, maar 102
heeft een ouder 103
en je wilt dat het resultaat deze vier is (100, 101, 102, 103
) voor elke invoer in die set. Dit is de reden waarom je in het begin niet kunt filteren, omdat je geen enkele manier hebt om te weten welke relatie door een andere relatie zal worden geketend.
Dit oplossen is niet zo eenvoudig als het lijkt en je kunt het niet oplossen met slechts 1 recursie.
Het volgende is een oplossing die ik lang geleden heb gemaakt om al deze relaties te groeperen. Houd er rekening mee dat het voor grote datasets (meer dan 100k) enige tijd kan duren om te berekenen, omdat eerst alle groepen moeten worden geïdentificeerd en aan het einde het resultaat moet worden geselecteerd.
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
Dat zie je voor @thingID
van waarde 100
, 101
, 102
en 103
het resultaat zijn deze vier, en voor waarden 200
, 201
en 202
de resultaten zijn deze drie.
Ik ben er vrij zeker van dat dit geen optimale oplossing is, maar het geeft de juiste output en ik heb nooit de behoefte gehad om het af te stellen omdat het snel werkt voor mijn vereisten.