sql >> Database >  >> RDS >> Sqlserver

Hiërarchische relaties groeperen in SQL Server

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.



  1. Punttype gebruiken met PostgreSQL en JPA/Hibernate

  2. PROBLEEM:Mysql zet Enum om in Int

  3. Python en MySQL gebruiken in het ETL-proces

  4. Hoe verbinding te maken met postgresql met behulp van url