ORA-02014-如何更新表中随机选择的行?

时间:2009-12-08 04:58:42

标签: oracle plsql cursors ora-02014

我正在尝试使用一个程序从cards表格中随机选择一张卡片c_valuec_suit。选择后,该过程应将该条目的taken字段更新为“Y”。

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 个答案:

答案 0 :(得分:2)

以下是对该方案的不同看法(我还在a different answer中解决了您的直接问题)。

鉴于我们确实正在构建一个交易卡计划(而不是针对业务场景使用测试用例),我不喜欢TAKEN列。更新表列以标记暂时状态似乎是错误的。当我们想要玩另一场比赛时会发生什么?

以下解决方案通过在所有卡中预先填充随机顺序(shuffle)来解决此问题。只需将下一个条目从堆栈中取出即可处理这些卡片。该软件包提供了一种用于耗尽卡片的方法:要么抛出用户定义的异常,要么只是再次在套牌中循环。

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;
/

这是在行动。如果将连续交易模式设置为LOOP,则不要使用开放Y

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 :(得分: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()