【问题标题】:SQL query to fill multiple needs with partial quantities by using Oracle SQL window functions使用 Oracle SQL 窗口函数以部分数量满足多个需求的 SQL 查询
【发布时间】:2019-09-27 09:19:42
【问题描述】:

我想解决一个在我的 SQL 历史中多次出现的抽象问题。如果我们假设我们是一家销售公司并谈论我们想要销售的零件(产品),那么这个抽象问题可以更好地实现,因此在不同的日期需要不同的数量。 另一方面,我们有我们的“填充可能性”,例如来自不同日期和数量的库存、生产、采购的数量。

我想仅使用一个查询(无过程,无临时表)来满足按所需日期和可用日期排序的需求。

作为技术基础,您可以假设有两个表:

  • NEED_TABLE,其中列出了多种需求、所需日期和数量。
  • FILL_TABLE,其中列出了多个“填充”、可用日期和数量。

在我的例子中有两个需求:

  • 需要 A:我们需要 2019 年 1 月 2 日,数量 4 中的部件号 123
  • 需要 B:我们需要 2019 年 6 月 2 日第 2 件的零件编号 123

我们有两个不同数量的“填充物”:

  • 填写 X:我们在采购订单中的第 2 个零件编号为 123,可于 2019 年 1 月 1 日供货
  • 填写 Y:我们在采购订单中的第 4 个零件编号为 123,供货时间为 2019 年 6 月 1 日

