Я опишу проблему с точки зрения загрузки заказов на определенное количество грузовых автомобилей как можно более равномерно.
Входы:
@TruckCount - the number of empty trucks to fill
Множество:
OrderId,
OrderDetailId,
OrderDetailSize,
TruckId (initially null)
Orders
состоят из одного или нескольких OrderDetails
.
Задача здесь состоит в том, чтобы назначить TruckId
каждой записи.
Один заказ не может быть разделен на грузовики.
Грузовые автомобили должны быть равномерно загружены * , насколько это возможно, измеряются sum(OrderDetailSize)
.
* Равномерно: Наименьшая достижимая дельта между наименее загруженным грузовиком и наиболее загруженным грузовиком. По этому определению 1,2,3 распределяется более равномерно, чем 1,1,4. Если это поможет, представьте, что вы - алгоритм статистики, создавая гистограммы четной высоты.
Не учитывается максимальная загрузка грузовика. Это волшебные упругие грузовики. Количество грузовиков, однако, является фиксированным.
Существует очевидное решение, которое является итеративным - циклическое распределение заказов.
Но можно ли это сделать как логика, основанная на множестве?
Мой основной интерес для SQL Server 2014 или более поздней версии. Но сетевые решения для других платформ также могут быть интересными.
Это похоже на территорию Ицик Бен-Ган :)
Мое реальное приложение распределяет рабочую нагрузку по нескольким сегментам, чтобы соответствовать количеству логических процессоров. Следовательно, каждое ведро не имеет максимального размера. Статистика обновлений, в частности. Я просто подумал, что было бы забавнее абстрагировать проблему в грузовики как способ сформулировать проблему.
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
источник
Ответы:
Моя первая мысль была
Часть «лучшее решение» определяется в вопросе - наименьшая разница между наиболее загруженными и наименее загруженными грузовиками. Другой бит - все комбинации - заставил меня задуматься.
Рассмотрим ситуацию, когда у нас есть три заказа A, B и C и три грузовика. Возможности
Многие из них симметричны. Например, первые шесть рядов отличаются только тем, в каком грузовике размещен каждый заказ. Поскольку грузовики являются взаимозаменяемыми, эти механизмы будут давать тот же результат. Я буду игнорировать это сейчас.
Есть известные запросы для производства перестановок и комбинаций. Тем не менее, они будут производить мероприятия в одном ведре. Для этой проблемы мне нужны меры по нескольким ведрам.
Просмотр результатов стандартного запроса "все комбинации"
Я отметил, что результаты сформировались по той же схеме, что и таблица A. Сделав резкий скачок, рассматривая каждый столбец как Орден 1 , значения, указывающие , какой грузовик будет удерживать этот Орден, и строку, чтобы обозначить расположение Ордеров внутри грузовых автомобилей. Затем запрос становится
Расширяя это, чтобы охватить четырнадцать Орденов в данных примера, и упрощая имена, мы получаем это:
Для удобства я предпочитаю хранить промежуточные результаты во временных таблицах.
Последующие шаги будут намного проще, если данные сначала НЕИЗВЯЗАНЫ.
Веса можно ввести, присоединившись к таблице заказов.
Теперь можно ответить на вопрос, найдя расположение (я), которые имеют наименьшую разницу между наиболее загруженными и наименее загруженными грузовиками
обсуждение
С этим очень много проблем. Во-первых, это алгоритм перебора. Количество строк в рабочих таблицах экспоненциально по количеству грузовых автомобилей и заказов. Количество строк в # Arrangements составляет (количество грузовиков) ^ (количество заказов). Это не будет хорошо масштабироваться.
Второе - это то, что в запросы SQL включено количество заказов. Единственный способ обойти это - использовать динамический SQL, который имеет свои проблемы. Если количество заказов исчисляется тысячами, может наступить момент, когда сгенерированный SQL станет слишком длинным.
В-третьих, избыточность в договоренностях. Это раздувает промежуточные таблицы, значительно увеличивая время выполнения.
В-четвертых, многие строки в # Arrangements оставляют один или несколько грузовиков пустыми. Это не может быть оптимальной конфигурацией. Было бы легко отфильтровать эти строки при создании. Я решил не делать этого, чтобы сделать код более простым и целенаправленным.
С другой стороны, это работает с отрицательными весами, если ваше предприятие когда-либо начнет поставлять заполненные гелиевые шарики!
мысли
Если бы был способ заполнить #FilledTrucks непосредственно из списка грузовиков и заказов, я думаю, что худшее из этих опасений было бы управляемым. К сожалению, мое воображение наткнулось на это препятствие. Я надеюсь, что какой-нибудь будущий участник сможет предоставить то, что ускользнуло от меня.
1 Вы говорите, что все товары для заказа должны быть на одном грузовике. Это означает, что атом присваивания - это Order, а не OrderDetail. Я сгенерировал их из ваших тестовых данных таким образом:
Не имеет значения, однако, независимо от того, помечаем ли мы элементы в вопросе «Order» или «OrderDetail», решение остается тем же.
источник
Рассматривая ваши требования к реальному миру (я предполагаю, что это попытка сбалансировать вашу рабочую нагрузку с помощью набора процессоров) ...
Есть ли причина, по которой вам нужно предварительно назначать процессы конкретным сегментам / процессорам? [Пытаясь понять ваши реальные требования]
Для вашего примера «обновления статистики», как узнать, сколько времени займет конкретная операция? Что если данная операция сталкивается с неожиданной задержкой (например, более чем запланированная / чрезмерная фрагментация таблицы / индекса, длительный пользователь txn блокирует операцию «обновления статистики»)?
В целях балансировки нагрузки я обычно генерирую список задач (например, список таблиц, для которых должна обновляться статистика) и помещаю указанный список в (временную / временную) таблицу.
Структура таблицы может быть изменена в соответствии с вашими требованиями, например:
Затем я запускаю X число параллельных процессов для выполнения фактических операций «обновления статистики», причем каждый процесс выполняет следующее:
tasks
стол (гарантирует, что задача не будет выбрана более чем одним процессом; должна быть относительно недолговечной блокировкой)start = NULL
('first' будет определяться вами, например, order bypriority
?)start = getdate(), thread = <process_number>
id
иtarget/command
ценитьtarget
(поочередно, запуститьcommand
) и когда закончите ...tasks
сend = getdate() where id = <id>
С вышеупомянутым дизайном я теперь получил динамически (в основном) сбалансированную работу.
ПРИМЕЧАНИЯ:
tasks
tasks
таблицы должен предусматривать другие преимущества, например, историю времени выполнения, которую вы можете архивировать для дальнейшего использования, историю времени выполнения, которая может использоваться для изменения приоритетов, предоставления статуса текущих операций и т. д.tasks
может показаться немного чрезмерной, имейте в виду, что мы должны планировать потенциальную проблему 2 (или более) процессов, пытающихся получить новую задачу в одно и то же время , поэтому мы должны гарантировать задачу присваивается только одному процессу (и да, вы можете получить те же результаты с помощью комбинированного оператора «обновить / выбрать» - в зависимости от возможностей языка вашей РСУБД SQL); шаг к получению новой «задачи» должен быть быстрым, то есть «эксклюзивная блокировка» должна быть недолгой, и в действительности процессы будут срабатыватьtasks
довольно случайным образом, так что в любом случае будет небольшая блокировкаЛично я считаю, что этот
tasks
процесс, управляемый таблицами, немного легче реализовать и поддерживать ... в отличие от (обычно) более сложного процесса попытки предварительно назначить сопоставления задачи / процесса ...Очевидно, что для примера вы не можете заставить свои грузовики возвращаться к распределению / складу для следующего заказа, поэтому вам нужно предварительно назначить свои заказы различным грузовым автомобилям (учитывая, что UPS / Fedex / и т. Д. Также должны назначить на основе маршрутов доставки с целью сокращения сроков доставки и использования газа).
Однако в вашем примере с реальным миром («обновление статистики») нет причин, по которым назначения задач / процессов не могут выполняться динамически, что обеспечивает лучший шанс сбалансировать рабочую нагрузку (по всему процессору и с точки зрения сокращения общего времени выполнения). ,
ПРИМЕЧАНИЕ. Я обычно вижу (ИТ) людей, пытающихся предварительно назначить свои задачи (как форму балансировки нагрузки) перед тем, как фактически запускать указанные задачи, и в каждом случае ему / ей приходится постоянно настраивать процесс предварительного назначения, чтобы выполнить его. принимая во внимание постоянно меняющиеся задачи (например, уровень фрагментации в таблице / индексе, одновременная активность пользователей и т. д.).
источник
создать и заполнить таблицу чисел, как вы хотите. Это только один раз.
Созданный грузовик стол
Я создал одну
OrderSummary
таблицуПожалуйста, проверьте мое значение Delta и дайте мне знать, если это не так
Вы можете проверить результат CTE1, у него есть все возможное
Permutation and Combination of order along with their size
.Если мой подход верен до сих пор, то мне нужна помощь.
фильтруйте и делите результат
CTE1
на 3 части (Truck count
) так, что ониOrderid
уникальны для каждой группы, и каждая часть ТruckOrderSize
находится вблизи дельты.источник