グループ内の値の合計が可能な限り均等に分散されているテーブルからデータを4つのグループに選択したいと思います。私はそれを十分に明確に説明していないと確信しているので、例を挙げようと思います。
ここでは、NTILE(4)を使用して4つのグループを作成します。
SELECT Time, NTILE(4) OVER (ORDER BY Time DESC) AS N FROM TableX
Time - N
-------------
10 - 1
9 - 2
8 - 3
7 - 4
6 - 1
5 - 2
4 - 3
3 - 4
2 - 1
1 - 2
上記のクエリと結果では、他の列は簡潔にするために省略されています。
したがって、次のようにグループを表示することもできます。
1 2 3 4
--- --- --- ---
10 9 8 7
6 5 4 3
2 1
--- --- --- ---
18 15 12 10 Sum Totals of Time
NTileを使用した時間の合計は、グループ間で実際にはバランスが取れていないことに注意してください。時間値のより良い分布は、例えば次のようになります:
1 2 3 4
--- --- --- ---
10 9 8 7
3 5 4 6
1 2
--- --- --- ---
14 14 14 13 Sum Totals of Time
ここでは、時間の合計が4つのグループに均等に分散されています。
TSQLステートメントを使用してこれをどのように実行できますか?
さらに、私はSQL Server 2012を使用していると言わなければなりません。私を助けることができるものがあれば、私に知らせてください。
よい一日を。
スタン
これがアルゴリズムのスタブです。それは完璧ではありません、そしてあなたがそれを精製することに費やしたいどのくらいの時間に依存して、作られるべきいくつかの更なる小さな利益があるでしょう。
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
_列に入力するものです。
ご覧のとおり、最高の世界では、グループ1からグループ2に約40ポイントの行を移動し、グループ3からグループ0に約24ポイントを移動する必要があります。
これらの候補行を識別するコードは次のとおりです。
_ 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);
_