解决Bin Packing sql问题

时间:2009-09-30 23:09:18

标签: sql

我在sql中遇到问题,我需要从事务列表中生成一个装箱单。

数据模型

交易存储在包含以下内容的表格中:

  • 交易ID
  • item id
  • 料品数量

每个事务可以有多个项目(巧合的是多个行具有相同的事务ID)。然后每个项目的数量从1到N.

业务问题

业务要求我们创建一个装箱单,其中装箱单中的每个项目都包含方框中每个项目的计数。

每个盒子只能包含160个物品(它们的大小/重量都相同)。根据订单的总数,我们需要将项目拆分到不同的框中(有时甚至将单个项目的集合拆分为两个框)

因此,挑战在于采用该数据模式并提出结果集,其中包括每个项目中每个项目的数量。

我目前正以一些不那么漂亮的方式强行推动这一点,并想知道是否有人有一个我忽略的优雅/简单的解决方案。

示例输入/输出

我们确实需要隔离每个项目中每个项目的数量...例如:

订单1:

  • 项目A中的100项
  • <项目B中的100项项目C的
  • 140

这应该在结果集中产生三行:

  • 方框1:A(100),B(60)
  • 方框2:B(40),C(120)
  • 方框3:C(20)


理想情况下,查询将足够聪明以将所有C放在一起,但此时 - 我们并不太关心它。

5 个答案:

答案 0 :(得分:3)

你读过包装箱吗?

https://sqlserverfast.com/?s=bin+packing

答案 1 :(得分:2)

这样的东西
SELECT SUM([Item quantity]) as totalItems
     , SUM([Item quantity]) / 160 as totalBoxes
     , MOD(SUM([Item Quantity), 160) amountInLastBox
FROM [Transactions]
GROUP BY [Transaction Id]

让我知道您正在寻找的结果集中的哪些字段,我可以想出一个更好的字段

答案 2 :(得分:1)

我正在寻找类似的东西,我所能做到的只是将行扩展到事务中的项目计数,并将它们分组到容器中。虽然不是很优雅..而且,因为字符串聚合在SQL Server中仍然非常麻烦(Oracle,我想念你!),我不得不留下最后一部分。我的意思是把计数放在一行......

我的解决方案如下:

交易表示例:

INSERT INTO transactions
(trans_id, item, cnt) VALUES
('1','A','50'), 
('2','A','140'), 
('3','B','100'), 
('4','C','80');
GO

创建一个虚拟序列表,其中包含1到1000之间的数字(我假设单个事务中项目允许的最大数量为1000):

CREATE TABLE numseq (n INT NOT NULL IDENTITY) ;
GO
INSERT numseq DEFAULT VALUES ;
WHILE SCOPE_IDENTITY() < 1000 INSERT numseq DEFAULT VALUES ;
GO

现在我们可以从事务表中生成一个临时表,其中每个事务和项在子查询中存在“cnt”次,然后使用除法给bin分配数字,并按bin分组号:

SELECT bin_nr, item, count(*) count_in_bin
INTO result
FROM (
  SELECT t.item, ((row_number() over (order by t.item, s.n) - 1) / 160) + 1 as bin_nr
  FROM transactions t 
  INNER JOIN numseq s
  ON t.cnt >= s.n -- join conditionally to repeat transaction rows "cnt" times
) a
GROUP BY bin_id, item
ORDER BY bin_id, item
GO

结果是:

bin_id item count_in_bin
1      A    160
2      A    30
2      B    100
2      C    30
3      C    50

在Oracle中,最后一步就是这样简单:

SELECT bin_id, WM_CONCAT(CONCAT(item,'(',count_in_bin,')')) contents
FROM result
GROUP BY bin_id

答案 3 :(得分:1)

答案 4 :(得分:0)

这不是最漂亮的答案,但我使用类似的方法通过订单流程跟踪库存商品,这很容易理解,并可能导致您开发出比我更好的方法。

我会创建一个名为“PackedItem”的表或类似的东西。列将是:

packed_item_id (int) - Primary Key, Identity column
trans_id (int)
item_id (int)
box_number (int)

此表中的每条记录代表您将发运的1个物理单位。

让我们说有人在事务4中添加第20行第20项,我会在PackedItem表中添加20条记录,所有记录都包含事务ID,Item ID和NULL框号。如果更新了一行,则需要在PackedItem表中添加或删除记录,以便始终保持1:1的相关性。

到货的时候,你可以简单地

SELECT TOP 160 FROM PackedItem WHERE trans_id = 4 AND box_number IS NULL

并将这些记录的box_number设置为下一个可用的框号,直到box_number为NULL时没有记录。这可以在WHILE循环中使用一个相当复杂的UPDATE语句 - 我没有时间完全构造。

现在,您可以通过查询此表轻松获得所需的装箱单:

SELECT box_number, item_id, COUNT(*) AS Qty
FROM PackedItem
WHERE trans_id = 4
GROUP BY box_number, item_id

优点 - 易于理解,易于实施。 陷阱 - 如果表与事务中的行不同步,最终结果可能是错误的;该表将在其中获得许多记录,并将为服务器提供额外的工作。需要对每个ID字段进行索引以保持良好的性能。