sql >> データベース >  >> RDS >> Sqlserver

関連するすべてのレコードを多対多の関係でグループ化、SQL グラフ接続コンポーネント

    再帰 CTE の使用を考えました 、しかし、私の知る限り、SQL Server で UNION を使用することはできません 再帰的 CTE のアンカー メンバーと再帰的メンバーを接続する (PostgreSQL では可能だと思います) ため、重複を排除することはできません。

    declare @i int
    
    with cte as (
         select
             GroupID,
             row_number() over(order by Company) as rn
         from Table1
    )
    update cte set GroupID = rn
    
    select @i = @@rowcount
    
    -- while some rows updated
    while @i > 0
    begin
        update T1 set
            GroupID = T2.GroupID
        from Table1 as T1
            inner join (
                select T2.Company, min(T2.GroupID) as GroupID
                from Table1 as T2
                group by T2.Company
            ) as T2 on T2.Company = T1.Company
        where T1.GroupID > T2.GroupID
    
        select @i = @@rowcount
    
        update T1 set
            GroupID = T2.GroupID
        from Table1 as T1
            inner join (
                select T2.Publisher, min(T2.GroupID) as GroupID
                from Table1 as T2
                group by T2.Publisher
            ) as T2 on T2.Publisher = T1.Publisher
        where T1.GroupID > T2.GroupID
    
        -- will be > 0 if any rows updated
        select @i = @i + @@rowcount
    end
    
    ;with cte as (
         select
             GroupID,
             dense_rank() over(order by GroupID) as rn
         from Table1
    )
    update cte set GroupID = rn
    

    SQL フィドルのデモ

    幅優先探索アルゴリズムも試しました。より高速になる可能性があると考えた (複雑さの点では優れている) ため、ここで解決策を提供します。ただし、SQL アプローチよりも高速ではないことがわかりました:

    declare @Company nvarchar(2), @Publisher nvarchar(2), @GroupID int
    
    declare @Queue table (
        Company nvarchar(2), Publisher nvarchar(2), ID int identity(1, 1),
        primary key(Company, Publisher)
    )
    
    select @GroupID = 0
    
    while 1 = 1
    begin
        select top 1 @Company = Company, @Publisher = Publisher
        from Table1
        where GroupID is null
    
        if @@rowcount = 0 break
    
        select @GroupID = @GroupID + 1
    
        insert into @Queue(Company, Publisher)
        select @Company, @Publisher
    
        while 1 = 1
        begin
            select top 1 @Company = Company, @Publisher = Publisher
            from @Queue
            order by ID asc
    
            if @@rowcount = 0 break
    
            update Table1 set
                GroupID = @GroupID
            where Company = @Company and Publisher = @Publisher
    
            delete from @Queue where Company = @Company and Publisher = @Publisher
    
            ;with cte as (
                select Company, Publisher from Table1 where Company = @Company and GroupID is null
                union all
                select Company, Publisher from Table1 where Publisher = @Publisher and GroupID is null
            )
            insert into @Queue(Company, Publisher)
            select distinct c.Company, c.Publisher
            from cte as c
            where not exists (select * from @Queue as q where q.Company = c.Company and q.Publisher = c.Publisher)
       end
    end
    

    SQL フィドルのデモ

    私のバージョンと Gordon Linoff のバージョンをテストして、そのパフォーマンスを確認しました。 CTE はもっと悪いようです。1000 行以上で完了するのが待ちきれませんでした。

    SQL フィドルのデモはこちら ランダムデータで。私の結果は次のとおりでした:
    128 行 :
    私の RBAR ソリューション:190ms
    私の SQL ソリューション:27ms
    Gordon Linoff のソリューション:958ms
    256 行 :
    私の RBAR ソリューション:560ms
    私の SQL ソリューション:1226ms
    Gordon Linoff のソリューション:45371ms

    これはランダムなデータであるため、結果はあまり一貫していない場合があります。タイミングはインデックスによって変更できると思いますが、全体像が変わるとは思いません。

    古い バージョン - 一時テーブルを使用し、最初のテーブルに触れずに GroupID を計算するだけです:

    declare @i int
    
    -- creating table to gather all possible GroupID for each row
    create table #Temp
    (
        Company varchar(1), Publisher varchar(1), GroupID varchar(1),
        primary key (Company, Publisher, GroupID)
    )
    
    -- initializing it with data
    insert into #Temp (Company, Publisher, GroupID)
    select Company, Publisher, Company
    from Table1
    
    select @i = @@rowcount
    
    -- while some rows inserted into #Temp
    while @i > 0
    begin
        -- expand #Temp in both directions
        ;with cte as (
            select
                T2.Company, T1.Publisher,
                T1.GroupID as GroupID1, T2.GroupID as GroupID2
            from #Temp as T1
                inner join #Temp as T2 on T2.Company = T1.Company
            union
            select
                T1.Company, T2.Publisher,
                T1.GroupID as GroupID1, T2.GroupID as GroupID2
            from #Temp as T1
                inner join #Temp as T2 on T2.Publisher = T1.Publisher        
        ), cte2 as (
            select
                Company, Publisher,
                case when GroupID1 < GroupID2 then GroupID1 else GroupID2 end as GroupID
            from cte
        )
        insert into #Temp
        select Company, Publisher, GroupID
        from cte2
        -- don't insert duplicates
        except
        select Company, Publisher, GroupID
        from #Temp
    
        -- will be > 0 if any row inserted
        select @i = @@rowcount
    end
    
    select
        Company, Publisher,
        dense_rank() over(order by min(GroupID)) as GroupID
    from #Temp
    group by Company, Publisher
    

    => SQL フィドルの例



    1. 複雑な基準クエリ:NativeQueryの代わりにJPAを使用

    2. 条件付き分割変換:SQLServerからOracleおよびMySQLデータベースへのデータのエクスポート

    3. ランニング乗算の計算方法

    4. postgresql9.4または9.5のjsonオブジェクトのネストされた配列内の要素をクエリします