2009-12-31 246 views
5

Necesito generar números únicos y consecutivos (para usar en una factura), de una manera rápida y confiable. actualmente usa una secuencia de Oracle, pero en algunos casos los números generados son no consecutivos debido a excepciones que pueden ocurrir.La mejor manera de generar números únicos y consecutivos en Oracle

Pensé en un par de soluciones para manejar este problema, pero ninguno de ellos me convenció. ¿Qué solución recomiendas?

  1. Utilice un select max()

    SELECT MAX (NVL (doc_num, 0)) +1 FROM invoices 
    
  2. utilizar una tabla para almacenar el último número generado por la factura.

    UPDATE docs_numbers 
        SET last_invoice = last_invoice + 1 
    
  3. ¿Otra solución?

+0

¿Has probado la opción 'NOCACHE' en' CREATE SEQUENCE'? Eso debería eliminar las lagunas. – skaffman

+6

@skaffman: No, 'NOCACHE' especifica cuántos valores de secuencia se almacenarán en la memoria para un acceso más rápido: http://www.techonthenet.com/oracle/sequences.php –

Respuesta

3

Los huecos aparecen si una transacción utiliza un número de secuencia pero luego se revierte.

Quizás la respuesta no sea asignar el número de factura hasta que la factura no se pueda deshacer. Esto minimiza (pero probablemente no elimina) las posibilidades de brechas.

No estoy seguro de que haya una forma rápida o fácil de garantizar la ausencia de huecos en la secuencia: escanear para MAX, agregar uno e insertarlo probablemente sea lo más cercano a seguro, pero no se recomienda por motivos de rendimiento (y dificultades con la concurrencia) y la técnica no detectará si se asigna el último número de factura, luego se elimina y se reasigna.

¿Puede explicar las lagunas de alguna manera, identificando qué números de factura fueron 'usados' pero 'no hechos permanentes' de alguna manera? ¿Podría una transacción autónoma ayudar a hacer eso?


Otra posibilidad - suponiendo que las lagunas son relativamente pocas y distantes.

Cree una tabla que registre los números de secuencia que se deben reutilizar antes de que se capture un nuevo valor de secuencia. Normalmente, estaría vacío, pero algún proceso que se ejecuta cada ... minuto, hora, día ... busca huecos e inserta los valores perdidos en esta tabla. Todos los procesos primero verifican la tabla de valores perdidos, y si hay algún presente, use un valor desde allí, pasando por el lento proceso de actualización de la tabla y eliminando la fila que usan. Si la tabla está vacía, toma el siguiente número de secuencia.

No es muy agradable, pero el desacoplamiento de 'emitir números de factura' de 'escanear valores perdidos' significa que incluso si el proceso de facturación falla para un subproceso cuando está utilizando uno de los valores perdidos, ese valor será redescubierto para perder y volver a publicar la próxima vez, repetir hasta que algún proceso tenga éxito.

1

Mantener la secuencia actual - se puede utilizar el siguiente para restablecer el valor al máximo de lo que está almacenado actualmente en la tabla (s):

-- -------------------------------- 
-- 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. 
-- ------------------------------------------------------------------------------------- 

El ejemplo es un procedimiento almacenado en el anonimato - cámbielo por procedimientos correctos en un paquete, y llámelo antes de insertar una nueva factura para mantener la numeración constante.

7

Como él recomienda, que realmente debe revisar la necesidad de los "sin huecos" requisito

+0

La contabilidad Cdn (y probablemente EE. UU.) Requiere el" no " brechas "en los números de factura como medio de detección de fraude. –

+2

También he escuchado esto, pero como se señala en el enlace, nadie parece poder señalar algo que prohíbe los vacíos, creo que las brechas solo tienen que ser explicables. – dpbradley

+0

huecos es un retroceso a documentos preimpresos como facturas. Era un mecanismo de control que realmente ya no es necesario para la mayoría de las aplicaciones informáticas. Un ejemplo de donde todavía sería aplicable es cheques. – David

