Je voudrais sélectionner en 4 groupes les données d'une table ayant la somme des valeurs dans les groupes aussi uniformément réparties que possible. Je suis sûr que je ne l'explique pas assez clairement, je vais donc essayer de donner un exemple.
Ici, j'utilise NTILE (4) pour créer les 4 groupes:
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
Dans la requête et le résultat ci-dessus, les autres colonnes ont été omises par souci de concision.
Vous pouvez donc voir les groupes également comme suit:
1 2 3 4
--- --- --- ---
10 9 8 7
6 5 4 3
2 1
--- --- --- ---
18 15 12 10 Sum Totals of Time
Notez que la somme des totaux de temps utilisant NTile n'est pas vraiment équilibrée entre les groupes. Une meilleure distribution des valeurs de temps serait par exemple:
1 2 3 4
--- --- --- ---
10 9 8 7
3 5 4 6
1 2
--- --- --- ---
14 14 14 13 Sum Totals of Time
Ici, la somme des totaux de temps est répartie de manière plus égale sur les 4 groupes.
Comment puis-je effectuer cela via une instruction TSQL?
De plus, je dois dire que j'utilise SQL Server 2012. Si vous avez quelque chose qui peut m'aider, faites le moi savoir.
Je vous souhaite une bonne journée.
Stan
Réponses:
Voici un coup de poignard à un algorithme. Ce n'est pas parfait, et selon le temps que vous souhaitez passer à l'affiner, il y a probablement d'autres petits gains à faire.
Supposons que vous ayez une table de tâches à exécuter par quatre files d'attente. Vous connaissez la quantité de travail associée à l'exécution de chaque tâche et vous voulez que les quatre files d'attente obtiennent une quantité de travail presque égale, de sorte que toutes les files d'attente se termineront à peu près au même moment.
Tout d'abord, je partitionnerais les tâches en utilisant un module modulé, ordonné par leur taille, de petit à grand.
Les
ROW_NUMBER()
commandes chaque ligne par la taille, attribue ensuite un numéro de ligne, à partir de 1. Ce numéro de ligne est attribué un « groupe » (lagrp
colonne) sur une base préliminaire ronde. La première rangée est le groupe 1, la deuxième rangée est le groupe 2, puis 3, la quatrième obtient le groupe 0, et ainsi de suite.Pour faciliter l'utilisation, je stocke les colonnes
time
etgrp
dans une variable de table appelée@work
.Maintenant, nous pouvons effectuer quelques calculs sur ces données:
La colonne indique à
_grpoffset
quel point le totaltime
pargrp
diffère de la moyenne "idéale". Si le totaltime
de toutes les tâches est de 1000 et qu'il y a quatre groupes, il devrait idéalement y en avoir 250 dans chaque groupe. Si un groupe contient un total de 268, ce groupe est_grpoffset=18
.L'idée est d'identifier les deux meilleures lignes, une dans un groupe «positif» (avec trop de travail) et une dans un groupe «négatif» (avec trop peu de travail). Si nous pouvons échanger des groupes sur ces deux lignes, nous pourrions réduire l'absolu
_grpoffset
des deux groupes.Exemple:
Avec un grand total de 727, chaque groupe devrait avoir un score d'environ 182 pour que la distribution soit parfaite. La différence entre le score du groupe et 182 est ce que nous mettons dans la
_grpoffset
colonne.Comme vous pouvez le voir maintenant, dans le meilleur des mondes, nous devrions déplacer environ 40 points de lignes du groupe 1 au groupe 2 et environ 24 points du groupe 3 au groupe 0.
Voici le code pour identifier ces lignes candidates:
Je rejoins l'expression de table commune que nous avons créée auparavant
cte
: d'un côté, les groupes avec un positif_grpoffset
, de l'autre côté les groupes avec des négatifs. Pour filtrer davantage les lignes supposées correspondre, l'échange des lignes des côtés positif et négatif doit s'améliorer_grpoffset
, c'est-à-dire le rapprocher de 0.Le
TOP 1
etORDER BY
sélectionne la «meilleure» correspondance à permuter en premier.Maintenant, tout ce que nous devons faire est d'ajouter un
UPDATE
et de le boucler jusqu'à ce qu'il n'y ait plus d'optimisation à trouver.TL; DR - voici la requête
Voici le code complet:
la source