Sqlserver
 sql >> Cơ Sở Dữ Liệu >  >> RDS >> Sqlserver

Cách nhóm các mối quan hệ phân cấp với nhau trong SQL Server

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 , 102103 kết quả là bốn giá trị này và cho các giá trị 200 , 201202 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.



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Sự khác biệt giữa sys.views, sys.system_views và sys.all_views trong SQL Server

  2. Làm cách nào để kết nối với cơ sở dữ liệu SQL từ C #?

  3. Khắc phục sự cố từ chối truy cập đang chờ xử lý phục hồi cơ sở dữ liệu SQL

  4. Thêm màu hàng xen kẽ vào báo cáo Dịch vụ báo cáo máy chủ SQL

  5. Phương pháp hiệu quả nhất để phát hiện sự thay đổi cột trong MS SQL Server