Моя перша думка була
select
<best solution>
from
<all possible combinations>
Частина "найкращого рішення" визначена у питанні - найменша різниця між найбільш завантаженими та найменш завантаженими вантажівками. Інший шматочок - всі комбінації - викликав у мене паузу для роздумів.
Розглянемо ситуацію, коли у нас є три замовлення A, B і C і три вантажівки. Можливості є
Truck 1 Truck 2 Truck 3
------- ------- -------
A B C
A C B
B A C
B C A
C A B
C B A
AB C -
AB - C
C AB -
- AB C
C - AB
- C AB
AC B -
AC - B
B AC -
- AC B
B - AC
- B AC
BC A -
BC - A
A BC -
- BC A
A - BC
- A BC
ABC - -
- ABC -
- - ABC
Table A: all permutations.
Багато з них симетричні. Наприклад, перші шість рядків відрізняються лише тим, на яку вантажівку розміщено кожне замовлення. Оскільки вантажні автомобілі підлягають зміні, ці домовленості дадуть такий же результат. Я зараз ігнорую це.
Відомі запити для створення перестановок та комбінацій. Однак вони створюють домовленості в межах одного відра. Для цієї проблеми мені потрібні домовленості в декількох відрах.
Дивлячись на вихід із стандартного запиту "всі комбінації"
;with Numbers as
(
select n = 1
union
select 2
union
select 3
)
select
a.n,
b.n,
c.n
from Numbers as a
cross join Numbers as b
cross join Numbers as c
order by 1, 2, 3;
n n n
--- --- ---
1 1 1
1 1 2
1 1 3
1 2 1
<snip>
3 2 3
3 3 1
3 3 2
3 3 3
Table B: cross join of three values.
Я зазначив, що результати формували ту саму схему, що і Таблиця А. Здійснюючи спільний стрибок, розглядаючи кожен стовпець як Порядок 1 , значення говорять про те, який вантажівка буде приймати цей Орден, а ряд - це розташування ордерів у вантажних автомобілях. Потім запит стає
select
Arrangement = ROW_NUMBER() over(order by (select null)),
First_order_goes_in = a.TruckNumber,
Second_order_goes_in = b.TruckNumber,
Third_order_goes_in = c.TruckNumber
from Trucks a -- aka Numbers in Table B
cross join Trucks b
cross join Trucks c
Arrangement First_order_goes_in Second_order_goes_in Third_order_goes_in
----------- ------------------- -------------------- -------------------
1 1 1 1
2 1 1 2
3 1 1 3
4 1 2 1
<snip>
Query C: Orders in trucks.
Розширивши це, щоб охопити чотирнадцять порядків у прикладі даних та спростити імена, ми отримаємо це:
;with Trucks as
(
select *
from (values (1), (2), (3)) as T(TruckNumber)
)
select
arrangement = ROW_NUMBER() over(order by (select null)),
First = a.TruckNumber,
Second = b.TruckNumber,
Third = c.TruckNumber,
Fourth = d.TruckNumber,
Fifth = e.TruckNumber,
Sixth = f.TruckNumber,
Seventh = g.TruckNumber,
Eigth = h.TruckNumber,
Ninth = i.TruckNumber,
Tenth = j.TruckNumber,
Eleventh = k.TruckNumber,
Twelth = l.TruckNumber,
Thirteenth = m.TruckNumber,
Fourteenth = n.TruckNumber
into #Arrangements
from Trucks a
cross join Trucks b
cross join Trucks c
cross join Trucks d
cross join Trucks e
cross join Trucks f
cross join Trucks g
cross join Trucks h
cross join Trucks i
cross join Trucks j
cross join Trucks k
cross join Trucks l
cross join Trucks m
cross join Trucks n;
Query D: Orders spread over trucks.
Я вирішу провести проміжні результати у тимчасових таблицях для зручності.
Подальші кроки стануть набагато простішими, якщо дані спочатку НЕ ПОВЕРНЕННІ.
select
Arrangement,
TruckNumber,
ItemNumber = case NewColumn
when 'First' then 1
when 'Second' then 2
when 'Third' then 3
when 'Fourth' then 4
when 'Fifth' then 5
when 'Sixth' then 6
when 'Seventh' then 7
when 'Eigth' then 8
when 'Ninth' then 9
when 'Tenth' then 10
when 'Eleventh' then 11
when 'Twelth' then 12
when 'Thirteenth' then 13
when 'Fourteenth' then 14
else -1
end
into #FilledTrucks
from #Arrangements
unpivot
(
TruckNumber
for NewColumn IN
(
First,
Second,
Third,
Fourth,
Fifth,
Sixth,
Seventh,
Eigth,
Ninth,
Tenth,
Eleventh,
Twelth,
Thirteenth,
Fourteenth
)
) as q;
Query E: Filled trucks, unpivoted.
Ваги можна ввести, приєднавшись до таблиці Замовлення.
select
ft.arrangement,
ft.TruckNumber,
TruckWeight = sum(i.Size)
into #TruckWeights
from #FilledTrucks as ft
inner join #Order as i
on i.OrderId = ft.ItemNumber
group by
ft.arrangement,
ft.TruckNumber;
Query F: truck weights
Тепер на це питання можна відповісти, знайшовши домовленості, які мають найменшу різницю між найбільш завантаженими та найменш завантаженими вантажівками
select
Arrangement,
LightestTruck = MIN(TruckWeight),
HeaviestTruck = MAX(TruckWeight),
Delta = MAX(TruckWeight) - MIN(TruckWeight)
from #TruckWeights
group by
arrangement
order by
4 ASC;
Query G: most balanced arrangements
Обговорення
З цим існує дуже багато проблем. По-перше, це алгоритм грубої сили. Кількість рядків у робочих столах експоненціальна у кількості вантажних автомобілів та замовлень. Кількість рядків у # Домовленостях (кількість вантажівок) ^ (кількість замовлень). Це не буде добре масштабуватися.
По-друге, те, що запити SQL мають кількість вкладених у них замовлень. Єдиний спосіб цього - використовувати динамічний SQL, який має власні проблеми. Якщо кількість замовлень становить тисячі, може настати час, коли згенерований SQL стане занадто довгим.
Третє - це надмірність у домовленостях. Це сильно збільшує проміжні таблиці, збільшуючи час виконання.
По-четверте, багато рядків у # Домовленостях залишають порожнім один або кілька вантажівок. Це не може бути оптимальною конфігурацією. Було б легко відфільтрувати ці рядки під час створення. Я вирішив не робити цього, щоб зробити код простішим та зосередженим.
З верхньої сторони це обробляє негативні ваги, якщо ваше підприємство коли-небудь почне перевозити наповнені гелієм повітряні кулі!
Думки
Якби був спосіб заповнити #FilledTrucks безпосередньо зі списку вантажівок та замовлень, я думаю, що найгірше з цих проблем було б керованим. На жаль, моє уявлення натрапило на цю перешкоду. Я сподіваюсь, що якийсь майбутній співробітник, можливо, зможе поставити те, що мені ухиляється.
1 Ви говорите, що всі товари для замовлення повинні знаходитися на одній вантажівці. Це означає, що атомом присвоєння є Порядок, а не OrderDetail. Я створив це з ваших тестових даних таким чином:
select
OrderId,
Size = sum(OrderDetailSize)
into #Order
from #OrderDetail
group by OrderId;
Немає жодної різниці, однак, якщо ми позначимо питання, що виникають під питанням, "Порядок" чи "Замовлення", рішення залишається тим самим.