【问题标题】:Dividing a list of numbers into roughtly equal totals将数字列表分成大致相等的总数
【发布时间】:2011-05-08 06:40:39
【问题描述】:

我知道我的问题可能没有“完美”的解决方案(这听起来像是背包或垃圾箱包装问题的变体),但这是我的场景:

我想将一个 SQL 数据库表列表划分为 n 个(比如说 7)个大致相同大小的堆(这样我可以将一些维护任务大致平均分布在整个一周内)。

假设我有 100 张桌子(可能更高或更低,但不可能高于 5000),大小从 1 到 10,000,000 不等(当然,更大的桌子不太常见)。

我最初的想法是按字母顺序(伪随机)对表格进行排序,然后从头开始遍历,当总数超过 Sum(Size)/7 时移动到下一组。对于某些数据库,这可能会正常工作,但如果两个巨大的表彼此相邻,那么这会导致非常不平等的组。 (这并不像听起来那么不可能,请考虑两个巨大的表,Account_History 和 Account_History_Archive)。

是否有任何普遍接受的技术可以通过各种源数据提供“良好”的结果?我倾向于一种更简单的技术,而不是更精确的分组(如果某些日子的维护时间比其他日子稍长,那没什么大不了的)。

【问题讨论】:

    标签: sql algorithm np-complete bin-packing


    【解决方案1】:

    如何按大小对表格进行排序,然后对于每个表格,将其放入当前总行数最少的那一天?这意味着最大的 7 张桌子将首先分布在几天内。然后,第 8 个最大的将与前 7 个中最小的一起去,依此类推。您将继续以安排最少的工作量来填补这一天。

    小参考表最终的结果可能没有太大区别。

    您可以发明这样不好的场景,但我希望它可以在实践中发挥作用而不会太复杂。

    【讨论】:

    • 听起来是个可行的策略。即使我有一张大桌子,它最终也会单独放在一个桶中,而其他 6 个桶的大小应该大致相同。
    【解决方案2】:

    仅供参考,这就是我的做法。我想将“桶”放入一个持久表中,并且每两周只“重新计算”一次。否则,我担心如果我每天都计算这些桶,一张桌子可能会从一个桶跳到下一个桶。但是,我想经常重新计算模式和 DDL 修改。这是sn-p。

    -------------------------------------------------------------------------------------
    --Get the total table size (by rows)
    -------------------------------------------------------------------------------------
    
    
    if object_id('tempdb..#Space') is not null
    drop table #Space
    
    SELECT 
        TableName = t.NAME,
        Schem = s.name,
        Pages = sum(a.total_pages),
        Grp = row_number() over (order by sum(a.total_pages) desc)
    INTO #Space
    FROM 
        sys.tables t
    INNER JOIN      
        sys.indexes i ON t.OBJECT_ID = i.object_id
    INNER JOIN 
        sys.partitions p ON i.object_id = p.OBJECT_ID AND i.index_id = p.index_id
    INNER JOIN 
        sys.allocation_units a ON p.partition_id = a.container_id
    LEFT OUTER JOIN 
        sys.schemas s ON t.schema_id = s.schema_id
    WHERE 
        t.NAME NOT LIKE 'dt%' 
        AND t.is_ms_shipped = 0
        AND i.OBJECT_ID > 255 
    GROUP BY 
        t.Name, s.name
    
    
    -------------------------------------------------------------------------------------
    --split the tables into 7 buckets by:
        --updating the Grp to the Grp with the lowest cumulative sum of all members by
        --ordering by the current cumulative sum of all members
    -------------------------------------------------------------------------------------
    
    declare @ct int = 8
    
    
    while @ct <= (select max(Grp) from #Space)
    begin
    
        update S
        set Grp = (select top 1 Grp from #Space where Grp < 8 order by sum(Pages) over (partition by Grp) asc)
        from #Space S
        where S.Grp = @ct
    
        set @ct = @ct + 1
    
    end
    
    
    insert into AdminTools..TableSpace (TableName
                                        ,Schem
                                        ,Pages
                                        ,Grp
                                        ,GrpPages
                                        ,LoadDate)
    select 
        TableName
        ,Schem
        ,Pages
        ,Grp
        ,GrpPages = sum(Pages) over (partition by Grp)
        ,LoadDate = getdate()
    from #Space
    end
    

    【讨论】:

    • 我正在尝试实施这种方法。那么在我将表拆分为 7 个存储桶后,如何在这些存储桶上运行 DBCC 命令?在一组桌子上?
    【解决方案3】:

    我不知道这在 好代码 规模上的评价如何,但我追求的解决方案是将作业列表放入优先级队列中,按成本最高和工人排序将垃圾箱放入另一个优先级队列,按分配的最少工作排序,然后从一个队列中弹出作业并将它们分配给顶部(最不忙)的工作箱,直到没有工作。

    【讨论】:

    • 您说在这种情况下单个队列优于 7 个队列是正确的。不过,这可能需要更多的工作来实现。
    • 对我来说很有意义。使用简单的ORDER BY 轻松实现 TSQL
    猜你喜欢
    • 1970-01-01
    • 2015-12-19
    • 2010-10-27
    • 2012-01-07
    • 2015-05-23
    • 1970-01-01
    • 2012-11-20
    • 2011-01-08
    • 2022-10-29
    相关资源
    最近更新 更多