2009-12-08 3 views
2

c_valuec_suit 열을 사용하여 cards 테이블에서 임의로 카드를 선택하려고합니다. 그것을 선택한 후에, 절차는 해당 엔트리의 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()으로 전화해야한다는 점을 기억하십시오.

관련 문제