Sqlserver
 sql >> Baza danych >  >> RDS >> Sqlserver

Jak grupować relacje hierarchiczne razem w SQL Server

Problem z twoją próbą polega na filtrowaniu na początku. Jeśli mam rację, chcesz pogrupować swoje dane (pogrupować je wszystkie) według ich relacji, wstępujących lub potomnych, lub ich mieszanką. Na przykład ID 100 ma dziecko 101 , który ma inne dziecko 102 , ale 102 ma rodzica 103 i chcesz, aby wynik był tymi czterema (100, 101, 102, 103 ) dla każdego wejścia znajdującego się w tym zestawie. Dlatego nie możesz filtrować na początku, ponieważ nie masz żadnych środków, aby dowiedzieć się, który związek będzie powiązany z inną relacją.

Rozwiązanie tego nie jest tak proste, jak się wydaje i nie będziesz w stanie go rozwiązać za pomocą tylko jednej rekurencji.

Oto rozwiązanie, które zrobiłem dawno temu, aby pogrupować wszystkie te relacje. Pamiętaj, że w przypadku dużych zbiorów danych (powyżej 100 000) obliczenia mogą zająć trochę czasu, ponieważ najpierw trzeba zidentyfikować wszystkie grupy, a na końcu wybrać wynik.

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

Zobaczysz to dla @thingID o wartości 100 , 101 , 102 i 103 wynikiem są te cztery, a dla wartości 200 , 201 i 202 wyniki to te trzy.

Jestem prawie pewien, że nie jest to optymalne rozwiązanie, ale daje prawidłowe wyniki i nigdy nie musiałem go dostrajać, ponieważ działa szybko zgodnie z moimi wymaganiami.



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Eksportuj tabele (obiekty i dane) na podstawie wybranych kryteriów

  2. Jak filtrować wiersze z wartościami null w instrukcji Select w SQL Server — samouczek SQL Server / TSQL, część 110

  3. Przedstawiamy pierwszą na świecie platformę SaaS zapewniającą dogłębną diagnostykę dla hybrydowych środowisk SQL Server

  4. Kiedy używać indeksów klastrowych lub nieklastrowych w programie SQL Server

  5. MS SQL Server 2008 :Pobieranie daty rozpoczęcia i zakończenia tygodnia do następnych 8 tygodni