Jump to content


Photo

Conocer el ultimo registro de una tabla firebird

firebird ultimo

  • Please log in to reply
17 replies to this topic

#1 AngelF

AngelF

    Advanced Member

  • Miembros
  • PipPipPip
  • 100 posts
  • LocationValencia - España

Posted 24 September 2015 - 03:03 AM

Hola a todos.
 
Por aquí con una dudilla existencial. Tengo una tabla en FB 2.5 tal que más o menos así:
 
TABLE REGISTRO_ENTRADA
 
Campo: IDREGISTRO INTEGER NOT NULL Autoincremento, Clave primaria
Campo:  FECHA_ENTRADA DATE NOT NULL,
Campo:  HORA_ENTRADA TIME NOT NULL,
Campo:  FICHERO_ENTRADA VARCHAR(300),
Campo: ....
 
Tengo un programa que me inserta un registro en la tabla REGISTRO_ENTRADA y una vez insertado debo saber cual es el IDREGISTRO que acabo de crear.
Esto lo hago con la siguiente consulta SQL:

sql
  1. SELECT FIRST 1 IDREGISTRO, FECHA_ENTRADA, HORA_ENTRADA FROM REGISTRO_ENTRADA
  2. ORDER BY FECHA_ENTRADA DESC, HORA_ENTRADA DESC

Mi pregunta es, ¿serviría igualmente la siguiente consulta?:

sql
  1. SELECT FIRST 1 IDREGISTRO, FECHA_ENTRADA, HORA_ENTRADA FROM REGISTRO_ENTRADA
  2. ORDER BY IDREGISTRO DESC

En teoría sí. La primera consulta devuelve el último registro teniendo en cuenta la fecha/hora. La segunda consulta devuelve el último registro teniendo en cuenta el IDREGISTRO mayor.
 
Pero siendo un poco paranoico (soy asín, qué le vamos a hacer) ¿existe alguna probabilidad de que el último IDREGISTRO (por fecha) no sea el mayor? Hay que tener en cuenta que es una tabla en la que el usuario puede borrar registros, pero no insertar de forma manual.
 
Un saludo
  • 0

#2 Wilson

Wilson

    Advanced Member

  • Moderadores
  • PipPipPip
  • 2137 posts

Posted 24 September 2015 - 06:46 AM

Si es una aplicación multiusuario y la fecha y hora la toma del PC que hace de  servidor (en el momento de la inserción, por ejemplo vía trigger) entonces el último IDRegistro contendrá la última fecha-hora, pero si la toma de los pcs clientes habría que analizar como le pasas el autoincremental, hay componentes que te piden el nombre del generador y si lo quieres disparar al iniciar o finalizar la inserción, todas estas posibilidades podrían generar algún retraso y no ser el último idRegistro quien contenga la última fecha-hora.

 

Ahora bien, si la fecha-hora  las toma del servidor y el idRegistro lo pasa firebird a la hora de la inserción, entonces el último IdRegistro contendrá la mayor fecha-hora y para consultarlo te bastaría SELECT MAX(ID_REGISTRO) FROM TABLA.

 

Saludos.


  • 0

#3 Agustin Ortu

Agustin Ortu

    Advanced Member

  • Moderadores
  • PipPipPip
  • 831 posts
  • LocationArgentina

Posted 24 September 2015 - 07:08 AM

Lo mas conveniente es hacer un insert into tabla (Id, val1, val2) values (Null, 1, 2) returning Id
 
Desde Delphi, usarias un componente query para ejecutar la consulta y luego consultas el retorno haciendo un simple FieldByName
 

delphi
  1. Query.SQL.Text := 'INSERT INTO Personas (Id, Nombre) VALUES (NULL, :Nombre) RETURNING Id ';
  2. Query.ParamByName('Nombre').AsString := 'Pepito';
  3. Query.Open;
  4. ShowMessage(Query.FieldByName('Id').AsInteger);


  • 0

#4 AngelF

AngelF

    Advanced Member

  • Miembros
  • PipPipPip
  • 100 posts
  • LocationValencia - España

Posted 24 September 2015 - 01:45 PM

