关于组合来自两个表的数据的复杂查询

时间:2017-12-28 11:53:45

标签: sql-server tsql sql-server-2012 window-functions

最近我有以下要求。有两个表。

-- 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查询?

1 个答案:

答案 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