martes, 9 de enero de 2018

Renumerar Secuencias en Oracle (ALTER SEQUENCE)

Uno de mis usuarios me ha notificado hoy un mensaje de error que le ocurre al insertar registros en una tabla. La operación de inserción da un error en la BD Oracle:

HY000 - 1 OCIStmtExecute: ORA-00001: restricción única (...) violada

Analizando la estructura de la tabla, veo que esta tiene como clave primaria un campo ID [NUMBER(7,0)], y que la operación de inserción obtiene el siguiente ID a insertar de una secuencia (CREATE SEQUENCE...). Esto me lleva a sospechar que probablemente el siguiente ID a insertar esté coincidiendo con algún ID ya existente en la tabla, cosa extraña tratándose de una secuencia, y que no debería  pasar, pero... ¡tantas cosas no deberían pasar y pasan!

Vamos con las secuencias en ORACLE.

El juego SEQUENCE



Para saber cuál será el siguiente valor que devolverá una secuencia, podemos hacer dos cosas:

1. Función NEXTVAL.


La instrucción

SELECT SEC_MI_SECUENCIA.nextval FROM dual;
=> 3601

nos devuelve el siguiente número de la secuencia. El único "problema" es que esta operación "consume" un valor de esa secuencia. Supongamos que me devuelve el valor 3601. Si vuelvo a ejecutar la operación, devolvería un nuevo valor, 3602 (asumiendo que la secuencia se incrementa en pasos de 1 unidad). Cada vez que se vuelva a ejecutar esa instrucción, estaré "gastando" un número de la secuencia. Así que, si este comportamiento no es deseable, cuidado. Esto puede ocurrir cuando tenemos tablas donde queremos TODOS los registros que se puedan ir insertando, como una tabla de registro de actividad (log), por ejemplo. Si vemos saltos en la secuencia de IDs, no sabemos si es que son "normales" o es que tal vez alguien ha eliminado registros para borrar las huellas de alguna operación.

SELECT SEC_MI_SECUENCIA.nextval FROM dual; 

=> 3609

2. Función CURRVAL

Esta alternativa permite consultar el último valor que devolvió la secuencia, pero sin generar uno nuevo. Es decir, por muchas ejecuciones que hagamos, siempre nos va a devolver el mismo valor.

select SEC_MI_SECUENCIA.currval from dual;

=> 3609

select SEC_MI_SECUENCIA.currval from dual;

=> 3609

Dicho esto, la solución a mi problema es fácil. Veo en los datos que hay registros con un ID ya coincidente con el próximo que generará la secuencia. Así que obtengo el máximo, que resulta ser

select max(id) from SEC_MI_SECUENCIA;

==> 8596

Así que voy a ALTERAR mi secuencia para que genere números a partir del 8600. Para ello, debo especificar un incremento igual a la diferencia entre el valor actual y el deseado. En mi caso, 8600 - 3609 = 4991. Así que hay que alterar la secuencia DOS VECES: una para poner un incremento de 4991, a continuación obtenemos el siguiente valor, y después volvemos a alterar la secuencia para poner el incremento en 1. Así:

ALTER SEQUENCE SEC_MI_SECUENCIA INCREMENT BY 4991;

SELECT SEC_MI_SECUENCIA.nextval FROM dual;

ALTER SEQUENCE SEC_MI_SECUENCIA INCREMENT BY 1;

Tras esto, ya hemos comprobado que la inserción no da problemas.

Hasta que alguien vuelva a tocar los datos...