2009-12-08 72 views
2

我試圖從cards的表中隨機選擇一個卡,其中列c_valuec_suit使用過程。選擇它後,程序應將該條目的taken字段更新爲'Y'。ORA-02014-如何從表中隨機選擇一行進行更新?

create or replace procedure j_prc_sel_card(p_value OUT number, 
              p_suit OUT number) 
AS 

    CURSOR CUR_GET_RAND_CARD IS SELECT c_value, 
             c_suit 
           FROM (SELECT c_value, 
               c_suit, 
               taken 
             FROM jackson_card 
            ORDER BY dbms_random.value) 
           WHERE rownum = 1 
         FOR UPDATE OF taken; 

BEGIN 

    OPEN CUR_GET_RAND_CARD; 
    FETCH CUR_GET_RAND_CARD into p_value, p_suit; 

    UPDATE jackson_card 
    SET taken = 'Y' 
    WHERE c_value = p_value 
    AND c_suit = p_suit; 

    CLOSE CUR_GET_RAND_CARD; 

END; 

然後,我試圖獲取選定的卡並輸出它作爲開始。有了這個:

SET serveroutput on; 

DECLARE v_value number; 
     v_suit number; 

BEGIN 

    j_prc_sel_card(p_value => v_value,p_suit => v_suit); 
    DBMS_OUTPUT.PUT_LINE(v_value); 
    DBMS_OUTPUT.PUT_LINE(v_suit); 

END; 
/

但是我得到了標題中所述的錯誤,看來我選擇一個隨機卡的方式,從做一個更新停止我。提前致謝!

回答

2

下面是對該場景的不同看法(我也在a different answer中解決了您的直接問題)。

鑑於我們確實在制定一個卡處理程序(而不是針對業務場景的測試案例),我不喜歡TAKEN列。更新表列以標記暫時狀態似乎是錯誤的。當我們想玩另一場比賽時會發生什麼?

以下解決方案通過在隨機順序中預先填充所有卡(陣列)來解決此問題。卡片是通過簡單地從堆疊中取出下一個條目來處理的。該軟件包提供了一個用完卡片的方法:或者拋出一個用戶定義的例外,或者只是再次循環播放卡片。

create or replace package card_deck is 

    no_more_cards exception; 
    pragma exception_init(no_more_cards, -20000); 

    procedure shuffle; 

    function deal_one 
     (p_yn_continuous in varchar2 := 'N') 
     return cards%rowtype; 

end card_deck; 
/

create or replace package body card_deck is 

    type deck_t is table of cards%rowtype; 
    the_deck deck_t; 

    card_counter pls_integer; 

    procedure shuffle is 
    begin 
     dbms_random.seed (to_number(to_char(sysdate, 'sssss'))); 
     select * 
     bulk collect into the_deck 
     from cards 
     order by dbms_random.value; 
     card_counter := 0; 
    end shuffle; 

    function deal_one 
     (p_yn_continuous in varchar2 := 'N') 
     return cards%rowtype 
    is 
    begin 
     card_counter := card_counter + 1; 
     if card_counter > the_deck.count() 
     then 
      if p_yn_continuous = 'N' 
      then 
       raise no_more_cards; 
      else 
       card_counter := 1; 
      end if; 
     end if; 
     return the_deck(card_counter); 
    end deal_one; 

end card_deck; 
/

這裏是在行動。如果將連續處理模式設置爲Y,請勿使用開放的LOOP

SQL> set serveroutput on 
SQL> 
SQL> declare 
    2  my_card cards%rowtype; 
    3 begin 
    4  card_deck.shuffle; 
    5  loop 
    6   my_card := card_deck.deal_one; 
    7   dbms_output.put_line ('my card is '||my_card.c_suit||my_card.c_value); 
    8  end loop; 
    9 exception 
10  when card_deck.no_more_cards then 
11   dbms_output.put_line('no more cards!'); 
12 end; 
13/
my card is HA 
my card is H7 
my card is DJ 
my card is CQ 
my card is D9 
my card is SK 
no more cards! 

PL/SQL procedure successfully completed. 

SQL> 

你可能會認爲我沒有處理完整的套牌。你不會是第一個想到的;)

1

您已使用明確的光標,因此您不需要ROWNUM = 1過濾器。試試這個:

create or replace procedure j_prc_sel_card(p_value OUT number, 
              p_suit OUT number) 
AS 

    CURSOR CUR_GET_RAND_CARD IS 
     SELECT c_value, 
       c_suit, 
       taken 
     FROM jackson_card 
     WHERE taken != 'Y' 
     ORDER BY dbms_random.value 
     FOR UPDATE OF taken; 

BEGIN 

    OPEN CUR_GET_RAND_CARD; 
    FETCH CUR_GET_RAND_CARD into p_value, p_suit; 

    UPDATE jackson_card 
    SET taken = 'Y' 
    WHERE CURRENT OF cur_get_rand_card; 

    CLOSE CUR_GET_RAND_CARD; 

END; 

請注意使用WHERE CURRENT OF。當我們使用FOR UPDATE CLAUSE時,這是找到一行最有效的方法。如果不使用NOWAIT子句,則如果所選卡被另一個會話鎖定,光標將掛起。一種不太可能的情況,但是當你超越紙牌遊戲並進入真實場景時,值得考慮。

此外,請記住,對於真正的隨機洗牌,您需要在程序開始時致電DBMS_RANDOM.SEED()