1

No está claro a qué te refieres con 'debido a las excepciones que pueden ocurrir'. Si deseas que NO se incremente el número si tu transacción finalmente retrocede, entonces SEQUENCE no te va a funcionar, porque hasta donde yo sé, una vez que NEXTVAL está solicitado desde secuencia la secuencia positio n se incrementa y la reversión no lo invertirá.

Si esto es realmente un requisito, entonces probablemente tendrías que recurrir al almacenamiento del contador actual en una tabla separada, pero ten cuidado con las actualizaciones concurrentes, tanto de 'actualización perdida' como de prospectiva de escalabilidad.

0

Es posible que tenga que volver a pensar su proceso ligeramente y dividirlo en más pasos. Haga que un paso no transaccional cree la factura del marcador de posición (esto no está en la transacción debe eliminar las brechas) y luego dentro de la transacción haga el resto de su negocio. Creo que fue así como lo hicimos en un sistema con el que estuve atrapado hace años, pero no recuerdo, solo recuerdo que fue "raro".

Diría que la secuencia garantizará números únicos/consecutivos pero cuando se lanzan transacciones en la mezcla no se puede garantizar a menos que la generación de la secuencia no esté dentro de esa transacción.

+2

Las secuencias de Oracle solo se deben utilizar para garantizar la exclusividad, no los números consecutivos. –

0

El enlace de dpbradley en el n. ° 2 parece ser la mejor opción. Tom mantiene la transaccionalidad con la persona que llama, si no quiere que se podía hacer una transacción autónoma, así:

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

Creo que usted encontrará que el uso de la MAX() de los números existentes es propenso a un nuevo y emocionante problema: pueden producirse duplicados si se crean varias facturas al mismo tiempo. (No me preguntes cómo lo sé ...).

Una posible solución es derivar la clave primaria en su tabla de FACTURA de una secuencia, pero no debe ser el número de la factura. Después de crear correcta y correctamente su factura, y después del punto en el que una excepción o capricho del usuario puede causar la terminación de la creación de la factura, vaya a una segunda secuencia para obtener el número secuencial que se presenta como "el" número de factura . Esto significa que tendrá dos números únicos que no se repiten en su tabla de FACTURA, y el obvio (INVOICE_NO) no será la clave principal (pero puede y debe ser ÚNICO), por lo que hay un poco de maldad, pero la alternativa, que es crear la fila FACTURA con un valor en la clave primaria, luego cambiar la clave primaria después de que se crea la FACTURA, es demasiado mala para las palabras. :-)

Comparta y disfrute.

0

Lo que hacemos es emitir un número de secuencia para la transacción y luego, cuando el elemento que estamos procesando se finaliza emitimos un número permanente (también una secuencia). Funciona bien para nosotros.

Saludos
K

+0

Es el mismo problema, esa segunda actualización puede fallar por varias razones – steve

+0

Puede, y nosotros verificamos eso. El proceso que asigna el número permanente es pequeño y ocurre después de todo otro procesamiento de los datos, por lo que las posibilidades de que falle algo son escasas. – Khb

1

Si realmente quiere tener ningún espacio, es necesario serializar completamente el acceso, de lo contrario siempre habrá lagunas. Las razones de las lagunas son:

  • rollback
  • apagado abortar
1

me he encontrado con este problema antes. En un caso, pudimos convencer a la empresa para que aceptara que las facturas "reales" pudieran tener vacíos, y escribimos un trabajo que se ejecutaba todos los días para "completar" las lagunas con las facturas "nulas" para fines de auditoría.

En la práctica, si ponemos NOCACHE en la secuencia, el número de huecos sería relativamente bajo, por lo que los auditores generalmente estarán contentos siempre que su consulta en las facturas "nulas" no devuelva demasiados resultados.

Cuestiones relacionadas