Ich beschreibe das Problem, indem ich eine feste Anzahl von LKWs mit Bestellungen so gleichmäßig wie möglich belade.
Eingänge:
@TruckCount - the number of empty trucks to fill
Ein Satz:
OrderId,
OrderDetailId,
OrderDetailSize,
TruckId (initially null)
Orders
bestehen aus einem oder mehreren OrderDetails
.
Hier besteht die Herausforderung darin, TruckId
jedem Datensatz ein zuzuweisen .
Eine einzelne Bestellung kann nicht auf mehrere Lkw aufgeteilt werden.
LKWs sollten möglichst gleichmäßig * beladen sein, gemessen an sum(OrderDetailSize)
.
* Gleichmäßig: Das kleinste erreichbare Delta zwischen dem am wenigsten beladenen und dem am meisten beladenen LKW. Nach dieser Definition ist 1,2,3 gleichmäßiger verteilt als 1,1,4. Wenn es hilft, tun Sie so, als wären Sie ein Statistikalgorithmus, der Histogramme mit gleichmäßiger Höhe erstellt.
Es wird keine maximale LKW-Ladung berücksichtigt. Dies sind magische elastische Lastwagen. Die Anzahl der LKWs ist jedoch festgelegt.
Es gibt offensichtlich eine iterative Lösung: Round-Robin-Zuweisung von Befehlen.
Aber kann es als satzbasierte Logik durchgeführt werden?
Mein Hauptinteresse gilt SQL Server 2014 oder höher. Aber auch Set-basierte Lösungen für andere Plattformen könnten interessant sein.
Das fühlt sich an wie Itzik Ben-Gan Gebiet :)
Meine reale Anwendung verteilt eine Verarbeitungsauslastung auf eine Anzahl von Buckets, die der Anzahl der logischen CPUs entsprechen. Daher hat jeder Eimer keine maximale Größe. Insbesondere Statistik-Updates. Ich dachte nur, dass es mehr Spaß macht, das Problem in Lastwagen zusammenzufassen, um die Herausforderung zu formulieren.
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
quelle
Antworten:
Mein erster Gedanke war
Der Teil "Beste Lösung" wird in der Frage definiert - der kleinste Unterschied zwischen den am meisten beladenen und den am wenigsten beladenen Lastwagen. Das andere Stück - alle Kombinationen - ließ mich nachdenken.
Stellen Sie sich eine Situation vor, in der wir drei Aufträge A, B und C sowie drei Lastwagen haben. Die Möglichkeiten sind
Viele davon sind symmetrisch. Die ersten sechs Zeilen unterscheiden sich beispielsweise nur darin, in welchem Lkw jede Bestellung aufgegeben wird. Da die Lastwagen fungibel sind, erzielen diese Arrangements das gleiche Ergebnis. Ich werde das jetzt ignorieren.
Es sind Abfragen zum Erzeugen von Permutationen und Kombinationen bekannt. Diese werden jedoch Anordnungen innerhalb eines einzelnen Eimers erzeugen. Für dieses Problem brauche ich Vereinbarungen über mehrere Eimer.
Betrachtet man die Ausgabe der Standardabfrage "Alle Kombinationen"
Ich bemerkte, dass die Ergebnisse dasselbe Muster wie in Tabelle A bildeten. Indem ich den Gesamtsprung machte, jede Spalte als eine Bestellung 1 zu betrachten , die Werte , die besagen, welcher LKW diese Bestellung enthält, und eine Reihe , die eine Anordnung von Bestellungen innerhalb von LKWs darstellt. Die Abfrage wird dann
Erweitern Sie dies, um die vierzehn Befehle in den Beispieldaten abzudecken, und vereinfachen Sie die Namen, die wir erhalten:
Ich halte die Zwischenergebnisse der Einfachheit halber in temporären Tabellen.
Nachfolgende Schritte werden viel einfacher, wenn die Daten zuerst UNPIVOTED sind.
Gewichte können durch Hinzufügen zur Tabelle "Bestellungen" eingegeben werden.
Die Frage kann nun beantwortet werden, indem die Anordnung (en) gefunden werden, die den geringsten Unterschied zwischen am meisten beladenen und am wenigsten beladenen Lastwagen aufweisen
Diskussion
Es gibt sehr viele Probleme damit. Erstens ist es ein Brute-Force-Algorithmus. Die Anzahl der Zeilen in den Arbeitstabellen ist in Bezug auf die Anzahl der Lastkraftwagen und Aufträge exponentiell. Die Anzahl der Zeilen in #Arrangements ist (Anzahl der Lastwagen) ^ (Anzahl der Bestellungen). Dies wird nicht gut skalieren.
Zweitens ist in den SQL-Abfragen die Anzahl der Bestellungen eingebettet. Der einzige Weg, dies zu umgehen, ist die Verwendung von dynamischem SQL, das seine eigenen Probleme hat. Wenn die Anzahl der Bestellungen in Tausenden liegt, kann es vorkommen, dass die generierte SQL zu lang wird.
Drittens ist die Redundanz in den Vereinbarungen. Dadurch werden die Zwischentabellen aufgebläht und die Laufzeit erheblich erhöht.
Viertens lassen viele Zeilen in #Arrangements einen oder mehrere Lastwagen leer. Dies kann unmöglich die optimale Konfiguration sein. Es wäre einfach, diese Zeilen bei der Erstellung herauszufiltern. Ich habe beschlossen, dies nicht zu tun, um den Code einfacher und fokussierter zu halten.
Auf der anderen Seite können negative Gewichte verarbeitet werden, falls Ihr Unternehmen jemals mit dem Versand gefüllter Heliumballons beginnen sollte!
Gedanken
Wenn es eine Möglichkeit gäbe, #FilledTrucks direkt aus der Liste der LKWs und Aufträge zu übernehmen, wären die schlimmsten dieser Bedenken meines Erachtens beherrschbar. Leider stolperte meine Vorstellungskraft über diese Hürde. Ich hoffe, dass ein zukünftiger Mitwirkender das liefern kann, was mir entgangen ist.
1 Sie sagen, dass sich alle Artikel für eine Bestellung auf demselben LKW befinden müssen. Dies bedeutet, dass das Zuweisungsatom der Auftrag und nicht der Auftragsdetail ist. Ich habe diese aus Ihren Testdaten so generiert:
Es macht jedoch keinen Unterschied, ob wir die betreffenden Artikel mit "Bestellung" oder "BestellDetail" kennzeichnen, die Lösung bleibt gleich.
quelle
Ein Blick auf Ihre Anforderungen aus der realen Welt (von denen ich annehme, dass es sich um den Versuch handelt, Ihre Arbeitslast über eine Reihe von CPUs zu verteilen) ...
Gibt es einen Grund, warum Sie Prozesse bestimmten Buckets / CPUs vorab zuweisen müssen? [Der Versuch, Ihre tatsächlichen Anforderungen zu verstehen ]
Woher wissen Sie, wie lange ein bestimmter Vorgang in Anspruch nehmen wird, wenn Sie ein Beispiel für die Aktualisierung von Statistiken verwenden? Was passiert, wenn eine bestimmte Operation in eine unerwartete Verzögerung gerät (z. B. mehr als geplante / übermäßige Fragmentierung von Tabelle / Index, lang andauernder Benutzer txn blockiert eine 'Statistikaktualisierungs'-Operation)?
Zu Lastenausgleichszwecken generiere ich normalerweise die Liste der Aufgaben (z. B. Liste der Tabellen, deren Statistiken aktualisiert werden sollen) und platziere diese Liste in einer (temporären / temporären) Tabelle.
Die Struktur der Tabelle kann nach Ihren Wünschen angepasst werden, zB:
Als nächstes starte ich X gleichzeitige Prozesse, um die eigentlichen "Statistiken aktualisieren" -Operationen auszuführen, wobei jeder Prozess Folgendes ausführt:
tasks
Tisch (stellt sicher, dass keine Aufgabe von mehr als einem Prozess übernommen wird; sollte eine relativ kurzlebige Sperre sein)start = NULL
('first' würde von dir bestimmt, zB order bypriority
?)start = getdate(), thread = <process_number>
id
undtarget/command
wertetarget
Führen Sie die gewünschte Operation gegen (alternativ ausführencommand
) und wenn Sie fertig sind ...tasks
mitend = getdate() where id = <id>
Mit dem obigen Design habe ich jetzt einen dynamisch (größtenteils) ausgeglichenen Betrieb.
ANMERKUNGEN:
tasks
tasks
Tabelle sollte weitere Vorteile bieten, z. B. einen Verlauf von Laufzeiten, die Sie zur späteren Bezugnahme archivieren können, einen Verlauf von Laufzeiten, mit denen Prioritäten geändert werden können, einen Status aktueller Vorgänge uswtasks
übermäßigen ein wenig erscheinen mag, bedenken wir haben für das potenzielle Problem Plan von 2 (oder mehr) Prozessen versuchen , eine neue Aufgabe zu erhalten exakt zur selben Zeit , so dass wir eine Aufgabe gewährleisten , müssen ist nur einem Prozess zugeordnet (und ja, Sie können die gleichen Ergebnisse mit einer kombinierten 'update / select'-Anweisung erzielen - abhängig von den SQL-Sprachfähigkeiten Ihres RDBMS). Der Schritt des Erhaltens einer neuen "Aufgabe" sollte schnell sein, dh die "exklusive Sperre" sollte von kurzer Dauer sein und in Wirklichkeit werden die Prozessetasks
in einer ziemlich zufälligen Weise ablaufen, so dass sowieso wenig blockiert wirdPersönlich finde ich diesen
tasks
tabellengesteuerten Prozess ein bisschen einfacher zu implementieren und zu warten ... im Gegensatz zu einem (normalerweise) komplexeren Prozess, bei dem versucht wird, Aufgaben- / Prozesszuordnungen vorab zuzuweisen ... ymmv.Offensichtlich für Ihr Make glauben Beispiel Sie nicht Ihre Lastwagen geht zurück auf die Verteilung / Lager für den nächsten Auftrag haben, so dass Sie benötigen , Ihre Aufträge zu verschiedenen Lkw vorbelegen ( wenn man bedenkt, dass UPS / Fedex / etc haben auch anhand der Lieferwege zuordnen, um Lieferzeiten und Gasverbrauch zu reduzieren).
In Ihrem Beispiel aus der realen Welt ("Aktualisierung der Statistiken") gibt es jedoch keinen Grund, warum die Aufgaben- / Prozesszuweisungen nicht dynamisch ausgeführt werden können, wodurch eine bessere Chance für die Verteilung der Arbeitslast (über den gesamten Prozessor und im Hinblick auf die Reduzierung der Gesamtlaufzeit) gewährleistet wird. .
HINWEIS: Ich routinemäßig sehen (IT) versuchen , die Leute ihre Aufgaben zur Vorbelegung (als eine Form der Load - Balancing) , bevor sie tatsächlich Aufgaben der ausgeführt wird , und in jedem Fall er / sie endet mit bis zu ständig die Vorbelegung Prozess optimieren zu nehmen Berücksichtigung ständig wechselnder Aufgabenbereiche (z. B. Fragmentierungsgrad in Tabelle / Index, gleichzeitige Benutzeraktivität usw.).
quelle
Erstellen und füllen Sie die Nummerntabelle nach Ihren Wünschen. Dies ist nur eine einmalige Erstellung.
Erstellt LKW-Tabelle
Ich habe eine
OrderSummary
Tabelle erstelltBitte überprüfen Sie meinen Delta-Wert und teilen Sie mir mit, ob er falsch ist
Sie können das Ergebnis von CTE1 überprüfen, es hat alles möglich
Permutation and Combination of order along with their size
.Wenn mein Ansatz bis hier richtig ist, dann brauche ich jemanden, der mir hilft.
filter and Divide result von
CTE1
in bis 3 part (Truck count
), so dassOrderid
es für jede Gruppe einzigartig ist und jeder Teil TruckOrderSize
in der Nähe von Delta liegt.quelle