これがアルゴリズムのスタブです。それは完璧ではありません、そしてあなたがそれを精製することに費やしたいどのくらいの時間に依存して、作られるべきいくつかのさらなる小さな利益があるでしょう。
4つのキューによって実行されるタスクのテーブルがあるとします。各タスクの実行に関連する作業量がわかっていて、4つのキューすべてにほぼ同じ量の作業を行わせたいので、すべてのキューがほぼ同時に完了します。
最初に、サイズの大きいものから小さいものへと順番に並べて、タスクを分割します。
SELECT [time], ROW_NUMBER() OVER (ORDER BY [time])%4 AS grp, 0
ROW_NUMBER()
注文サイズによってすべての行は、次に「グループ」(割り当てられている。この行番号1から始まる行番号を割り当てるgrp
ラウンドロビンに基づいて列)。最初の行はグループ1、2番目の行はグループ2、次に3、4番目はグループ0、というようになります。
time ROW_NUMBER() grp
---- ------------ ---
1 1 1
10 2 2
12 3 3
15 4 0
19 5 1
22 6 2
...
使いやすくするために、と呼ばれるテーブル変数にtime
およびgrp
列を格納しています@work
。
これで、このデータに対していくつかの計算を実行できます。
WITH cte AS (
SELECT *, SUM([time]) OVER (PARTITION BY grp)
-SUM([time]) OVER (PARTITION BY (SELECT NULL))/4 AS _grpoffset
FROM @work)
...
この列_grpoffset
は、time
あたりの合計grp
が「理想的な」平均とどの程度異なるかを示しています。time
すべてのタスクの合計が1000で、4つのグループがある場合、理想的には、各グループで合計250になるはずです。グループに合計268が含まれている場合、そのグループは_grpoffset=18
です。
アイデアは、「ポジティブ」グループ(作業が多すぎる)と「ネガティブ」グループ(作業が少なすぎる)の2つの最適な行を識別することです。これら2つの行でグループを入れ替えることができれば_grpoffset
、両方のグループの絶対数を減らすことができます。
例:
time grp total _grpoffset
---- --- ----- ----------
3 1 222 40
46 1 222 40
73 1 222 40
100 1 222 40
6 2 134 -48
52 2 134 -48
76 2 134 -48
11 3 163 -21
66 3 163 -21
86 3 163 -21
45 0 208 24
71 0 208 24
92 0 208 24
----
=727
総計が727であるため、各グループの分布が完璧になるには、約182のスコアが必要です。グループのスコアと182の違いは、_grpoffset
列に入力するものです。
ご覧のとおり、最高の世界では、約40ポイント分の行をグループ1からグループ2に移動し、約24ポイントをグループ3からグループ0に移動する必要があります。
これらの候補行を識別するコードは次のとおりです。
SELECT TOP 1 pos._row AS _pos_row, pos.grp AS _pos_grp,
neg._row AS _neg_row, neg.grp AS _neg_grp
FROM cte AS pos
INNER JOIN cte AS neg ON
pos._grpoffset>0 AND
neg._grpoffset<0 AND
--- To prevent infinite recursion:
pos.moved<4 AND
neg.moved<4
WHERE --- must improve positive side's offset:
ABS(pos._grpoffset-pos.[time]+neg.[time])<=pos._grpoffset AND
--- must improve negative side's offset:
ABS(neg._grpoffset-neg.[time]+pos.[time])<=ABS(neg._grpoffset)
--- Largest changes first:
ORDER BY ABS(pos.[time]-neg.[time]) DESC
) AS x ON w._row IN (x._pos_row, x._neg_row);
私は前に作成した共通のテーブル式を自己結合していますcte
:片側では、が正の_grpoffset
グループ、反対側では負のグループ。どの行が互いに一致すると想定されるかをさらにフィルターで除外するには、正側と負側の行の入れ替えを改善する_grpoffset
、つまり0に近づける必要があります。
TOP 1
そしてORDER BY
最初のスワップへの「最良の」マッチを選択します。
ここで必要なのは、を追加しUPDATE
、最適化が見つからなくなるまでループすることだけです。
TL; DR-これがクエリです
完全なコードは次のとおりです。
DECLARE @work TABLE (
_row int IDENTITY(1, 1) NOT NULL,
[time] int NOT NULL,
grp int NOT NULL,
moved tinyint NOT NULL,
PRIMARY KEY CLUSTERED ([time], _row)
);
WITH cte AS (
SELECT 0 AS n, CAST(1+100*RAND(CHECKSUM(NEWID())) AS int) AS [time]
UNION ALL
SELECT n+1, CAST(1+100*RAND(CHECKSUM(NEWID())) AS int) AS [time]
FROM cte WHERE n<100)
INSERT INTO @work ([time], grp, moved)
SELECT [time], ROW_NUMBER() OVER (ORDER BY [time])%4 AS grp, 0
FROM cte;
WHILE (@@ROWCOUNT!=0)
WITH cte AS (
SELECT *, SUM([time]) OVER (PARTITION BY grp)
-SUM([time]) OVER (PARTITION BY (SELECT NULL))/4 AS _grpoffset
FROM @work)
UPDATE w
SET w.grp=(CASE w._row
WHEN x._pos_row THEN x._neg_grp
ELSE x._pos_grp END),
w.moved=w.moved+1
FROM @work AS w
INNER JOIN (
SELECT TOP 1 pos._row AS _pos_row, pos.grp AS _pos_grp,
neg._row AS _neg_row, neg.grp AS _neg_grp
FROM cte AS pos
INNER JOIN cte AS neg ON
pos._grpoffset>0 AND
neg._grpoffset<0 AND
--- To prevent infinite recursion:
pos.moved<4 AND
neg.moved<4
WHERE --- must improve positive side's offset:
ABS(pos._grpoffset-pos.[time]+neg.[time])<=pos._grpoffset AND
--- must improve negative side's offset:
ABS(neg._grpoffset-neg.[time]+pos.[time])<=ABS(neg._grpoffset)
--- Largest changes first:
ORDER BY ABS(pos.[time]-neg.[time]) DESC
) AS x ON w._row IN (x._pos_row, x._neg_row);