Je décrirai le problème en termes de chargement d'un nombre fixe de camions avec des commandes, aussi uniformément que possible.
Contributions:
@TruckCount - the number of empty trucks to fill
Un ensemble:
OrderId,
OrderDetailId,
OrderDetailSize,
TruckId (initially null)
Orders
sont composés d'un ou plusieurs OrderDetails
.
Le défi ici est d'attribuer un TruckId
à chaque enregistrement.
Une seule commande ne peut pas être répartie entre plusieurs camions.
Les camions doivent être aussi uniformément chargés que possible, mesurés par sum(OrderDetailSize)
.
* Également: le plus petit delta réalisable entre le camion le moins chargé et le camion le plus chargé. Selon cette définition, 1,2,3 est plus uniformément distribué que 1,1,4. Si cela vous aide, faites comme si vous étiez un algorithme de statistiques, créant des histogrammes de hauteur égale.
Il n'y a aucune considération pour la charge maximale du camion. Ce sont des camions élastiques magiques. Le nombre de camions est cependant fixe.
Il existe évidemment une solution itérative: le tournoi à la ronde alloue les commandes.
Mais peut-il être fait comme une logique basée sur un ensemble?
Mon intérêt principal est pour SQL Server 2014 ou version ultérieure. Mais des solutions basées sur des ensembles pour d'autres plates-formes pourraient également être intéressantes.
Cela ressemble au territoire d'Itzik Ben-Gan :)
Mon application réelle distribue une charge de travail de traitement dans un certain nombre de compartiments pour correspondre au nombre de CPU logiques. Par conséquent, chaque seau n'a pas de taille maximale. Mises à jour des statistiques, en particulier. Je pensais juste que c'était plus amusant de résumer le problème dans les camions comme un moyen de cadrer le défi.
CREATE TABLE #OrderDetail (
OrderId int NOT NULL,
OrderDetailId int NOT NULL PRIMARY KEY,
OrderDetailSize tinyint NOT NULL,
TruckId tinyint NULL)
-- Sample Data
INSERT #OrderDetail (OrderId, OrderDetailId, OrderDetailSize)
VALUES
(1 ,100 ,75 ),
(2 ,101 ,5 ),
(2 ,102 ,5 ),
(2 ,103 ,5 ),
(2 ,104 ,5 ),
(2 ,105 ,5 ),
(3 ,106 ,100),
(4 ,107 ,1 ),
(5 ,108 ,11 ),
(6 ,109 ,21 ),
(7 ,110 ,49 ),
(8 ,111 ,25 ),
(8 ,112 ,25 ),
(9 ,113 ,40 ),
(10 ,114 ,49 ),
(11 ,115 ,10 ),
(11 ,116 ,10 ),
(12 ,117 ,15 ),
(13 ,118 ,18 ),
(14 ,119 ,26 )
--> YOUR SOLUTION HERE
-- After assigning Trucks, Measure delta between most and least loaded trucks.
-- Zero is perfect score, however the challenge is a set based solution that will scale, and produce good results, rather
-- than iterative solution that will produce perfect results by exploring every possibility.
SELECT max(TruckOrderDetailSize) - MIN(TruckOrderDetailSize) AS TruckMinMaxDelta
FROM
(SELECT SUM(OrderDetailSize) AS TruckOrderDetailSize FROM #OrderDetail GROUP BY TruckId) AS Truck
DROP TABLE #OrderDetail
la source
Réponses:
Ma première pensée a été
La partie «meilleure solution» est définie dans la question - la plus petite différence entre les camions les plus chargés et les moins chargés. L'autre morceau - toutes les combinaisons - m'a fait réfléchir.
Prenons une situation où nous avons trois commandes A, B et C et trois camions. Les possibilités sont
Beaucoup d'entre eux sont symétriques. Les six premières lignes, par exemple, ne diffèrent que par le camion dans lequel chaque commande est passée. Étant donné que les camions sont fongibles, ces arrangements produiront le même résultat. Je vais ignorer cela pour l'instant.
Il existe des requêtes connues pour produire des permutations et des combinaisons. Cependant, ceux-ci produiront des arrangements dans un seul seau. Pour ce problème, j'ai besoin d'arrangements sur plusieurs compartiments.
Examen de la sortie de la requête standard "toutes combinaisons"
J'ai noté que les résultats formaient le même schéma que le tableau A. En faisant le saut congnitif de considérer chaque colonne comme un ordre 1 , les valeurs pour dire quel camion contiendra cet ordre, et une ligne pour être un arrangement des ordres dans les camions. La requête devient alors
En étendant cela pour couvrir les quatorze ordres dans les données d'exemple, et en simplifiant les noms, nous obtenons ceci:
Je choisis de conserver les résultats intermédiaires dans des tableaux temporaires pour plus de commodité.
Les étapes suivantes seront beaucoup plus faciles si les données sont d'abord UNPIVOTED.
Les poids peuvent être introduits en se joignant à la table Commandes.
Il est maintenant possible de répondre à la question en trouvant le ou les arrangements qui présentent la plus petite différence entre les camions les plus chargés et les moins chargés
Discussion
Il y a beaucoup de problèmes avec cela. C'est d'abord un algorithme de force brute. Le nombre de lignes dans les tables de travail est exponentiel dans le nombre de camions et de commandes. Le nombre de lignes dans #Arrangements est (nombre de camions) ^ (nombre de commandes). Cela n'évolue pas bien.
Deuxièmement, les requêtes SQL contiennent le nombre de commandes incorporées. Le seul moyen de contourner ce problème est d'utiliser le SQL dynamique, qui a ses propres problèmes. Si le nombre de commandes est dans les milliers, il peut arriver un moment où le SQL généré devient trop long.
Troisièmement, la redondance des dispositions. Cela gonfle énormément les tables intermédiaires, ce qui augmente considérablement l'exécution.
Quatrièmement, de nombreuses lignes dans #Arrangements laissent un ou plusieurs camions vides. Cela ne peut pas être la configuration optimale. Il serait facile de filtrer ces lignes lors de la création. J'ai choisi de ne pas le faire pour garder le code plus simple et ciblé.
Du côté positif, cela gère les poids négatifs, si votre entreprise devait commencer à expédier des ballons d'hélium remplis!
Pensées
S'il y avait un moyen de remplir #FilledTrucks directement à partir de la liste des camions et des commandes, je pense que la pire de ces préoccupations serait gérable. Malheureusement, mon imagination a trébuché sur cet obstacle. J'espère qu'un futur contributeur pourra peut-être fournir ce qui m'a échappé.
1 Vous dites que tous les articles d'une commande doivent se trouver sur le même camion. Cela signifie que l'atome d'affectation est l'Ordre, pas l'OrdreDétail. J'ai généré ceux-ci à partir de vos données de test ainsi:
Cela ne fait aucune différence, que nous étiquetions les articles en question «Commande» ou «CommandeDétail», la solution reste la même.
la source
En regardant vos besoins réels (qui, je suppose, visent à équilibrer votre charge de travail sur un ensemble de processeurs) ...
Y a-t-il une raison pour laquelle vous devez pré-affecter des processus à des compartiments / processeurs spécifiques? [Essayer de comprendre vos besoins réels ]
Pour votre exemple de «mise à jour des statistiques», comment savez-vous combien de temps prendra une opération particulière? Que se passe-t-il si une opération donnée rencontre un retard inattendu (par exemple, une fragmentation de la table / de l'index plus que prévu / excessive, l'utilisateur txn de longue durée bloque une opération de «mise à jour des statistiques»)?
À des fins d'équilibrage de charge, je génère généralement la liste des tâches (par exemple, la liste des tables pour lesquelles les statistiques sont mises à jour) et je place cette liste dans une table (temporaire / temporaire).
La structure de la table peut être modifiée selon vos besoins, par exemple:
Ensuite, je lance X nombre de processus simultanés pour effectuer les opérations de mise à jour des statistiques, chaque processus effectuant les opérations suivantes:
tasks
table (garantit qu'aucune tâche n'est récupérée par plus d'un processus; devrait être un verrou de courte durée)start = NULL
(«la première» serait déterminée par vous, par exemple, commander parpriority
?)start = getdate(), thread = <process_number>
id
ettarget/command
valeurstarget
(alternativement, exécutercommand
) et une fois terminé ...tasks
à jour avecend = getdate() where id = <id>
Avec la conception ci-dessus, j'ai maintenant une opération équilibrée dynamiquement (principalement).
REMARQUES:
tasks
tasks
table doit fournir d'autres avantages, par exemple, un historique des temps d'exécution que vous pouvez archiver pour référence future, un historique des temps d'exécution qui peut être utilisé pour modifier les priorités, fournir un état des opérations en cours, etc.tasks
puisse sembler un peu excessif, gardez à l'esprit que nous devons planifier le problème potentiel de 2 (ou plus) processus tentant d'obtenir une nouvelle tâche en même temps , nous devons donc garantir une tâche est affecté à un seul processus (et oui, vous pouvez obtenir les mêmes résultats avec une instruction combinée «mise à jour / sélection» - selon les capacités du langage SQL de votre SGBDR); l'étape d'obtention d'une nouvelle «tâche» devrait être rapide, c'est-à-dire que le «verrou exclusif» devrait être de courte durée et en réalité, les processus se produironttasks
de manière assez aléatoire et seront donc peu bloquants de toute façonPersonnellement, je trouve ce
tasks
processus piloté par table un peu plus facile à mettre en œuvre et à maintenir ... par opposition à un processus (généralement) plus complexe d'essayer de pré-assigner des mappages de tâches / processus ... ymmv.Évidemment, pour votre exemple imaginaire, vous ne pouvez pas faire revenir vos camions à la distribution / entrepôt pour la prochaine commande, vous devez donc pré-affecter vos commandes à divers camions (en gardant à l'esprit qu'UPS / Fedex / etc. doivent également attribution en fonction des itinéraires de livraison afin de réduire les délais de livraison et la consommation de gaz).
Cependant, dans votre exemple réel (`` mise à jour des statistiques ''), il n'y a aucune raison pour que les affectations de tâches / processus ne puissent pas être effectuées de manière dynamique, ce qui garantit une meilleure chance d'équilibrer la charge de travail (sur tous les processeurs et en termes de réduction du temps d'exécution global) .
REMARQUE: je vois régulièrement des personnes (IT) essayer de pré-assigner leurs tâches (comme une forme d'équilibrage de charge) avant d'exécuter lesdites tâches, et dans tous les cas, il / elle finit par devoir constamment ajuster le processus de pré-affectation pour prendre en tenant compte des problèmes de tâches qui varient constamment (par exemple, le niveau de fragmentation dans la table / l'index, l'activité simultanée des utilisateurs, etc.).
la source
créer et remplir la table des nombres comme vous le souhaitez. Il s'agit d'une création unique.
Table de camion créée
J'ai créé une
OrderSummary
tableVeuillez vérifier ma valeur Delta et faites-moi savoir si elle est erronée
Vous pouvez vérifier le résultat de CTE1, tout est possible
Permutation and Combination of order along with their size
.Si mon approche est correcte jusqu'ici, alors j'ai besoin de l'aide de quelqu'un.
filtrer et diviser le résultat de
CTE1
dans à 3 parties (Truck count
) de telle sorte qu'ellesOrderid
soient uniques parmi chaque groupe et chaque partie TruckOrderSize
est proche de Delta.la source