我的第一个念头是
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.
我注意到结果形成了与表A相同的模式。通过认知上的飞跃,将每列视为订单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.
将其扩展为涵盖示例数据中的十四个Order,并简化名称,我们得到以下信息:
;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
讨论区
这有很多问题。首先,它是蛮力算法。工作表中的行数与卡车和订单数成指数关系。#Arrangements中的行数为(卡车数)^(订单数)。这将无法很好地扩展。
其次,SQL查询中嵌入了订单数。解决此问题的唯一方法是使用动态SQL,它有其自身的问题。如果订单数量成千上万,则可能会出现生成的SQL太长的情况。
第三是安排上的冗余。这使中间表膨胀,极大地增加了运行时间。
第四,#Arrangements中的许多行将一辆或多辆卡车空着。这可能不是最佳配置。创建时很容易过滤掉这些行。我选择不这样做是为了使代码更简单,更集中。
从好的方面来说,如果您的企业开始装运填充的氦气球,这将减轻负重!
思想
如果有办法直接从卡车和订单清单中填充#FilledTrucks,我认为这些问题中最糟糕的是可以解决的。可悲的是,我的想象力跌落了那个障碍。我希望将来的一些贡献者能够提供我所无法企及的东西。
1您说订单的所有物品都必须在同一辆卡车上。这意味着分配的原子是Order,而不是OrderDetail。我是从您的测试数据生成这些的,因此:
select
OrderId,
Size = sum(OrderDetailSize)
into #Order
from #OrderDetail
group by OrderId;
但是,无论我们将问题标记为“订单”还是“ OrderDetail”都没有关系,解决方案保持不变。