Gracias Wilson y Agustín. Me ha servido lo que decís.

En principio, va a ser monousuario con lo que el código de Wilson me bastaría. No sabía lo que apunta Agustín de "returning id", me parece muy interesante.

 

Un saludo.


  • 0

#5 Wilson

Wilson

    Advanced Member

  • Moderadores
  • PipPipPip
  • 2137 posts

Posted 25 September 2015 - 06:21 AM

Gracias Wilson y Agustín. Me ha servido lo que decís.

En principio, va a ser monousuario con lo que el código de Wilson me bastaría. No sabía lo que apunta Agustín de "returning id", me parece muy interesante.

 

Un saludo.

 

Lo que sugiere Agustín es el método más expedito para obtener el último ID, puesto que se logra en una sola llamada al servidor, y como el lo plantea  el ID se toma justo a la hora de la inserción mediante un Trigger o un SP (tal y como se había comentado), lo que permite que todo vaya en orden. Aunque tu aplicación hoy es monousuario podría variar en el futuro y, la manera correcta para que el último ID coincida con la fecha-hora mayor es que estos tres valores  los obtengas justo a la hora de insertar en el lado del server y no a la hora de iniciar la inserción en el lado cliente.


delphi
  1. Query.SQL.Text := 'INSERT INTO Tabla (Id, Fecha, hora) VALUES (NULL, CURRENT_DATE, CURRENT_TIME) RETURNING Id ';
  2. Query.Open;
  3. ShowMessage(Query.FieldByName('Id').AsInteger);


  • 0

#6 Delphius

Delphius

    Advanced Member

  • Administrador
  • 6295 posts
  • LocationArgentina

Posted 25 September 2015 - 06:50 AM

Todo muy lindo pero esto nos los podemos tirar por la borda si uno permite que el usuario pueda modificar la fecha a posteriori. OJO. ;)

 

Saludos,


  • 0

#7 AngelF

AngelF

    Advanced Member

  • Miembros
  • PipPipPip
  • 100 posts
  • LocationValencia - España

Posted 25 September 2015 - 06:54 AM

Todo muy lindo pero esto nos los podemos tirar por la borda si uno permite que el usuario pueda modificar la fecha a posteriori. OJO. ;)

 

Saludos,

 

No, el usuario no puede modificar la fecha. Puede borrar el registro entero, pero no modificarlo ni añadir manualmente.


  • 0

#8 Delphius

Delphius

    Advanced Member

  • Administrador
  • 6295 posts
  • LocationArgentina

Posted 25 September 2015 - 12:00 PM

No, el usuario no puede modificar la fecha. Puede borrar el registro entero, pero no modificarlo ni añadir manualmente.

 

Te propongo el contra-ejemplo:

1) El usuario inserta un registro.

2) Obtienes el id máximo del momento.

3) El usuario elimina ese mismo registro.

 

Pregunta: ¿Cuál es ahora el máximo?

Si lo obtienes por medio de cualquiera de las consultas propuestas obtendrás el máximo anterior y no necesariamente el máximo actualizado. Por tanto ante una nueva inserción se estaría "reclicando" ese último ID que hemos eliminado.

 

Por lo general cuando uno piensa en un campo ID está pensando en un campo que actuará como clave primaria. Por tanto debe ser único, inalterable, y aún ante la pérdida o borrado no debiera de ser reutilizable. Precisamente por ello es que en Firebird han diseñado el uso de Generadores (Secuencias a partir de Firebird 2.5). Como éstos escapan al concepto transaccional una vez generado uno no puede volverse atrás (*).

 

Si te interesa obtener y asegurar que leas siempre el "máximo id" entonces puedes utilizar el concepto de generadores/secuencias. Puedes obtener el último valor generado invocando a dicho generador con un incremento de 0:


sql
  1. SELECT GEN_id (NombreGenerador, 0) FROM RDB$DATABASE;

Y a partir de Firebird 2, con secuencias, puedes obtener actual valor haciendo esto:


sql
  1. SHOW SEQUENCE NombreSecuencia;