结果应该是:

  • 我需要 2019 年 1 月 2 日数量为 4 的零件编号 123(“需要 A”),由数量 2 中的采购订单(“填充 X”)和数量 2 中的另一个采购订单(“填充Y" - 部分)。
  • 我需要 2019 年 6 月 2 日的部件号 123,数量为 2(“需要 B”),它由数量 2 的采购订单填充(“填充 Y” - 部分)。

SQL 查询:

with
    NEED_TABLE
    as
        (select 'A' NEED_ID, 123 partno, to_date('01/02/2019', 'MM/DD/YYYY') DATE_NEEDED, 4 NEED_QTY from dual
         union all
         select 'B' NEED_ID, 123 partno, to_date('06/02/2019', 'MM/DD/YYYY') DATE_NEEDED, 2 NEED_QTY from dual),
    FILL_TABLE
    as
        (select 'X' FILL_ID, 123 partno, to_date('01/01/2019', 'MM/DD/YYYY') DATE_AVAILABLE, 2 FILL_QTY from dual
         union all
         select 'Y' FILL_ID, 123 partno, to_date('06/01/2019', 'MM/DD/YYYY') DATE_AVAILABLE, 4 FILL_QTY from dual)
select   NEED_TABLE.NEED_ID
       , NEED_TABLE.PARTNO
       , NEED_TABLE.DATE_NEEDED
       , NEED_TABLE.NEED_QTY
       , FILL_TABLE.FILL_ID
       , FILL_TABLE.DATE_AVAILABLE
       , FILL_TABLE.FILL_QTY
       /* all following is wrong/incomplete */
       , lag(need_QTY - fill_QTY, 1, need_QTY)
             over(
                 partition by NEED_ID
                 order by DATE_NEEDED, DATE_AVAILABLE) REAL_NEED_QTY
       , greatest(
               lag(need_QTY - fill_QTY, 1, need_QTY)
                   over(
                       partition by NEED_ID
                       order by DATE_NEEDED, DATE_AVAILABLE)
             - FILL_QTY
           , 0) LEFT_NEED_QTY
       , abs(
             least(
                   lag(need_QTY - fill_QTY, 1, need_QTY)
                       over(
                           partition by NEED_ID
                           order by DATE_NEEDED, DATE_AVAILABLE)
                 - FILL_QTY
               , 0)) LEFT_FILL_QTY
    from NEED_TABLE, FILL_TABLE
order by DATE_NEEDED, DATE_AVAILABLE;

如果您检查该查询的结果,第一个 NEED_ID“A”似乎一切正常。但是当它继续使用 NEED_ID“B”时,它不记得在填充需求“A”时 FILL_ID X 和 Y 已经减少了。

我希望得到如下结果:

NEED_ID A is filled by FILL_ID X qty 2

NEED_ID A is filled by FILL_ID Y qty 2

(NEED_ID A is filled by FILL_ID X qty 0)

NEED_ID B is filled by FILL_ID Y qty 2

NEED_TABLE:

| NEED_ID | PARTNO | DATE_NEEDED | NEED_QTY |
|---------|--------|-------------|----------|
| A       | 123    | 01/02/2019  | 4        |
| B       | 123    | 06/02/2019  | 2        |

FILL_TABLE:

| FILL_ID | PARTNO | DATE_AVAILABLE | FILL_QTY |
|---------|--------|----------------|----------|
| X       | 123    | 01/01/2019     | 2        |
| Y       | 123    | 06/01/2019     | 4        |

预期的查询结果:

| NEED_ID | PARTNO | DATE_NEEDED | NEED_QTY | FILL_ID | DATE_AVAILABLE | FILL_QTY | ***REAL_FILL*** | "WHY?"                                                              |
|---------|--------|-------------|----------|---------|----------------|----------|-----------------|---------------------------------------------------------------------|
| A       | 123    | 01/02/2019  | 4        | X       | 01/01/2019     | 2        | 2               | A needs 4, gets partially filled by X by 2                          |
| A       | 123    | 01/02/2019  | 4        | Y       | 06/01/2019     | 4        | 2               | A still needs 2, gets completely filled by Y by 2                   |
| B       | 123    | 06/02/2019  | 2        | X       | 01/01/2019     | 2        | 0               | B needs 2, can't get filled by X, because A already used that qty   |
| B       | 123    | 06/02/2019  | 2        | Y       | 06/01/2019     | 4        | 2               | B still needs 2, gets completely filled by remaining qty of Y, by 2 |

非常感谢任何帮助 - 谢谢!

【问题讨论】:

  • 填充表和需要表之间没有连接条件;交叉连接真的是你想要的吗?此外,请编辑您的问题以提供两个表的示例数据,以及您希望从查询中看到的结果。
  • 交叉连接旨在使示例尽可能简单。在我的真实查询中,我加入了所有合适的“填充”的需求。但问题是,我的需求只能通过 FILL_TABLE 中的一组部分数量来满足。
  • 类似抽象问题的另一个例子是:我想从一个仓库中取出 2 次 10 件的零件,该仓库有几个箱子,每个箱子的数量为 5。为了满足我的第一个需求,我从第一个垃圾箱中取出 5 件,从第二个垃圾箱中取出 5 件。为了满足我的第二个需求,我从第三个垃圾箱中取出 5 件,从第四个垃圾箱中取出 5 件。我现在的方法是,用合适的“填充”来“交叉连接”所有需求,并使用窗口函数(或其他东西?)将每个“填充”(在这种情况下是仓库箱)的(部分)数量分配给我的需求,直到需求得到满足或填充为空。

标签: sql oracle window-functions


【解决方案1】:

这是我的尝试:

with 
  need(rn, nid, nq) as (
    select 1, 'A', 4 from dual union all 
    select 2, 'B', 2 from dual ),
  fill(rf, fid, fq) as (
    select 1, 'X', 2 from dual union all 
    select 2, 'Y', 4 from dual ),
  u as (
    select rn, nid, -nq nq, null rf, null fid, null fq from need
    union all select null, null, null, rf, fid, fq from fill),
  c (crn, cnid, cnq, crf, cfid, cfq, rest, amt) as (
    select rn, nid, nq, 0, fid, fq, nq, 0 from u where rn = 1
    union all 
    select nvl(rn, crn), nvl(nid, cnid), nvl(nq, cnq), 
           nvl(rf, crf), nvl(fid, cfid), nvl(fq, cfq), rest + nvl(nq, fq), 
           least(abs(rest), abs(nvl(nq, fq)))
      from c 
      join u on rest >= 0 and rn = crn + 1 
             or rest <  0 and rf = crf + 1 )
select cnid, cfid, amt from c where amt <> 0

我简化了数据,但 partno 可以很容易地添加到连接中,并且正确的 row_numbers 和日期的 partition by 仅对于排序行很重要。如果它们有更多含义,现在可以添加,但让我们从更清晰的开始。

它是如何工作的。 need 和 fill 是我们的数据源。 u 是这些表的联合体,need 和 fill 数据位于不同的列中。需要这个联合才能使下一个查询正常工作。

c 是递归 CTE,从第一个 fill 开始。它是我们的锚。在下一步中,我添加(加入)fill 或 need 行,具体取决于我们在之前的rest 中得到的内容。如果 rest 小于零,则意味着我们必须寻找下一个 fill 行。 如果它更大,则意味着我们从 fills 获得了剩余,我们可以寻找下一个 need。在每个步骤中,都会计算交易金额,该金额等于先前休息和当前加入的填充/需要中的较低值。

最后,我计算交易的金额和双方。在一些示例上进行了测试。

demo

【讨论】:

  • 非常感谢您花时间编写这个“基本查询”。这似乎正是我想要的。我将尝试根据我的情况调整此查询。几行 SQL 的强大功能真是太棒了。
【解决方案2】:

也许以下想法会有所帮助:{1} 编写 2 个视图,将 NEED_QTY 和 FILL_QTY 值分解为“原子”,即最小的可用单位。 (如果您最终只想要一个海量查询,您可以稍后将视图代码合并到最终查询中)。 {2} 加入 2 个视图 {3} 使用 GROUP BY ROLLUP 来获得一些可用的输出。

观看次数

-- NEED: required items, broken down into "atoms" ie smallest available quantities
create or replace view nvw
as
select
  need_id as nid
, partno
, need_qty as nqty
, date_needed as ndate
, single_item
, row_number() over ( order by need_id, partno, date_needed ) row_
from need_table N cross apply (
    select 1 as single_item from dual connect by level <= N.need_qty
) SN
;

-- FILL: available items, broken down into "atoms" ie smallest available quantities
create or replace view fvw
as
select
  fill_id as fid
, partno
, fill_qty as fqty
, date_available as fdate
, single_item
, row_number() over ( order by fill_id, partno, date_available ) row_
from fill_table F cross apply (
    select 1 as single_item from dual connect by level <= F.fill_qty
) SF
;

加入视图

select *
from (
  select fid, partno, fqty, single_item, row_ from fvw
) FV join (
  select nid, partno, nqty, single_item, row_ from nvw
) NV on FV.row_ = NV.row_ ;

-- result
FID  PARTNO  FQTY  SINGLE_ITEM  ROW_  NID  PARTNO  NQTY  SINGLE_ITEM  ROW_  
X    123     2     1            1     A    123     4     1            1     
X    123     2     1            2     A    123     4     1            2     
Y    123     4     1            3     A    123     4     1            3     
Y    123     4     1            4     A    123     4     1            4     
Y    123     4     1            5     B    123     2     1            5     
Y    123     4     1            6     B    123     2     1            6     

6 rows selected. 

按汇总分组

-- dates omitted for clarity
select  NV.nid, FV.fid, sum( FV.single_item ) real_fill
from (
  select fid, partno, fqty, single_item, row_ from fvw
) FV join (
  select nid, partno, nqty, single_item, row_ from nvw
) NV on FV.row_ = NV.row_
group by rollup ( NV.nid, FV.fid );

-- result
NID   FID   REAL_FILL  
A     X     2          
A     Y     2          
A     NULL  4          
B     Y     2          
B     NULL  2          
NULL  NULL  6

在编写视图/查询时考虑了中心问题,即“用部分数量满足多个需求”。您可以在 GROUP BY 子句中添加更多列名(例如日期)。使用 Oracle 12c 和 18c 测试。 DBfiddle here.

此概念也可用于您评论中描述的场景(2 包 10 件必须由存储在 4 个箱子中的物品填充,每个箱子包含 5 件物品) - 请参阅DBfiddle。

【讨论】:

  • 巧妙 :) 可能存在一些问题,例如 fill_qty = 10.5。或大量使connect by 查询非常慢。否则很好的解决方案。
  • @PonderStibbons - 感谢您的客气话!最初,我认为我会使用递归子查询分解(就像您所做的那样),所以看到 your 解决方案对我来说很有趣。
  • @stefan 非常感谢您的意见!对于我有数量为 1 的原子的其他情况,我通过加入基于值的数量的表来使用类似的东西:inner join table( cast( multiset( select rownum from dual connect by FILL_TABLE.fill_qty &gt; 1 * (level - 1)) as sys.OdciNumberList)) 在这种情况下使用汇总的有趣方法,没有考虑到这一点。
猜你喜欢
  • 2021-02-20
  • 1970-01-01
  • 1970-01-01
  • 2017-03-28
  • 2015-11-22
  • 1970-01-01
  • 2021-01-25
  • 2013-12-19
  • 1970-01-01
相关资源
最近更新 更多