2009-12-08 5 views
2

Я пытаюсь случайно выбрать карту из таблицы cards с колонками c_value и c_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; 
/

Здесь находится в действии. Не используйте открытый 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

Вы уже используете явный курсор, поэтому вам не нужен фильтр 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() в начале разбирательства.

Смежные вопросы