【问题标题】:Minimum transfer Sql query最小转Sql查询
【发布时间】:2017-05-22 18:30:01
【问题描述】:

我有两张桌子,我需要在桌子 2 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 需要_qty 得到填满 有没有办法在没有程序的情况下实现这一目标?

到目前为止,我已经尝试交叉连接以找到组合,但没有太大帮助

【问题讨论】:

  • 当您说“完成”时,您的意思是将表 1 中的项目插入到表 2 中,而这些项目在表 2 中尚不存在?
  • @krokodilko 我尝试过的间隔重叠解决方案之一,但无论如何可以最大限度地减少移动
  • 第二个表中不存在第一个表中的商店ID,或者这只是不是很好的示例数据?我无法弄清楚这两个表之间是否存在任何关系。你是怎么得出这些数字的?
  • @dlatikay 我添加了一些示例数据
  • 你如何定义“最小移动”?除此之外,这听起来像是一个典型的“包装问题”——有几种算法可以解决。不要指望一般的“最佳”解决方案(这将需要不合理的处理时间),而只是“相当好的”解决方案。在过去的几个月里,关于这些问题,OTN 上已经进行了一些很好的讨论。

标签: sql oracle


【解决方案1】:

这可以通过使用包含整数分区的辅助表使用常规 SQL 来解决。为了这个例子,我们假设这个辅助表有三列:partitions、rank 和 number。对于任何给定的数字,都会有几行可能的分区,每个分区都有它的等级。如果数字为 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 分区集合中的分区行。这将大大减少大集合到几行潜在的传输。 Store ID B 中的一行(如上例所示)将是:

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

由于它同时出现在 Store ID A 和 Store ID B 中。在 Store 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

最后一步是选择 Store ID B 上排名最低的行。这将是最少的转移次数,您可以像上面一样输出它。

走到这一步的奖励:如果你想看看我们是否可以完全耗尽 Store ID A 的整个库存(而不是满足 Store 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;

【讨论】:

  • 非常感谢您提供方向看起来很有趣的算法。你能提供查询以建立辅助表吗
  • 下面的示例将为数字 12 提供所有 3-rank 分区。您可以使用各种参数运行它以在表中生成具有各种数字和级别的行。要更改数字,只需更改顶行。要更改排名,您需要相应地添加 n_range 表并添加到 where。与 nth_w 作为(从对偶中选择第 12 个),n_range 作为(从对偶中选择 rownum t,nth_w 通过 rownum =r2.t and r2.t>=r3.t and r1.t+r2.t+r3.t = nth_w.nth;
  • 非常感谢。似乎我很难理解逻辑中间和最后部分。如果您可以编辑您的答案并在辅助表的帮助下输入一些 SQL 查询以达到最终结果,那就太好了。
  • 这将需要一些时间——创建辅助表、填充它、使用示例数据创建存储表——我明天将尝试找时间设置环境。很高兴您喜欢这个解决方案。
  • 谢谢!我也会试着写。这个创新算法有什么名字吗?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-01-24
  • 2018-04-28
  • 2013-10-26
  • 2011-12-19
  • 2012-07-11
  • 1970-01-01
相关资源
最近更新 更多