我有两张桌子,我需要完成表格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得到满足 有没有办法在没有程序方法的情况下实现这个目标?
到目前为止,我已尝试交叉加入以找到这些组合,但没有多少帮助
答案 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;