2017-12-16 5 views
2

私は在庫テーブルの下のように持っている:PL/SQLプロシージャは、それに応じて

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から取り出し、更新します。結果のテーブルには、以下のようになります。

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; 

を下回っているしかし、あなたは気づいているだろうと、これは全体の数量の列を更新すると通過チェックの問題を解決しません行単位でアイテムを取り出し、それに応じて更新する。

おかげ条件は、在庫テーブルにitem_quantityを更新する場所として

+1

これは、はるかに高速な単純SQLで解決できます。 PL/SQLプロシージャを問題の要件の一部にしていますか? (つまり、PL/SQLプロシージャでクラスを取っていますか?これは単なるクラス・アサインですか?)そうでない場合は、問題を述べ、特定のそれを解決する。 – mathguy

+0

この手順は、新しい注文とすべてのテーブルを更新する私のpl/sqlパッケージ内にある必要があります – dwalker

+0

@mathguyあなたは私の理解と将来の参考のためだけにSQLだけで回答を投稿してください。 – pOrinG

答えて

0

使用item_noとwarehouse_no。どこでwarehouse_noを使用するかのパターンを見つける必要があります。機能以下

0

はあなたの条件のために動作します:

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); 
+0

あなたは伝説です。それはうまくいった!長いコードを高く評価していただきありがとうございます。 – dwalker

+0

@dwalkerああ、あなたはロジックを理解して願っています。ドミトリーは一言で、もっと面白いものを書いています。 – pOrinG

+0

@dwalker前にカーソルを閉じるのを忘れてしまった。コードを使用している場合は、その点を考慮してください。 – pOrinG

1

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; 
+0

ありがとう、私は論理を得た! :) – pOrinG

+0

ありがとう!今よりはるかに良い理解が得られました。非常に高く評価されています – dwalker

+0

@ドミトリーしかし、総量が足りない場合は考慮しません。レコードの数が更新された場合= 0とした場合、不足しているためにジョブが失敗したことを確認する条件を追加することができます。答えをありがとう:) – pOrinG

0

その他は、すでにこのに答えてきたが、ちょうど楽しみのために私はとにかく始めたバージョンを共有しましょう:

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ではなく診断メッセージに標準のロガーを使用します。

+0

素晴らしい1つ。非常に明確で論理的です。それを書く時間をとっていただきありがとうございます。 – dwalker

関連する問題