最近我有以下要求。有两个表。
-- lots of items
declare @items table(id varchar(10), pieces integer)
-- boxes
declare @boxes table(num varchar(10), capacity integer)
insert @items(id, pieces)
select 'l1', 5
union all select 'l2', 12
union all select 'l3', 8
insert @boxes(num, capacity)
select 'o1',2
union all select 'o2', 8
union all select 'o3', 2
union all select 'o4', 5
union all select 'o5', 9
union all select 'o6', 5
-- list all pairs of items-boxes. So that item will be put in what order
-- example: o1-l1, o2-l1, o2-l2, o3-l2, ...
请让我解释一下当务之急: 有两张桌子。一个带有另一个带盒子的物品。我们需要通过以下方式将所有项目放入框中:
我们拿第一项l1和第一个方框o1。项目l1有5个,盒子o1容量为2.我们只能在盒子o1中放入最多2个。所以我们创建了第一行:
o1-l1
我们已经填满了o1。移动到下一个框o2。它的容量为8,我们有项目l1,剩下3件。将l1的左侧部分放入框o2中,因此我们创建第二条记录:
o2-l1
我们已将项目l1的所有部分放入框中。转到下一个项目l2。它有12件。我们在盒子o2中剩下5个容量。所以我们将5个l2放入o2并创建下一个记录:
o2-l2
然后我们按顺序拍摄下一个框并创建以下记录:
o3-l2
这样我们就会生成行,直到我们将所有项目“放”到框中。 生成的查询应该类似于:
o1-l1
o2-l1
o2-l2
o3-l2
...
使用CURSOR和东西在T-SQL中可以解决必要的方法,这在性能上并不好。是否有任何可以生成所需输出的SQL查询?
答案 0 :(得分:1)
Ilya Sh,我无法确定你自己的问题答案是否正确。但这是我的方法。
最初我认为这可以通过递归查询解决,但是所有框的大小相等且保证大于项目,并且项目是不可分割的但是大小不同(所以关于每个项目的唯一决定是它是否可以打包在当前框中,或者必须进入下一个框。)
在这种情况下,我们有一组项目(即指定项目类型和数量的行),其中项目大小相等,但这些组可以整除,因此每个项目组可以分布在多个项目上。框,每个框可能包含多个项目组的部分,在框和项目组之间的多对多关系中。
我的想法是,根据容量,每个盒子都有许多单独的插槽和#34; (即一定的空间)可以接收单个物品。
我接触解决方案的方法是使用"数字表"将每个盒子/项目组的数量扩展到单个盒子位置和单个项目 - 每个盒子一行一行,每个项目一行。在我的机器上,我有一个名为zx_numbers的表 - 但是我在下面包含了代码,为了便于说明,它消除了对该表的依赖。
一旦我们以这种方式规范化数据 - 通过将盒子扩展到各自的插槽中,并将项目组和汇总数量扩展到单个项目中 - 整个批次中的每个盒子插槽和项目按顺序编号,然后两个简单地加入了序列号。
我使用了FULL OUTER JOIN
来保存不匹配的广告位/项目。这为我们提供了一个非常通用且适应性强的问题解决方案,然后我们可以通过各种方式进一步处理以获取我们想要的特定数据(在这种情况下,只是盒子项组组合的摘要)。
我目前编写查询的方式是,未填充空格的框(或在所有框完全填充后留下余数的项目组)将保留在结果中,并放在最后,但这些可以是如果不需要过滤。
WITH
item_groups(item_group_id, group_qty) AS
(
select 'l1', 5
union all select 'l2', 12
union all select 'l3', 8
--union all select 'l4', 8
)
,boxes(box_id, capacity) AS
(
select 'o1',2
union all select 'o2', 8
union all select 'o3', 2
union all select 'o4', 5
union all select 'o5', 9
union all select 'o6', 5
)
,zx_numbers(zx_number) AS
(
--SELECT * FROM dbo.zx_numbers
--I have a dedicated numbers table on my machine, but I've substituted a
--manual sequence generator for the purposes of a self-contained demonstration
SELECT
(ones.n) + (10 * tens.n) + (100 * hundreds.n) AS zx_number
FROM --range 0 to 999
(VALUES (0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) AS ones(n)
,(VALUES (0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) AS tens(n)
,(VALUES (0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) AS hundreds(n)
)
,items AS
(
SELECT
item_groups.*
,zx_number AS group_item_number
,ROW_NUMBER() OVER (ORDER BY item_group_id, zx_number) AS batch_item_number
FROM
item_groups
INNER JOIN
zx_numbers
ON (zx_number BETWEEN 1 AND item_groups.group_qty)
)
,box_slots AS
(
SELECT
boxes.*
,zx_number AS box_slot_number
,ROW_NUMBER() OVER (ORDER BY box_id, zx_number) AS batch_slot_number
FROM
boxes
INNER JOIN
zx_numbers
ON (zx_number BETWEEN 1 AND boxes.capacity)
)
,box_item_matches AS
(
SELECT
COALESCE(bxsl.batch_slot_number, itms.batch_item_number) AS slot_number
,bxsl.box_id
,bxsl.capacity
,bxsl.box_slot_number
,itms.item_group_id
,itms.group_qty
,itms.group_item_number
FROM
box_slots AS bxsl
FULL OUTER JOIN
items AS itms
ON (bxsl.batch_slot_number = itms.batch_item_number)
)
--SELECT * FROM box_item_matches
SELECT
box_id
,item_group_id
FROM
box_item_matches
GROUP BY
box_id, item_group_id
ORDER BY
IIF(box_id IS NULL OR item_group_id IS NULL, 1, 0) --i.e. NULLS LAST
,box_id
,item_group_id