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 程序将一一检查库存以查找项目并相应更新的主要内容,如果未能解决你的问题,请参考以下文章