sql >> Database teknologi >  >> RDS >> Sqlserver

Sådan grupperer du hierarkiske relationer i SQL Server

Problemet med dit forsøg er filtrering i starten. Hvis jeg har ret, vil du gerne gruppere dine data (gruppere dem alle sammen) efter deres relationer, enten ascendent eller efterkommer, eller en blanding af dem. For eksempel ID 100 har underordnet 101 , som har et andet underordnet 102 , men 102 har en forælder 103 og du vil have resultatet til at være disse fire (100, 101, 102, 103 ) for enhver input, der er i det sæt. Dette er grunden til, at du ikke kan filtrere i starten, da du ikke har nogen mulighed for at vide, hvilket forhold der vil blive lænket gennem et andet forhold.

At løse dette er ikke så simpelt, som det ser ud til, og du vil ikke være i stand til at løse det med kun 1 rekursion.

Følgende er en løsning, jeg lavede for længe siden for at gruppere alle disse forhold sammen. Husk, at for store datasæt (over 100k) kan det tage et stykke tid at beregne, da det først skal identificere alle grupper og vælge resultatet til sidst.

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

Du vil se det for @thingID med værdien 100 , 101 , 102 og 103 resultatet er disse fire, og for værdierne 200 , 201 og 202 resultaterne er disse tre.

Jeg er ret sikker på, at dette ikke er en optimal løsning, men det giver det korrekte output, og jeg har aldrig haft behov for at justere det, da det fungerer hurtigt efter mine krav.



  1. Hvordan bruger man en procent (%) i et LIKE uden at det bliver behandlet som et jokertegn?

  2. Laravel-tidsstempler for at vise millisekunder

  3. SQL Server Operativsystem fejl 5:5 (Adgang nægtes.)

  4. Flask-Sqlalchemy:DB-forespørgsler returnerer ikke nye data