数字のリストを非常に等しい合計に分割します
-
08-10-2019 - |
質問
私はおそらく私の質問に「完璧な」解決策がないことを知っています(これはナップサックやビンの梱包の問題のバリエーションのように聞こえます)が、私のシナリオは次のとおりです。
SQLデータベーステーブルのリストをN(たとえば7)ほぼ等しくサイズの山に分割したいと思います(したがって、1週間にわたってほぼ均等にメンテナンスタスクを広めることができます)。
サイズ1からサイズ10,000,000までの範囲で、100個のテーブル(これは高くても低いかもしれませんが、5000を超える可能性が高い)があるとしましょう(もちろん、大きなテーブルはあまり一般的ではありません)。
私の元のアイデアは、テーブルをアルファベット順に分類し(擬似ランダムリー)、最初から歩いて、合計が合計(サイズ)/7を超えたときに次のグループに移動することでした。一部のデータベースの場合、これはおそらく正常に機能しますが、2つの巨大なテーブルが互いに隣にある場合、これは非常に不平等なグループになります。 (これは見た目ほどありそうもないので、2つの巨大なテーブル、Account_historyとAccount_history_archiveを考慮してください)。
さまざまなソースデータで「良好な」結果をもたらす、これについて一般的に受け入れられている手法はありますか?私はより正確なグループ化ではなく、よりシンプルなテクニックに傾いています(メンテナンスが他の日よりも少し長く実行された場合、そうではありません それ 大規模な取引)。
解決
テーブルをサイズごとに並べ替えてから、各テーブルについて、現在最小の総行がある日にそれを入れてください。これは、7つの最大のテーブルが最初に1日に広がることを意味します。その後、8番目に大きなものは最初の7つの最小のものなどになります。あなたは、それに予定されている作業の最小量でその日を埋め続けます。
小さなリファレンステーブルが最終的に最終的に終わる場合、おそらく大きな違いはありません。
これが良くないシナリオを発明することもできますが、複雑すぎずに実際に機能することを期待しています。
他のヒント
これがどのようにレートしているのかわかりません 良いコード スケールですが、私が追求する解決策は、ジョブリストを最優先キューに配置し、最も費用がかかります。ワーカーは、割り当てられた最小の作業でソートされ、1つのキューからジョブをポップしてポップして、別の優先キューにビンを入れます。作業がなくなるまで、それらを上部(忙しい)労働者ビンに割り当てます。
参照のためだけに、これが私がこれを行った方法です。 「バケツ」を永続的なテーブルに入れたいと思っていましたが、2週間ごとに「再計算」しました。そうでなければ、これらのバケツを毎日計算した場合、テーブルが1つのバケットから次のバケットにジャンプできることを恐れました。しかし、私はスキーマとDDLの変更のために頻繁にすべての再計算をしたかったのです。これがそのスニペットです。
-------------------------------------------------------------------------------------
--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