Лучший способ Генерировать Уникальные и consecutives числа в Oracle

Я должен генерировать уникальные и последовательные числа (для использования на счете) быстрым и надежным способом. в настоящее время используйте последовательность Oracle, но в некоторых случаях сгенерированные числа не последовательны из-за исключений, которые могут произойти.

Я думал несколько решений управлять этой проблемой, но ни одним из них убеждающий меня. Какое решение Вы рекомендуете?

  1. Используйте выбор макс. ()

    SELECT MAX (NVL (doc_num, 0)) +1 FROM invoices
    
  2. Используйте таблицу для хранения последнего числа, сгенерированного для счета.

    UPDATE docs_numbers
        SET last_invoice = last_invoice + 1
    
  3. Другое решение?

5
задан John Smithers 25 April 2010 в 13:41
поделиться

10 ответов

Пробелы появляются, если транзакция использует порядковый номер, но затем откатывается.

Возможно, ответ заключается в том, чтобы не присваивать номер счета-фактуры до тех пор, пока счет-фактура не сможет быть откатирована. Это минимизирует (но, вероятно, не устраняет) возможности разрывов.

Я не уверен, что существует быстрый или простой способ убедиться в отсутствии разрывов в последовательности - сканирование MAX, добавление и вставка, которая, вероятно, наиболее безопасна, но не рекомендуется по причинам производительности (и сложностям с параллельностью), и метод не будет определять, присваивается ли последний номер счета, а затем удаляется и переназначается.

Можете ли вы как-то учесть пробелы - определив, какие номера счетов "использовались", но почему-то "не стали постоянными"? Может ли автономная транзакция помочь в этом?


Другая возможность - предположить, что разрывов относительно мало и далеко.

Создайте таблицу, в которой будут записываться номера последовательностей, которые должны быть повторно использованы до того, как будет захвачено новое значение последовательности. Обычно она пустая, но какой-то процесс, который запускается каждую ... минуту, час, день ... проверяет на наличие разрывов и вставляет пропущенные значения в эту таблицу. Все процессы сначала проверяют таблицу на наличие пропущенных значений, а если таковые имеются, то используют значение оттуда, проходя через медленный процесс обновления таблицы и удаления используемой строки. Если таблица пуста, то возьмите следующий порядковый номер.

Не очень приятно, но отделение "выдачи номеров счетов" от "проверки на пропущенные значения" означает, что даже если процесс выставления счетов провалится для какого-то потока, когда он использует одно из пропущенных значений, то это значение будет заново обнаружено и переиздано в следующий раз - повторяя до тех пор, пока какой-нибудь процесс не добьется с ним успеха.

.
3
ответ дан 18 December 2019 в 11:57
поделиться
  • 1 всегда можно заставить каким-то образом выйти из строя в окружении с одновременной работой

    . пользователи.
  • 2 будет работать, но ограничит масштабируемость (обязательный Том Кайт

    ). Справочник: http://asktom.oracle.com/pls/asktom/f?p=100:11:0::::P11_QUESTION_ID:1508205334476

Как он рекомендует, следует действительно пересмотреть требование о "отсутствии пробелов"

.
7
ответ дан 18 December 2019 в 11:57
поделиться

Непонятно, что вы имеете в виду под "из-за исключений, которые могут произойти". Если вы хотите, чтобы номер NOT был увеличен, если ваша транзакция в конце концов откатится назад, то SEQUENCE вам не подойдет, потому что, насколько я знаю, после запроса NEXTVAL из последовательности позиция последовательности увеличивается и откат не отменяет ее.

Если это действительно требование, то вам, вероятно, придется прибегнуть к хранению текущего счетчика в отдельной таблице, но остерегаться одновременных обновлений - как с точки зрения "потерянного обновления", так и с точки зрения масштабируемости.

.
1
ответ дан 18 December 2019 в 11:57
поделиться

Я думаю, что использование MAX() существующих номеров предрасполагает к новой и захватывающей проблеме - дубликаты могут возникать, если одновременно создается несколько счетов-фактур. (Не спрашивайте меня, откуда я знаю...).