La propuesta de Agustín de emplear la cláusura RETURNING viene bien para hacer ambas cosas de una sola vez. Mi consejo además es que te preguntes cuál es el propósito de obtener este ID, y que además te preguntes para que se lo usará ¿tiene un significado o valor para el usuario? ¿Puede haber un quiebre o saltos en su numeración? Responder a esta 2da pregunta te será de utilidad para ver si efectivamente es un campo que vale aplicar como clave primaria o si bien es un campo de clave candidata potencial (Es decir, un campo que podría llegar a ser considerado como clave primaria pero que por cuestiones de diseño o necesidad no cumple con todo los requisitos o que va a ser utilizado para otro contexto).

 

Te pongo un ejemplo, el DNI es un número artificial que nos identifica. En teoría puede ser de utilidad para poder llevar un control de unicidad, es en principio un campo candidato a ser clave primaria. Pero hay contextos y situaciones en que por más que en principio (y la teoría) te dice que podría servir como control de unicidad no te permite ni te garantiza que dicho número en verdad represente a quien dice ser... Hay DNI mellizos y truchos. Al menos en Argentina.

Te pongo un contra ejemplo: el número de factura.  Es otro número artificial que hemos inventado para poder unificar nuestras compras. ¿Es irrepetible? Si. ¿Permite saltos? No. Una vez emitida una factura, por más erronea no podemos volver atrás. Debemos anularla y emitir otra. La numeración sigue. ¿Es una candidata a ser clave primaria? SI.

 

En fin, debes evaluar un par de cosas: que pretendes de dicho ID, y ver si la idea de generadores o secuencias es buena o no. Es una lección que los que usan Firebird deben aprender... cuando aplica el uso de generadores o secuencias.

 

(*) Bueno, hay maneras de volver atrás, al menos con un generador. Pero no es buena idea.

 

Saludos,


  • 2

#9 cram

cram

    Advanced Member

  • Miembro Platino
  • PipPipPip
  • 832 posts
  • LocationMisiones, Argentina

Posted 27 September 2015 - 05:59 PM

En definitiva para solucionar todo tipo de problemas de desfase, etc. en ambientes multiusuarios, se podría utilizar la misma técnica que me recomendaron para los números de serie de facturas. De esta forma sería imposible perder el último registro.

Esto supone:

1. Agregar una tabla donde se almacena el último valor

2. Agregar un campo donde se almacene este valor incrementado cada vez que se agregue una fila.

3. Acceder al valor registrado más alto en este campo

 

y eso sería todo.

 

Saludos


Edited by cram, 27 September 2015 - 05:59 PM.

  • 0

#10 giulichajari

giulichajari

    Advanced Member

  • Miembros
  • PipPipPip
  • 477 posts

Posted 27 September 2015 - 06:20 PM

Tuve el mismo problema al trabajar con DataSnap. Y lo primero que habia pensado era usar la funcion last_insert_id() de mysql. Y descubri que el valor de esta no se restaura, es decir si falla una insercion continua incorrecto. Ademas no sirve porque 2 transacciones pueden ocurrir sobre el ultimo registro a la vez y tendran el mismo id.

 

http://www.forosdelw...ert_id-1127004/

 

Lo ideal es usar generadores..

Saludos


  • 0

#11 Nikolas

Nikolas

    Advanced Member

  • Miembro Platino
  • PipPipPip
  • 604 posts
  • LocationMar del Plata / Bs As / Argentina

Posted 29 September 2015 - 01:25 PM

te dejo un fragmento de codigo que te va a solucionar la cuestion:

 

 

 BuscoPedido.Active:=FALSE;
 BuscoPedido.SQL.Text:='select gen_id(GEN_PEDIDOS_ID, 1)from rdb$database';
 BuscoPedido.Active:=TRUE;
 pedidoactual:=BuscoPedido.Fields[0].AsInteger;
 BuscoPedido.Active:=FALSE;

 nuevopedido.Active:=FALSE;
 BuscoPedido.SQL.Text:='insert into PEDIDOS (numero) values(:aux)';
 BuscoPedido.ParamByName('aux').AsInteger:=Pedidoactual;
 BuscoPedido.ExecSQL;
 

 

esto debe funcionar con tu id autoincremental.


  • 0

#12 Agustin Ortu

