最小传输Sql查询

时间:2017-05-22 18:30:02

标签: sql oracle

我有两张桌子,我需要完成表格Need_qty,而表格1 sending_qty的移动最少。

表1

sending_QTY STORE_ID_A
30           30105
16           21168
10           21032
9            30118
6            30011
5            21190
2            21016

表2

Need_QTY    STORE_ID_B
15           21005
10           30019
11           21006
16           30001
11           21015
7            21004

预期输出

STORE_ID_A |STORE_ID_B |TRANSFERRED_QTY_FROM_A |
-----------|-----------|-----------------------|
30105      |21005      |15                     |
30105      |30019      |10                     |
30105      |21006      |5                      |
21168      |21006      |6                      |
21168      |30001      |10                     |
21032      |30001      |6                      |
21032      |21015      |4                      |
30118      |21015      |7                      |
30118      |21004      |2                      |
30011      |21004      |5                      |

还有其他几种组合来实现这一目标,但我需要找到最小可能的转移,以便表2 need_qty得到满足 有没有办法在没有程序方法的情况下实现这个目标?

到目前为止,我已尝试交叉加入以找到这些组合,但没有多少帮助

1 个答案:

答案 0 :(得分:1)

这可以通过使用包含整数分区的辅助表使用常规SQL来解决。为了这个例子,让我们假设这个辅助表有三列:分区,等级和数字。对于任何给定的数字,将存在可能的分区行,每个分区具有其等级。如果数字为5,则从该表中选择编号为5的所有行将显示:

partitions          rank        number
5                   1           5
4, 1                2           5
3, 2                2           5
3, 1, 1             3           5
2, 2, 1             3           5
2, 1, 1, 1          4           5
1, 1, 1, 1, 1       5           5

排名是行中使用的分区数,对您提供的问题很重要,因为它允许我们选择最小的传输。

对于数字5,我们有7行代表分区。对于更高的数字,返回的行将更高 - 数字12将有77个分区! - 但是在我们使用数据库的规模中,这个分区辅助表很容易在数字1到99中查询,例如提供的示例。更高的数字是可扩展性的问题。

如果你想要创建这样一个表格的说明,我很乐意为你提供 - 但由于这是一个很长的解决方案,让我们暂时设置一下辅助表的生成。

让我们看看商店ID A,它有要发送的数量。他们的数量是:

30
16
10
9
6
5
2 

对于每个商店数量,我们可以查询分区辅助表,并获取此数量及其排名的各种分区。然后我们可以创建自己的分区组合。例如,30将带来许多行,其中一行将是:

partitions         rank         number
15,10,5            3            30

和10将带来许多其他:

partitions         rank         number
6,4                2            10

您可以通过结果之间的交叉连接构建所有可能候选者的笛卡尔积,并且对于此产品的每一行,分区按升序排序,等级为分区等级的总和。

另一方面,您有商店ID B需要数量。你只需要做同样的处理,最后得到另一个相当大的笛卡尔积分排序分区产品。恭喜到目前为止。

现在,您只需要查看Store ID B分区集合完全包含在Store ID A分区集合中的分区行。这将使大型集合显着减少到几行潜在的转移。商店ID B中的一行(给出上面的示例)将是:

partitions                     rank
15,10,10,7,6,6,5,5,4,2         10

由于它出现在商店ID A和商店ID B.商店ID A中,它将是以下组合:

30 = 15,10,5     rank 3
16 = 10,6        rank 2
10 = 6,4         rank 2
9  = 7,2         rank 2
6  = 5,1         rank 2
5  = 5           rank 1
2  = 2           rank 1

给你一行:

partitions                     rank
15,10,10,7,6,6,5,5,5,4,2,2,1   13

最后一步是在商店ID B上选择排名最低的行。这将是最小的传输次数,您可以像上面那样输出它。

获得此奖励的奖励:如果您想查看我们是否可以完全耗尽商店ID A的整个库存(而不是满足商店ID B),请反转包含关系:确保分区集合A完全包含在分区集合中B.要查看将每个项目从A完全移动到B以满足B并耗尽A的最小转移,请在两个集合中查找相同的分区。

一些实际的SQL来模拟算法,至少部分是:

-- this function handles the sorting. It's not necessary but it help make the result look better.
WITH
FUNCTION SORT_PARTITIONS(p_id IN VARCHAR2) RETURN VARCHAR2 IS
result VARCHAR2(100);
BEGIN   
  select rtrim(XMLAGG(XMLELEMENT(E,str||',')).EXTRACT('//text()'),',') into result  
from (
with temp as  (
   select p_id num from dual       
 )
SELECT   trim(regexp_substr(str, '[^,]+', 1, level)) str
FROM (SELECT num str FROM temp) t
CONNECT BY instr(str, ',', 1, level - 1) > 0
order by to_number(str)
);
return result;
END;
-- this function handles containment - when we want to fulfil store ID B, and not necessarily deplete store ID A, or visa-versa.
FUNCTION PARTITION_CONTAINED(seta_partition IN VARCHAR2, setb_partition IN VARCHAR2) RETURN NUMBER IS
result NUMBER;
BEGIN
 with seta as 
(select str, count(str) cnt from (
SELECT trim(regexp_substr(str, '[^,]+', 1, level)) str
FROM (SELECT num str FROM (select SETA_PARTITION num from dual)) t
CONNECT BY instr(str, ',', 1, level - 1) > 0)
group by str),
setb as 
(select str, count(str) cnt from (
SELECT trim(regexp_substr(str, '[^,]+', 1, level)) str
FROM (SELECT num str FROM (select SETB_PARTITION num from dual)) t
CONNECT BY instr(str, ',', 1, level - 1) > 0)
group by str),
lenab as (select count(1) strab from seta, setb where seta.str=setb.str and seta.cnt>=setb.cnt),
lenb as (select count(1) strb from setb)
select strb-strab into result from lenb,lenab;
RETURN result;
END;
-- this store_a simply represents a small Cartesian product of two stores from the stores ID A table - one with quantity 5, the other with quantity 4. I found this was easier to set up. 
store_a as (select SORT_PARTITIONS(n1||','||n2) partitions_sending, rank1+rank2 rank_sending from (select num_partitions n1, rank rank1 from n_partitions where num=5),(select num_partitions n2, rank rank2 from n_partitions where num=4)),

-- this store_b represents the stores ID B's Cartesian product of partitions, again for simplicity. The receiving quantities are 3, 3 and 3.
store_b as (select SORT_PARTITIONS(n1||','||n2||','||n3) partitions_receive, rank1+rank2+rank3 rank_receive from (select num_partitions n1, rank rank1 from n_partitions where num=3),(select num_partitions n2, rank rank2 from n_partitions where num=3),(select num_partitions n3, rank rank3 from n_partitions where num=3))

-- and finally, the filtering that provides all possible transfers - with both "=" (which works for deplete and fulfil) and "partition_contained" which allows for fulfil or deplete. You can choose to leave both or just use partition contained, as it is more flexible.
SELECT * from store_a, store_b where store_a.partitions_sending=store_b.partitions_receive or partition_contained(store_a.partitions_sending,store_b.partitions_receive)=0 order by store_b.rank_receive, store_a.rank_sending asc;