2009-06-03 25 views
5

Como en la pregunta, ¿cómo restablezco automáticamente el valor de una secuencia de Oracle a 0 cada año en Oracle 10g?¿Cómo puedo restablecer automáticamente el valor de una secuencia a 0 cada año en Oracle 10g?

Estoy usando la secuencia para generar un identificador en el formato YYYY<sequence value> y el valor de secuencia debe restablecerse a 0 cada año.

YYYY se obtiene de Java y se concatena con el valor de secuencia de Oracle. El formato del identificador no se puede cambiar debido a requisitos externos de terceros. Gracias por cualquier ayuda de antemano.

Respuesta

6

Las secuencias no están realmente diseñadas para reiniciarse. Pero hay algunos casos en los que es deseable reiniciar una secuencia, por ejemplo, al configurar los datos de prueba o al fusionar los datos de producción en un entorno de prueba. Este tipo de actividad es no normalmente en producción.

SI este tipo de operación se va a poner en producción, necesita una prueba minuciosa. (Lo que más preocupa es la posibilidad de que el procedimiento de reinicio se realice accidentalmente en el momento equivocado, como a mediados de año.

La eliminación y recreación de la secuencia es un enfoque. Como operación, es bastante sencillo en cuanto a la secuencia va:

 
    DROP SEQUENCE MY_SEQ; 
    CREATE SEQUENCE MY_SEQ START WITH 1 INCREMENT BY 1 MINVALUE 0; 

[EDIT] como Matthew Watson señala correctamente, cada sentencia DDL (tales como DROP, CREATE, ALTER) hará que cometa un implícito [/ EDIT]

.

Pero, cualquier privilegio otorgado en la SECUENCIA se eliminará, por lo que deberá volver a otorgarse. Cualquier objeto que se refiera ya que la secuencia será invalidada. Para hacer esto más general, necesitaría guardar privilegios (antes de descartar la secuencia) y luego volver a otorgarlos.

Un segundo enfoque consiste en ALTERAR una SECUENCIA existente, sin soltarla y volverla a crear. Restablecer la secuencia se puede lograr cambiando el valor de INCREMENTO a un valor negativo (la diferencia entre el valor actual y 0), y luego hacer exactamente un .NEXTVAL para establecer el valor actual a 0, y luego cambiar el INCREMENT de nuevo a 1. He usado este mismo enfoque antes (manualmente, en un entorno de prueba), para establecer una secuencia a un valor mayor también.

Por supuesto, para que esto funcione correctamente, necesita asegurar ninguna otra sesión hace referencia a la secuencia mientras se realiza esta operación. Un .NEXTVAL adicional en el momento incorrecto arruinará el reinicio.(NOTA: lograr eso en el lado de la base de datos va a ser difícil, si la aplicación se conecta como el propietario de la secuencia, en lugar de como un usuario separado.)

Para que suceda todos los años, necesitaría para programar un trabajo. El reinicio de la secuencia tendrá que coordinarse con el restablecimiento de la parte YYYY de su identificador.

Aquí está un ejemplo:

http://www.jaredstill.com/content/reset-sequence.html

[EDIT]

sin probar marcador de posición para un posible diseño de un bloque PL/SQL para restablecer secuencia

 
    declare 
     pragma autonomous_transaction; 
     ln_increment  number; 
     ln_curr_val  number; 
     ln_reset_increment number; 
     ln_reset_val  number; 
    begin 

     -- save the current INCREMENT value for the sequence 
     select increment_by 
     into ln_increment 
     from user_sequences 
     where sequence_name = 'MY_SEQ'; 

     -- determine the increment value required to reset the sequence 
     -- from the next fetched value to 0 
     select -1 - MY_SEQ.nextval into ln_reset_increment from dual; 

     -- fetch the next value (to make it the current value) 
     select MY_SEQ.nextval into ln_curr from dual; 

     -- change the increment value of the sequence to 
     EXECUTE IMMEDIATE 'alter sequence MY_SEQ increment by ' 
     || ln_reset_increment ||' minvalue 0'; 

     -- advance the sequence to set it to 0 
     select MY_SEQ.nextval into ln_reset_val from dual; 

     -- set increment back to the previous(ly saved) value 
     EXECUTE IMMEDIATE 'alter sequence MY_SEQ increment by ' 
     || ln_increment ; 
    end; 
    /

NOTAS:

  • cómo proteger mejor la secuencia de acceso mientras se está restableciendo, ¿RENOMBRAR?
  • Varias cajas de prueba para trabajar aquí.
  • En primer paso, verifique los casos normativos de secuencia ascendente, incremental 1 positiva.
  • ¿sería un mejor enfoque crear una nueva SECUENCIA, agregar permisos, renombrar secuencias existentes y nuevas, y luego volver a compilar dependencias?
+0

CUIDADO !! Hacer DML implicará un commit implicado. Si necesita deshacer su transacción, no podrá hacerlo después de ejecutar esto. –

+1

Interesante. Pero como buena práctica, tiendo a tener mis secuencias locales para el esquema al que pertenecen, con lo que quiero decir que solo se usan en triggers/functions/procedures/packages en el mismo esquema. Por lo tanto, no tengo problemas relacionados con permisos, y la solución DROP/CREATE funciona bien. Si necesito un valor de secuencia fuera del esquema, siempre puedo usar una función/procedimiento personalizado. Además, esto es portátil (concedido, esto es inútil la mayor parte del tiempo). – Mac

+0

corrección: DDL realiza una confirmación, DML no confirma por sí mismo.(y ALTER SEQUENCE es DDL, entonces sí, este es un problema aquí si alguien intenta usar esto como parte de su código de transacción normal) –

1

No estoy seguro de que haya una buena manera de hacerlo, esta no es la secuencia para la que están diseñadas. solo son números únicos que aumentan de forma pura.

2 pensamientos vienen a la mente.

  1. A las 12 a.m. en la primera, reinicie la secuencia, esto es difícil, porque necesita asegurarse de superar cualquier código.
  2. Cree una secuencia para cada año, tal vez incluso tenga en su código para poder crear la secuencia, luego llame dinámicamente la secuencia correcta para el año.

Tiendo a favorecer la opción 2, ya que no está tratando de hacer nada elegante y siempre va a funcionar sin falta, cualquier opción que intente manipular la secuencia en sí misma te morderá.

+1

Una combinación de las dos soluciones. Ten una secuencia diferente para cada año. Cree veinte años de anticipación ya que no ocupan mucho espacio. Tiene un sinónimo que apunta al actual, y un trabajo por lotes que se ejecuta a las 12 a.m. en la primera para volver a marcar el sinónimo. Sin cambios de código. Los relojes con cuidado pueden no estar exactamente sincronizados (p. Ej., El nivel de Java puede pensar que es 2010 antes del nivel de la base de datos) y el trabajo puede retrasarse unos segundos. Tal vez soltar el sinónimo a las 11:58 y crearlo a las 12:02. –

0

En primer lugar, no parece ser una manera de hacer que la secuencia se reinicie automáticamente cada año. Lea esta referencia:

http://www.psoug.org/reference/OLD/sequences.html?PHPSESSID=5949da378678fa6d24b6fcc6eaae9888

Mi enfoque será:

  1. crear una tabla con el año y la secuencia de arranque para ese año (permite llamar a este year_seed tabla)

  2. crea un procedimiento que recibe el año, verifica la tabla year_seed y si es el primer cheque para el año genera el registro con la secuencia de inicio. Este procedimiento también debe devolver la secuencia menos la secuencia de inicio del año.

tal vez no es tan simple, pero creo que es la mejor solución. Buena suerte

+0

En realidad, el enlace al que se hace referencia SÍ muestra ejemplos de cómo restablecer una secuencia, incluido un procedimiento almacenado. – spencer7593

+0

Sí, lo hace, pero por lo que puedo ver, no hay forma de que se reinicie automáticamente cada año. – tekBlues

3

Use un trabajo para hacer el truco. En primer lugar, crear un procedimiento almacenado para restablecer la secuencia (por lo general voy con la caída/Crear solución, pero se puede usar truco spencer7593 's):

CREATE OR REPLACE PROCEDURE my_seq_reset AS 
BEGIN 
    EXECUTE IMMEDIATE 'DROP SEQUENCE my_seq'; 
    EXECUTE IMMEDIATE 
     'CREATE SEQUENCE my_seq' || 
     ' MINVALUE 1 ' || 
     ' MAXVALUE 999999 ' || 
     ' START WITH 1 ' || 
     ' INCREMENT BY 1 ' || 
     ' NOCACHE'; 
END; 

continuación, crear el puesto de trabajo (see here para la referencia):

BEGIN 
    dbms_scheduler.create_job(
    job_name  => 'job$my_seq_reset', 
    job_type  => 'STORED_PROCEDURE', 
    job_action  => 'my_seq_reset', 
    start_date  => TO_DATE('01-01-09', 'DD-MM-RR'), 
    repeat_interval => 'FREQ=YEARLY;BYDATE=0101', 
    enabled   => TRUE, 
    auto_drop  => FALSE, 
    comments  => 'My sequence yearly reset job.' 
); 
END; 

Ya ha terminado.

+0

Tenga en cuenta que puede haber un retraso entre el tiempo programado para un trabajo y el momento en que realmente se ejecuta, por lo que puede haber un momento en que las transacciones en los primeros minutos del año continúen obteniendo valores de secuencia antes de restablecerse. –

5

sólo tirar esto hacia fuera allí como una idea:

Si desea una solución que no requiere DDL en curso (es decir,no dejar caer y crear o restablecer secuencias), o incluso cualquier trabajo, podría considerar algo como esto (esto es solo en principio, no he probado este enfoque, pero estoy seguro de que funcionará):

  1. Crea una secuencia única.

  2. Cree una tabla de referencia, con una fila por cada año, p.

    años (número del año (4,0) CLAVE principal, número valor_inicial)

  3. Al llegar NEXTVAL de la secuencia, y luego tienes que restar el starting_value cuando se les pregunta de la tabla años para el año en curso. Si no se encuentra el año, se debe insertar una nueva fila (es decir, el primer proceso ejecutado en un año dado insertará el nuevo valor).

e.g. una función, p. get_year_starting_value (pn_year IN NUMBER) RETURN NUMBER podría consultar esta tabla y devolver el starting_value para el año dado; si obtiene NO_DATA_FOUND, podría llamar a un procedimiento para insertarlo usando el NEXTVAL de la secuencia (confirmado en una transacción autónoma para que el nuevo valor esté inmediatamente disponible para otras sesiones, y para que la función no falle debido al lado efecto)

Probablemente no sea una solución para todos los casos, pero creo que este enfoque puede ayudar en al menos algunos escenarios.

+1

No creo que haya una respuesta a la pregunta tal como se le preguntó: no puede hacerlo automáticamente; las secuencias no están destinadas a ser utilizadas de esa manera. Esta parece ser una forma de lograr la mayor parte de lo que realmente se necesita. Creo que me inclinaría a dejar de tratar de usar una secuencia en absoluto. –

+0

@Jeffrey, parece un enfoque viable. Me preocupa ese oscuro caso de esquina, cuando dos transacciones separadas "descubren" la fila del año perdido de la mesa, parece que la serialización del acceso a la mesa es la forma segura de evitarlo (pero las secuencias son sobre cómo reducir el impacto en el rendimiento) de acceso serializado a los recursos en primer lugar). No estoy seguro de que una transacción autónoma resuelva el problema. Una estrategia de bloqueo oportunista puede funcionar, solo tenemos que ocuparnos de los casos de excepción cuando no se encuentra una fila para el año y evitar que se eliminen las filas. – spencer7593

+2

Sí. La restricción de clave principal en la tabla AÑOS efectivamente serializaría el acceso a la tabla, y esto solo ocurriría una vez al año; el procedimiento debería manejar la excepción DUP_VAL_ON_INDEX de manera apropiada. La idea de la transacción autónoma es solo evitar la confirmación que afecta la transacción de llamada. –

0

He encontrado que era mejor crear un disparador y una tabla. La tabla contendrá el año y la secuencia del año. El desencadenante obtiene el año actual, verifica la tabla, si no se encontró ningún registro, luego inserta uno nuevo a partir de 1. De lo contrario, seleccione el último y se incrementa en uno, actualizando la tabla correspondiente.

La Tabla :

create table GDRDOCUMENTOSEQ 
(
    noano NUMBER(4), 
    noseq NUMBER(6) 
) 
; 
alter table GDRDOCUMENTOSEQ 
    add unique (NOANO); 

El gatillo:

CREATE OR REPLACE TRIGGER "GDRGUIARESSARCIMENTONODOC_BIR" 
BEFORE INSERT ON GDR.GDRGUIARESSARCIMENTO 
FOR EACH ROW 

DECLARE 
    lNoAno number; 
    lNoSeq number; 
    lQtd number; 
begin 

    SELECT EXTRACT(YEAR FROM SYSDATE) into lNoAno FROM DUAL; 

    SELECT COUNT(0) 
    INTO lQtd 
    FROM gdr.gdrdocumentoseq ds 
    WHERE ds.noano = lNoAno; 

    IF lQtd = 0 then 
    lNoSeq := 1; 
    INSERT INTO GDR.GDRDOCUMENTOSEQ (NOANO, NOSEQ) VALUES (lNoAno, lNoSeq); 
    else 
    SELECT nvl(max(ds.noseq), 0) + 1 
     INTO lNoSeq 
     FROM gdr.gdrdocumentoseq ds 
    WHERE ds.noano = lNoAno; 

    UPDATE GDR.GDRDOCUMENTOSEQ ds 
     SET ds.noseq = lNoSeq 
    WHERE ds.noano = lNoAno; 
    end if; 

    :new.nodocumento := SUBSTR(lNoAno, 3) || lpad(lNoSeq, 6, '0'); 

end; 

tengo este código que se ejecuta en la producción de 2016. Estado CurrentY de la tabla es:

NOANO NOSEQ 
2017 1411  
2016 237 
Cuestiones relacionadas