Agustin Ortu

    Advanced Member

  • Moderadores
  • PipPipPip
  • 831 posts
  • LocationArgentina

Posted 29 September 2015 - 02:31 PM

Yo sigo pensando que lo mejor es que el motor se encarge de gestionar el autoincremental
 
El autoincremental se llama asi porque se incrementa "solo"

El "problema" en este caso es como obtener ese valor cuando se realiza una insercion, no el suministrar el "ultimo valor"

Sigo pensando que la mejor solucion es pedir el id que se genero luego de la insercion. No solamente es asegurarse que es el correcto sino que ademas es un solo acceso a la BD lo que es mucho mas rapido, ademas el codigo es mucho mas sencillo

Las inserciones que hago yo suelen ser de esta forma
 


delphi
  1. function GetQuery: TXXXQuery;
  2. begin
  3. Result := { retorna un componente query que es una variable privada de cierta clase y que
  4.   viene con una sentencia SQL para insertar y con prepared := True }
  5. end;
  6.  
  7. function Insert(const Obj: TMyClass): Boolean;
  8. var
  9. q: TQuery;
  10. begin
  11. q := GetQuery;
  12. try
  13. { setear los parametros para el insert }
  14. q.Execute;
  15. Obj.Id := q.FieldByName('Id');
  16. Result := True;
  17. except
  18. Result := False;
  19. raise;
  20. end;
  21. end; 

  
Ahora tambien hay que considerar mucho lo que comenta Marcelo, sobre todo la parte de "cual es el verdadero proposito de obtener este id?", "para que sirve?"


  • 0

#13 Marc

Marc

    Advanced Member

  • Moderadores
  • PipPipPip
  • 1484 posts
  • LocationMallorca

Posted 18 October 2015 - 01:11 PM

Lo de utilizar una tabla separada de contadores, como sugiere Cram, me parece excesivo.

 

Además puede ser fácilmente problemático. ¿ Qué ocurre cuando dos personas insertan un registro al mismo tiempo ?. Los dos leen el mismo último valor en la tabla de contadores, lo incrementan y lo intentan asignar a los nuevos registros. El programa te va a fallar porque se intentan insertar dos registros con la misma clave primaria.

 

Personalmente defiendo la solución de usar autoincrementales con Generadores (sequences) que propone Delphius. Es muy simple de utilizar y nunca me ha dado el menor problema y asegura que los nuevos registros tendrán claves superiores a los anteriores registros.

 

Como bien dice Agustín, es mejor dejar que sea el motor mismo quien genere los autoincrementos (con generadores), y una vez ejecutada la inserción nuestra aplicación Delphi ya puede recuperar perfectamente ese valor (gracias a la cláusula RETURNING de la sentencia INSERT INTO).


  • 1

#14 Sergio

Sergio

    Advanced Member

  • Moderadores
  • PipPipPip
  • 1092 posts
  • LocationMurcia, España

Posted 23 October 2015 - 08:54 AM

Hola chicos, hace siglos que no me conecto, pero aqui vuelvo!

 

Nuestra app principal es "muy multiusuario" y hacemos inserciones en masa algunas veces, y puede que curra desde dos pc simultaneamente, y ese problema lo tenemos 100% solucionado (bueno, digamos que 99%), os explico como:

 

1.- Necesitas un generador como dicen por arriba, una tabla adicional NO vale, porque al estar aislada en una transaccion no ve las otras simultaneas y puedes terminar con 2 id identicos, y usar el max(id+1) es lento y no te quita el problema de dos inserciones simultaneas chocando (te tocaria usar semaforos, pero ni eso te libra). Resumiendo: El generador no te lo quita nadie si quieres que funcione siempre.

 

2.- En lugar de pedirle numero al generador y usarlo en la insercion, que es valido pero trabajoso y tienes que ponerlo en tu codigo para caaadaaa insercion, enviamos la grabacion SIN poner nada en le id, es decir, llega con un null o con un cero.

 

3.- La tabla tiene un trigger before insert que, si el id esta vacio, pide al generador un id y se lo pone. Esto hace que si añades registros externamente, con dejar el id vacio todos entraran y no te cuasara problemas. Esto es muy importante si teneis opcion de importar datos desde otras apps que no sean vuestras.

 