Возможным решением является получение первичного ключа в таблице INVOICE из последовательности, но иметь это НЕ номер счета-фактуры. После правильного и корректного создания счета-фактуры, а также после того, как исключение или прихоть пользователя может привести к прекращению создания счета-фактуры, вы переходите ко второй последовательности, чтобы получить порядковый номер, который представляется как "номер" счета-фактуры. Это означает, что в вашей таблице INVOICE будет два уникальных неповторяющихся числа, а очевидное (INVOICE_NO) не будет первичным ключом (но оно может и должно быть UNIQUE), так что есть немного зла, но альтернатива - создать строку INVOICE с одним значением в первичном ключе, а затем изменить первичный ключ после создания INVOICE - просто слишком зла для слов. :-)

Share and enjoy.

1
ответ дан 18 December 2019 в 11:57
поделиться

Если вы действительно хотите, чтобы не было пробелов, вы должны полностью сериализовать доступ, иначе всегда будут пробелы. Причины разрывов:

  • rollback
  • shutort
1
ответ дан 18 December 2019 в 11:57
поделиться

Возможно, вам придется переосмыслить ваш процесс и разбить его на несколько этапов. На одном не трансакционном шаге создайте счет-фактуру владельца (если он не участвует в сделке, это должно устранить пробелы), а затем в рамках сделки сделайте остальную часть своего бизнеса. Я думаю, что именно так мы делали это в системе, с которой я застрял много лет назад, но я не могу вспомнить - я просто помню, что это было "странно".

Я бы сказал, что последовательность будет гарантировать уникальные/соответствующие номера, но когда вы бросаете транзакции в смесь, это не может быть гарантировано, если только генерация последовательности не находится внутри этой транзакции.

.
0
ответ дан 18 December 2019 в 11:57
поделиться

Для транзакции мы присваиваем транзакции порядковый номер, а затем, когда элемент, который мы обрабатываем, завершается, мы присваиваем ему постоянный номер (тоже последовательность). Для нас это хорошо работает

Regards
. K

0
ответ дан 18 December 2019 в 11:57
поделиться

ссылка dpbradley в #2 звучит как ваша лучшая ставка. Том сохраняет транзакцию с вызывающим абонентом, если вы не хотите, чтобы она была автономной:

create or replace 
function getNextInvoiceNumber()
return number is
   l_invoicenum     number;

   pragma autonomous_transaction;
   begin
      update docs_numbers
         set last_invoice = last_invoice + 1
      returning last_invoice 
      into l_invoicenum;
      commit;

      return l_invoicenum;

   exception
      when others then
         rollback;
         raise;
end;
0
ответ дан 18 December 2019 в 11:57
поделиться

Сохраните текущую последовательность - вы можете использовать следующее для сброса значения до максимума, что в настоящее время хранится в таблице (таблицах):

-- --------------------------------
-- Purpose..: Resets the sequences 
-- --------------------------------

