PL/SQL 程序将一一检查库存以查找项目并相应更新

Posted

技术标签:

【中文标题】PL/SQL 程序将一一检查库存以查找项目并相应更新【英文标题】:PL/SQL procedure that will go through inventory one by one looking for item and updates accordingly 【发布时间】:2017-12-16 14:18:29 【问题描述】:

我有一个 inventory 表,如下所示:

warehouse_no|item_no|item_quantity
------------|-------|-------------
 1          | 1000  |          300
------------|-------|-------------
 2          | 1000  |          500
------------|-------|-------------
 3          | 1000  |          200
------------|-------|-------------
 1          | 2000  |          100
------------|-------|-------------
 2          | 2000  |          200
------------|-------|-------------
 3          | 2000  |            0
------------|-------|-------------
 1          | 3000  |          100
------------|-------|-------------
 2          | 3000  |          200
------------|-------|-------------
 3          | 3000  |            0
------------|-------|-------------

现在,例如,如果有人订购 400 件商品编号。 1000,pl/sql应该遍历表,从仓库1取300,从仓库2取100并更新。结果表应如下所示:

warehouse_no|item_no|item_quantity
------------|-------|-------------
 1          | 1000  |            0
------------|-------|-------------
 2          | 1000  |          400
------------|-------|-------------
 3          | 1000  |          200
------------|-------|-------------

我写的程序如下

PROCEDURE upd_inventory(p_item_no inventory.item_no%TYPE, p_quantity number) 
AS
 CURSOR inventory_cur IS
  select MAX(item_quantity)as quantity
  from   inventory
  where  item_no=p_item_no;

  v_order_quantity number:=p_quantity;

BEGIN
 FOR  v_inventory_cur IN inventory_cur LOOP
  UPDATE INVENTORY
  SET item_quantity = ((v_inventory_cur.quantity) - p_quantity );
  COMMIT;
 END LOOP;
END upd_inventory;

但正如您所注意到的,这将更新整个数量列,并且不能解决逐行检查并相应更新的问题。

谢谢

【问题讨论】:

这个可以用纯SQL解决,会快很多。编写 PL/SQL 过程是问题要求的一部分吗? (即:您是否正在学习关于 PL/SQL 过程的课程,而这只是一个课程作业?)如果没有,最好只是陈述问题,而不是要求特定(和次优)的方法解决它。 此过程需要在我的 pl/sql 包中,它接受新订单并更新所有表 @mathguy 您能否仅使用 SQL 发布答案,仅供我理解和将来参考。 @pOrinG 我已经用一条 SQL 语句写了一个答案,请看一下。 【参考方案1】:

可以使用一个 SQL 查询。下面的查询显示,如何计算从每个仓库中取出的数量,并返回我们总共取出 400 件商品编号的仓库列表。 1000:

select warehouse_no, item_no, item_quantity,
       case when running_sum < 400 then item_quantity
            else 400 - (running_sum - item_quantity) end how_much_to_take
  from (select warehouse_no, item_no, item_quantity, 
               sum(item_quantity) over (partition by item_no 
                                        order by warehouse_no) running_sum
          from inventory
         where item_no = 1000)
 where running_sum - item_quantity < 400

how_much_to_take 包含数量,我们需要从仓库中取出多少物品。

所以我们可以编写如下MERGE 语句:

merge into inventory i
using ( select warehouse_no, item_no, item_quantity, running_sum,
               case when running_sum < 400 then item_quantity
                    else 400 - (running_sum - item_quantity) end to_take
          from (select warehouse_no, item_no, item_quantity, 
                       sum(item_quantity) over (partition by item_no 
                                                order by warehouse_no) running_sum
                  from inventory
                 where item_no = 1000)
         where running_sum - item_quantity < 400
        ) how_much
   on (i.warehouse_no = how_much.awrehouse_no and i.item_no = how_much.item_no)
 when matched then update
  set i.item_quntity = i.item_quntity - how_much.to_take

此语句将根据需要更新您的表格。而且,如果您仍然需要程序:

PROCEDURE upd_inventory(p_item_no inventory.item_no%TYPE, p_quantity number) AS
BEGIN
    merge into inventory i
    using ( select warehouse_no, item_no, item_quantity, running_sum,
                   case when running_sum < p_quantity then item_quantity
                        else p_quantity - (running_sum - item_quantity) end to_take
              from (select warehouse_no, item_no, item_quantity, 
                           sum(item_quantity) over (partition by item_no 
                                                    order by warehouse_no) running_sum
                      from inventory
                     where item_no = p_item_no)
             where running_sum - item_quantity < p_quantity
            ) how_much
       on (i.warehouse_no = how_much.awrehouse_no and i.item_no = how_much.item_no)
     when matched then update
      set i.item_quntity = i.item_quntity - how_much.to_take;
END upd_inventory;

【讨论】:

谢谢,我明白了! :) 非常感谢!现在有了更好的理解。非常感谢 @Dmitry 但是,如果总量不够,它不考虑。我们可以添加另一个 where 条件来检查 & 如果更新的记录数 = 0 则作业由于数量不足而失败。谢谢你的回答:) @pOrinG 是的,我忘记了这个案子。我认为最好将总量检查为select sum(item_quantity) from inventory where item_no = 1000。如果总数量少于所需数量,则只需简单地更新并在所有位置设置 0,然后返回缺失的数量。【参考方案2】:

使用 item_no 和 warehouse_no 作为 where 条件来更新库存表中的 item_quantity。您需要在其中找到一个模式以在 where 条件下使用 warehouse_no。

【讨论】:

【参考方案3】:

以下功能将满足您的要求:

CREATE OR REPLACE PROCEDURE 
DEDUCT_INV (in_item_no In Number,in_quantity  In Number) 
Is

TYPE someRefCursor IS REF CURSOR;
fetchWareHouseQuantitiesCursor someRefCursor;

tempWareHouseNo temp_inventory.warehouse_no%type;
tempItemQuantity temp_inventory.item_quantity%type;
requiredQuantity temp_inventory.item_quantity%type := in_quantity;

Begin

    Open fetchWareHouseQuantitiesCursor For 
    Select warehouse_no, item_quantity 
    From temp_inventory 
    Where item_no = in_item_no And item_quantity != 0
    order by warehouse_no;

    /* Ignoring 0 quantity warehouses 
    & also ordering by warehouse 
    but if required can be ordered by item_quantity desc so
    minimum warehouses are touched which is more efficient*/

    LOOP
        Fetch fetchWareHouseQuantitiesCursor Into tempWareHouseNo,tempItemQuantity;

        Dbms_Output.Put_Line('Required:'||requiredQuantity||'.');

        Exit When fetchWareHouseQuantitiesCursor%NotFound;

        Dbms_Output.Put_Line('Fetched:'||tempWareHouseNo||','||tempItemQuantity||'.');

        if(requiredQuantity > tempItemQuantity)
        then

            Dbms_Output.Put_Line('Updating:'||tempWareHouseNo||','||tempItemQuantity||' by 0.');

            update temp_inventory set item_quantity = 0 where warehouse_no = tempWareHouseNo And item_no = in_item_no;

            requiredQuantity:= requiredQuantity - tempItemQuantity;

        else

            Dbms_Output.Put_Line('Updating:'||tempWareHouseNo||','||tempItemQuantity||' by '||(tempItemQuantity - requiredQuantity)||'.');

            update temp_inventory set item_quantity = item_quantity-requiredQuantity where warehouse_no = tempWareHouseNo And item_no = in_item_no;

            requiredQuantity:= 0;

            exit;

        end if;

    END LOOP;

    Close fetchWareHouseQuantitiesCursor;

    if(requiredQuantity != 0)
    then
        rollback;

        Dbms_Output.Put_Line('Job Failed. Insufficient storage. Missing:'||requiredQuantity||' quantity.');
    else
        commit;

        Dbms_Output.Put_Line('Job Completed Successfully.');
    end if;

    return;

End;
/

execute DEDUCT_INV(1000,400);

【讨论】:

你是个传奇。那行得通!感谢您花时间编写这么长的代码,非常感谢。 @dwalker 啊,希望你明白其中的逻辑。德米特里用单一语句写了一些更有趣的东西。 @dwalker 我之前也忘了关闭光标。如果您正在使用代码,请考虑到这一点。【参考方案4】:

其他人已经回答了这个问题,但只是为了好玩,我还是会分享我开始的版本:

procedure upd_inventory
    ( p_item_no   inventory.item_no%type
    , p_quantity  inventory.item_quantity%type )
is
    l_total_stocked inventory.item_quantity%type := 0;
    l_outstanding inventory.item_quantity%type := p_quantity;
begin
    dbms_output.put_line('Item ' || p_item_no || ' target: ' || p_quantity);

    for r in (
        select i.item_no, i.warehouse_no
             , i.item_quantity as warehouse_quantity
             , i.item_quantity as warehouse_remaining
             , sum(i.item_quantity) over () as total_quantity
        from   inventory i
        where  i.item_no = p_item_no
        and    i.item_quantity > 0
        for update
        order by i.item_quantity desc
    )
    loop
        l_total_stocked := r.total_quantity;  -- redundant after first iteration but avoids separate query

        update inventory i
        set    i.item_quantity = greatest(r.warehouse_quantity - l_outstanding, 0)
        where  i.warehouse_no = r.warehouse_no
        and    i.item_no = p_item_no
        returning i.item_quantity into r.warehouse_remaining;

        dbms_output.put_line('Warehouse '||r.warehouse_no || ': reducing stock by ' || (r.warehouse_quantity - r.warehouse_remaining));

        l_outstanding := l_outstanding - (r.warehouse_quantity - r.warehouse_remaining);

        exit when l_outstanding = 0;
    end loop;

    if l_outstanding = 0 then
        dbms_output.put_line('Item ' || p_item_no || ' stock reduced by ' || p_quantity);
    else
        raise_application_error
        ( -20000
        , 'Insufficient stock for item ' || p_item_no ||
          ': stocked: ' || l_total_stocked || ', requested: ' || p_quantity ||
          ', short: ' || (p_quantity - l_total_stocked) );
    end if;

end upd_inventory;

虽然单一的merge 方法无疑是最快的(并且避免了在多用户环境中防止丢失更新所需的锁定),但我个人会担心支持和维护。也许我可以使用model 子句或递归with 重写我的光标,但是可怜的开发人员在一天之内更新它以考虑到交付距离,否则将有更难的工作。

我没有在过程中提交或回滚,因为通常最好将其留给调用者,但如果您愿意,您可以在最后提交(而不是在中间!)请注意,引发异常会隐式滚动返回更新。如果您需要显式回滚,请在开始时声明一个保存点并回滚。

可能一个真正的过程会使用标准记录器来记录诊断消息,而不是dbms_output

【讨论】:

不错的一个。非常清晰和合乎逻辑。感谢您花时间编写它。

以上是关于PL/SQL 程序将一一检查库存以查找项目并相应更新的主要内容,如果未能解决你的问题,请参考以下文章

PL/SQL:检查触发器中的所有行后运行包

PL/SQL 性能优化 - 根据记录更改计算总和和微分

PL/SQL 中的验证检查

pl/sql 查询以通过 deptno 查找经理名称

PL/SQL 触发器练习

Oracle pl sql 10g - 将一组行从表移动到具有相同结构的历史表