4.- La insercion se llama con "returning id" si necesitas saber el id que te ha tocado, en nuestro caso eso lo usamos poco, pero por ejemplo, si tras grabar la factura tienes que añadir lineas de factura, usamos el returning siempre.

 

Los posibles problemas son muy pocos, pero hay dos o tres de los que realmente no suele hacer falta preocuparse:

 

1.- Si la grabacion falla por alguna restriccion, el id se ha consumido. Es decir, NUNCA asumas que el id 100 quiere decir que llevas 100 registros creados, ni que no van a haber saltos, es un id y no significa nada, solo se le pide ser unico, y eso lo cumple siempre.

 

2.- El generador se crea con un valor de cero, de forma que te dara un 1 al primer uso. Si tu tabla tiene valores previos, el generador se tiene que inicializar con el max(id) de tus datos actuales, de otra forma la grabacion fallaria por id duplicado (momento en el que puedes capturar el error y saber que tienes que actualizar tu generador si o si).

 

3.- Si dejas que ese id sea editable (en algunas fichas lo permitimos cuando ese id es "oficial") el usuario tendra la opcion de dejarlo en blanco (con lo que se usa el generador) o opner el algo, para rellenar huecos etc. En ese caso, el generador no avanza, con lo que o bien te arriesgas a que la siguiente grabacion falle por id duplicado, o bien en le trigger de insert, si te llega un id, comprebas que no supere al valor del generador y lo aumentas si hace falta.

 

Esto ultimo la verdad no deberia suceder, el id no debe ser editable, pon un id "interno" y otro "externo" editable, con sus dos generadores o lo que necesites, pero el "interno" mejor no visible, asi puedes luego permitir cambiar el id "visible" sin afectar a las conexiones entre tablas. Nosotros no seguimos siempre este consejo... pero bueno.


  • 4

#15 egostar

egostar

    missing my father, I love my mother.

  • Administrador
  • 14460 posts
  • LocationMéxico

Posted 23 October 2015 - 09:25 AM

Off-Topic

Un gusto verte por aquí amigo Sergio. (y)

saludos
  • 0

#16 cram

cram

    Advanced Member

  • Miembro Platino
  • PipPipPip
  • 832 posts
  • LocationMisiones, Argentina

Posted 23 October 2015 - 08:47 PM

Off-Topic

Un gusto verte por aquí amigo Sergio. (y)

saludos

 

Lo mismo digo, ya extrañaba esa mezcla de inteligencia y experiencia (lo digo en serio).

 

Saludos

(b)


  • 0

#17 AngelF

AngelF

    Advanced Member

  • Miembros
  • PipPipPip
  • 100 posts
  • LocationValencia - España

Posted 10 December 2015 - 10:09 AM


 

4.- La insercion se llama con "returning id" si necesitas saber el id que te ha tocado, en nuestro caso eso lo usamos poco, pero por ejemplo, si tras grabar la factura tienes que añadir lineas de factura, usamos el returning siempre.

 

Los posibles problemas son muy pocos, pero hay dos o tres de los que realmente no suele hacer falta preocuparse:

 

 

Hola, mucho tiempo sin pasar por aquí, liado con otras cosas.

 

Me quedo con lo que comentáis de usar el "returning id" ya que necesito hacer cosas con el idregistro recién creado.

 

Interesante debate, muchas gracias a todos.


  • 1

#18 egostar

egostar

    missing my father, I love my mother.

  • Administrador
  • 14460 posts
  • LocationMéxico

Posted 10 December 2015 - 10:27 AM

Hola, mucho tiempo sin pasar por aquí, liado con otras cosas.

 

Me quedo con lo que comentáis de usar el "returning id" ya que necesito hacer cosas con el idregistro recién creado.

 

Interesante debate, muchas gracias a todos.

 

[OFF-TOPIC]

 

 

Un gusto verte de nuevo amigo AngelF.

 

Saludos


  • 0





Also tagged with one or more of these keywords: firebird, ultimo

IP.Board spam blocked by CleanTalk.