DECLARE
  -- record of temp data table
  TYPE data_rec_type IS RECORD(
    sequence_name VARCHAR2(30),
    table_name    VARCHAR2(30),
    column_name   VARCHAR2(30));

  -- temp data table
  TYPE data_table_type IS TABLE OF data_rec_type INDEX BY BINARY_INTEGER;

  v_data_table data_table_type;
  v_index      NUMBER;
  v_tmp_id     NUMBER;

  -- add row to temp table for later processing
  --
  PROCEDURE map_seq_to_col(in_sequence_name VARCHAR2,
                           in_table_name    VARCHAR2,
                           in_column_name   VARCHAR2) IS
    v_i_index NUMBER;
  BEGIN
    v_i_index := v_data_table.COUNT + 1;
    v_data_table(v_i_index).sequence_name := in_sequence_name;
    v_data_table(v_i_index).table_name := in_table_name;
    v_data_table(v_i_index).column_name := in_column_name;
  END;

  /**************************************************************************
      Resets a sequence to a given value
  ***************************************************************************/
  PROCEDURE reset_seq(in_seq_name VARCHAR2, in_new_value NUMBER) IS

    v_sql       VARCHAR2(2000);
    v_seq_name  VARCHAR2(30) := in_seq_name;
    v_reset_val NUMBER(10);
    v_old_val   NUMBER(10);
    v_new_value NUMBER(10);

  BEGIN

    -- get current sequence value

    v_sql := 'SELECT ' || v_seq_name || '.nextval FROM DUAL';
    EXECUTE IMMEDIATE v_sql
      INTO v_old_val;

    -- handle empty value
    v_new_value := in_new_value;
    if v_new_value IS NULL then
      v_new_value := 0;
    END IF;

    IF v_old_val <> v_new_value then    
      IF v_old_val > v_new_value then
        -- roll backwards
        v_reset_val := (v_old_val - v_new_value) * -1;
      elsif v_old_val < v_new_value then
        v_reset_val := (v_new_value - v_old_val);
      end if;

      -- make the sequence rollback to 0 on the next call
      v_sql := 'alter sequence ' || v_seq_name || ' increment by ' ||
           v_reset_val || ' minvalue 0';
      EXECUTE IMMEDIATE (v_sql);

      -- select from the sequence to make it roll back
      v_sql := 'SELECT ' || v_seq_name || '.nextval FROM DUAL';
      EXECUTE IMMEDIATE v_sql
        INTO v_reset_val;

      -- make it increment correctly again
      v_sql := 'alter sequence ' || v_seq_name || ' increment by 1';
      EXECUTE IMMEDIATE (v_sql);

      -- select from it again to prove it reset correctly.
      v_sql := 'SELECT ' || v_seq_name || '.currval FROM DUAL';
      EXECUTE IMMEDIATE v_sql
        INTO v_reset_val;

    END IF;

    DBMS_OUTPUT.PUT_LINE(v_seq_name || ': ' || v_old_val || ' to ' ||
                     v_new_value);
  END;

  /*********************************************************************************************
    Retrieves a max value for a table and then calls RESET_SEQ.
  *********************************************************************************************/
  PROCEDURE reset_seq_to_table(in_sequence_name VARCHAR2,
                               in_table_name    VARCHAR2,
                               in_column_name   VARCHAR2) IS

    v_sql_body  VARCHAR2(2000);
    v_max_value NUMBER;

      BEGIN

    -- get max value in the table
    v_sql_body := 'SELECT MAX(' || in_column_name || '+0) FROM ' ||
              in_table_name;
    EXECUTE IMMEDIATE (v_sql_body)
      INTO v_max_value;

    if v_max_value is null then
      -- handle empty tables
      v_max_value := 0;
    end if;

    -- use max value to reset the sequence
    RESET_SEQ(in_sequence_name, v_max_value);

  EXCEPTION
    WHEN OTHERS THEN
      DBMS_OUTPUT.PUT_LINE('Failed to reset ' || in_sequence_name ||
                       ' from ' || in_table_name || '.' ||
                       in_column_name || ' - ' || sqlerrm);
  END;

BEGIN
  --DBMS_OUTPUT.ENABLE(1000000);

  -- load sequence/table/column associations

  /***** START SCHEMA CUSTOMIZATION *****/
  map_seq_to_col('Your_SEQ',  
                 'your_table',
                 'the_invoice_number_column');

  /***** END SCHEMA CUSTOMIZATION *****/

  -- iterate all sequences that require a reset
  FOR v_index IN v_data_table.FIRST .. v_data_table.LAST LOOP

    BEGIN
      RESET_SEQ_TO_TABLE(v_data_table(v_index).sequence_name,
                         v_data_table(v_index).table_name,
                         v_data_table(v_index).column_name);
    END;
  END LOOP;

END;
/

-- -------------------------------------------------------------------------------------
-- End of Script.
-- -------------------------------------------------------------------------------------

Пример анонимного sproc - измените его на правильную процедуру в пакете, и вызовите его перед тем, как вставлять новый счет-фактуру, чтобы сохранить последовательность нумерации.

1
ответ дан 18 December 2019 в 11:57
поделиться

I ' мы сталкивались с этой проблемой раньше. В одном случае нам удалось убедить бизнес согласиться с тем, что в «реальных» счетах могут быть пробелы, и мы написали задание, которое выполнялось каждый день, чтобы «заполнить» пробелы «недействительными» счетами для целей аудита.

На практике, если мы добавим NOCACHE к последовательности, количество пробелов будет относительно небольшим, поэтому аудиторы обычно будут довольны, если их запрос на «недействительные» счета-фактуры не вернет слишком много результатов.

1
ответ дан 18 December 2019 в 11:57
поделиться
Другие вопросы по тегам:

Похожие вопросы: