Ir al contenido


Foto

Secuencias consecutivas con Firebird


  • Por favor identifícate para responder
8 respuestas en este tema

#1 enecumene

enecumene

    Webmaster

  • Administrador
  • 7.419 mensajes
  • LocationRepública Dominicana

Escrito 08 junio 2017 - 08:29

Amigos, necesito que me aporten ideas con este asunto, me explico, en mis módulos ya sea de facturación, pedidos, descpachos, entradas de mercancías, etc, tienen dos campos, uno es el ID interno ése utiliza su Generador y el otro es el número que se presenta ante el usuario final y aquí utilizo el select MAX(campo) + 1, el problema que tengo es que el primero siempre me deja saltos y huecos y el segundo cada x tiempo se presenta el problema de duplicados, sólo en una tabla encontré que ese campo no es único por lo que no puedo hacer nada teniendo registros duplicados, díganme cual es la mejor manera de hacer lo segundo, o sea, mostrar un secuencia limpia, consecutiva y sin huecos?, lo pregunto porque he estado leyendo por ahí ideas e ideas y nada de ejemplos, y que el select max(campo) + 1 es muy lento.

 

Espero sus comentarios.

 

Saludos.


  • 0

#2 Marc

Marc

    Advanced Member

  • Moderadores
  • PipPipPip
  • 1.484 mensajes
  • LocationMallorca

Escrito 08 junio 2017 - 11:32

Yo me olvidaría del Max(ID) + 1. No tiene porque ir lento (ya que si el campo ID es clave primaria, entonces tendrá un índice, con lo que el cálculo de Max(ID) será inmediato mediante ese índice), pero no le sé ver ninguna ventaja respecto a las secuencias (además del grave problema de que puede resultar en duplicados).

 

Así que yo me centraría en el motivo por el que las secuencias te generan vacíos. Eso ocurre cuando se ha solicitado un valor para una nueva ficha, y después esa ficha no se llega a guardar en la base de datos : porque el usuario ha cancelado el alta, ha ocurrido un error, etc. ...

 

Lo que debes hacer para evitar vacíos es retrasar la solicitud de nuevo valor hasta el último momento, cuando ya el usuario le ha dado al botón de Guardar, y por tanto no tiene posibilidad de cancelar dicha alta (lo que deja esos vacíos).

 

Si lo quieres llevar al extremo, puedes generar el nuevo valor dentro de un trigger "Before Insert" de tu tabla. De esta forma tu campo se convierte básicamente en un campo autonumérico : en el momento de insertar un nuevo registro se le asigna el valor siguiente (mediante una secuencia).


php
  1. CREATE TRIGGER MI_TABLA_Autoinc FOR MI_TABLA
  2. ACTIVE BEFORE INSERT POSITION 0
  3. AS
  4. BEGIN
  5.   IF (NEW.ID IS NULL) THEN
  6.     NEW.ID = GEN_ID(MI_TABLA_Secuencia,1);

Por cierto, si después borras una fila, esto te va a generar un vacío. No hay forma de evitarlo.


  • 1

#3 enecumene

enecumene

    Webmaster

  • Administrador
  • 7.419 mensajes
  • LocationRepública Dominicana

Escrito 08 junio 2017 - 01:44

con el generador el vacío aparece cuando al insertar ocurre un error y como quiera se genera el identificador y crea un salto *, porque uso decidí hacer uso de MAX() + 1, pero cada cierto tiempo si dos usuarios inserta al mismo tiempo crea un duplicado, o sea, dos despachos con el mismo número, por eso acudo aquí para ver otra solución.

 

(*) Por ejemplo, el último número es 5, y un usuario al hacer despacho guarda los datos y al momento de insertar en la base ocurre un error, ya ahí se suma uno más (6), si vuelve lo intenta otra vez genera uno mas (7), y vuelve a intentarlo haciendo cambios se guarda y se genera el número 8, o sea de la facura no. 5 salta a la 8, por esa razón utilizo el MAX() + 1. El trigger que propones no es exactamente lo mismo que el autoincremental? ya que en IBExpert se crea el Generador con su respectivo trigger.


  • 0

#4 Gaston

Gaston

    Advanced Member

  • Miembros
  • PipPipPip
  • 109 mensajes

Escrito 08 junio 2017 - 09:53

Hola enecumene, para la registración de asientos contables utilizo algo sencillo, de momento funciona sin problemas, no genera duplicados, y es consecutiva mientras no eliminen un asiento que no sea el último, o sea, si tengo 100 asientos y me borran el 33 y el 70, luego el próximo asiento será el 101, si no borran nada también será el 101 y si borran 99 también y dejan el último también. Si borran todos, entonces será el 1.

Al momento de grabar el asiento, previa validación de todos los datos, hago un select ordenado por número de asiento DESC LIMIT 1, eso me trae el último número, le sumo 1, lo guardo en una variable y guardo el registro, si se produce un error al guardar no pasa nada. El tema de los vacíos, estos se producen cuando se borra un asiento, tengo una opción renumerar, pero desde ya no creo que sea apropiado para facturas.

Es muy parecido a tu método, sólo que "escondo el número" hasta que se guarda correctamente, hay mando un showmessage diciendo "Asiento N° X guardado correctamente". Otra cosa, el nro. de asiento es clave primaria, si no lo fuese, existiría una muy remota posibilidad de duplicados. Con el métdo que describe Marc, mediante un trigger, creo que es más efectivo para descartar los duplicados.

 

Ahora si tienes que sí o sí mostrar el número mientras se cargan los datos, es decir, antes de guardar, mi método no sirve.

 

Saludos.


  • 0

#5 Marc

Marc

    Advanced Member

  • Moderadores
  • PipPipPip
  • 1.484 mensajes
  • LocationMallorca

Escrito 09 junio 2017 - 04:58

con el generador el vacío aparece cuando al insertar ocurre un error y como quiera se genera el identificador y crea un salto *, porque uso decidí hacer uso de MAX() + 1, pero cada cierto tiempo si dos usuarios inserta al mismo tiempo crea un duplicado, o sea, dos despachos con el mismo número, por eso acudo aquí para ver otra solución.

 

(*) Por ejemplo, el último número es 5, y un usuario al hacer despacho guarda los datos y al momento de insertar en la base ocurre un error, ya ahí se suma uno más (6), si vuelve lo intenta otra vez genera uno mas (7), y vuelve a intentarlo haciendo cambios se guarda y se genera el número 8, o sea de la facura no. 5 salta a la 8, por esa razón utilizo el MAX() + 1. El trigger que propones no es exactamente lo mismo que el autoincremental? ya que en IBExpert se crea el Generador con su respectivo trigger.

 

¿ Que tipo de errores te encuentras al guardar ?. Por eso te decía de asignar el autonumérico en un trigger (como dices, ese trigger es lo mismo que el autoincremental que crea IB-Expert), porque en este momento ya se ha ejecutado todo el código Delphi, y el único posible error puede ocurrir aún es en los constraints de la tabla : claves foráneas, índices únicos, ....  (o en triggers, pero con ordenar los triggers para que el autoincremental sea el último trigger a ejecutarse, evitas también esos errores).

 

Así que en principio crear un autoincremental con triggers y secuencias solo te podría crear vacíos por errores de las relaciones de integridad, que són los únicos errores que aún pueden ocurrir en ese punto.

 

Si tus erores son de relación de integridad, entonces tendremos que buscar otra solución (una tabla de contadores). Pero si los errores que tienes son previos, en tu código Delphi, entonces implementarlo de esta forma te evitará esos vacíos.

 

Saludos.


  • 0

#6 Delphius

Delphius

    Advanced Member

  • Administrador
  • 6.295 mensajes
  • LocationArgentina

Escrito 09 junio 2017 - 05:37

Por empezar déjenme decir algo.

En una facturación como en una contabilidad la función de eliminar físicamente un registro no es una opción. Es ILEGAL. No debe existir la posibilidad, via sistema, de que el usuario elimine estos registros.

En su lugar se anula la factura y se emite otra, y para un asiento contable mal confeccionado se ingresa un nuevo asiento (a veces llamado contra asiento) en que se detalle los movimientos correctivos.

 

Otra alternativa para tener en orden en las numeraciones es llevar una tabla adicional con la cuenta a modo de log o bitácora. De este modo se examina o consulta esta tabla para conocer el próximo ID y se procede a actualizar cuando finalice la operación.

La opción de leer el último registro como la que comenta Gaston en un ambiente C/S donde hay varios clientes en simultáneo puede traer problemas. Con la incorporación de esta tabla "CONTADORES" básicamente lo que se hace es que cada vez que un cliente va a insertar un nuevo registro incrementa el valor, y se lleva un control de quien pidió a que valor.

 

Por ejemplo, Pepito inició una nueva factura, lee la tabla y se le "asigna" a el la Factura 5. Luego está Ana y atiende a otro cliente, genera otra factura, y el sisteme le otorga el número 6. Pepito es un tipo lento... y Ana se lleva muy rápido con la PC, le gana y termina. Se ha materializado la Factura 6. El cliente que está con Pepito se arma de paciencia, y espera. Pepito confirma y se ha generado la factura 5. Ahora viene otro cliente, Pepito la tortuga lo atiende, el sistema le otorga el valor 7. Mientras tanto Ana ya se pone a atender a otro más, y ya el sistema detecta que debe darle el número 8. Ana nuevamente gana, se materializa la factura 8. Pepito se duerme en sus laureles, y el cliente se cansa y se va. Pepito ahora debe cancelar todo. Físicamente esto termina en la generación de la factura 7 anulada.

No hay huecos. ;)

 

Ahora bien voy a decir algo que ya hace un tiempo había comentado: para crear los números de factura (o cualquier cosa que no permita huecos en su numeración) JAMAS se debe utilizar las sequencias (FB 2+) o generadores (FB 1.5-) debido a que éstos escapan a las transacciones. Una cancelación de la transacción no vuelve atrás a la sequencia o al generador. Esto es motivo para que queden huecos.

Como bien ha dicho Marc, la opción es postergar la asignación y creación físicamente de la factura en la base de datos sino es hasta que el usuario confirme. Mientras tanto, se trabaja con los datos "en memoria", o bien en una cache local y luego proceder a materializar la operación sobre la DB.

 

Saludos,


  • 0

#7 enecumene

enecumene

    Webmaster

  • Administrador
  • 7.419 mensajes
  • LocationRepública Dominicana

Escrito 09 junio 2017 - 01:12

Hola Delphius, en realidad todo se trabaja en memoria, y se guarda ya en la confirmación, el problema de mi método actual es cada x tiempo  ocurre una duplicación de número, eso ocurre en una de las tablas donde el campo de numeración no es única, así es como la encontré y he estado teniendo problemas en ese aspecto,

 


Otra alternativa para tener en orden en las numeraciones es llevar una tabla adicional con la cuenta a modo de log o bitácora. De este modo se examina o consulta esta tabla para conocer el próximo ID y se procede a actualizar cuando finalice la operación.

....

 

Podrías detallar un poco más esa metodología?


  • 0

#8 Rolphy Reyes

Rolphy Reyes

    Advanced Member

  • Moderadores
  • PipPipPip
  • 2.092 mensajes
  • LocationRepública Dominicana

Escrito 09 junio 2017 - 01:35

Saludos.

 

Yo implementó una tabla de secuencias, la misma se actualiza cada vez que se hace guardar justo antes del Commit o Post.

 

Puedes además, implementar el LOCK que previamente discutimos con el tema de las existencias.


  • 0

#9 Delphius

Delphius

    Advanced Member

  • Administrador
  • 6.295 mensajes
  • LocationArgentina

Escrito 09 junio 2017 - 03:54

Hola Delphius, en realidad todo se trabaja en memoria, y se guarda ya en la confirmación, el problema de mi método actual es cada x tiempo  ocurre una duplicación de número, eso ocurre en una de las tablas donde el campo de numeración no es única, así es como la encontré y he estado teniendo problemas en ese aspecto,

 

 

Podrías detallar un poco más esa metodología?

 

Es relativamente simple.

Se trata de tener una tabla más, y usarla para almacenar la secuencia generada. La forma más simple consiste en tener un único registro en el que simplemente se va a actualizar e incrementar cada vez que se confirme una factura. Cuando creamos una factura leemos el valor que hay, incrementamos y guardamos. En esta tabla vamos a encontrar siempre el último número.

 

La variación que propongo es un poco más elaborada. En esta tabla se va añadiendo registros e incrementando el valor (puede ser mediante secuencias) pero además se va a añadir info adicional como el usuario que está efectuando esta operación. Cada vez que el usuario va a realizar una factura se añade un nuevo registro en esa tabla, cuando se confirma la operatoria se le asigna a la factura la numeración que se ha producido para ésta. Y en caso de que el usuario haya descartado, simplemente se marca la factura como anulada.

Esto puede funcionar con varios usuarios... cada uno cuando va a proceder a emitir factura va a insertar registros y para cada uno el propio motor gracias a las secuencias les dará un valor único y que ira de forma incremental. Este valor es el que recuperamos luego para asignarle a la factura, ya sea confirmada la venta o anulada.

 

La tabla puede tener una estructura así:

 

Tabla: EmisionDeFacturas

-----------------------------------

IDEmision: PK

FechaHora: TIMESTAMP

UsuarioID: FK(Usuarios.IDUsuario)

FacturaNro: INTEGER // Este es el valor que iremos generando e incrementando con una secuencia o generador

 

Podemos usar, temporalmente, el IDEmision para tener una asociación o referencia sobre la última factura que ha emitido el usuario. Es decir que al iniciar el proceso de facturación, lo primero que hacemos es dar de alta un registro en EmisionDeFacturas y gracias al "RETURNING IDEmision" es que podemos saber que registro leer para poder asignar a la factura que vamos a guardar el numero correspondiente.

Este esquema de trabajo permite trabajar de manera "perezosa" y en simultáneo con varias. Con otros enfoques se necesita postergar lo más posible la operación de un alta de factura. Acá podemos ir dando de alta una factura (podríamos llamarla una factura temporal) y cuando finalicemos (ya sea por un si o un no) recién le asignamos la numeración y tiene carácter legal. Todo de forma independiente de cuanto dure una operación.

 

Hay que jugar con algunos triggers, y requiere de más pasos pero es otra alternativa que puede complementar a otros enfoques. Incluso puede ser útil combinarlo con el enfoque de Marc. O lo que bien ha dado Sergio de lección en este hilo.

 

Espero que se me entienda.

 

Saludos,


  • 1




IP.Board spam blocked by CleanTalk.