Foros Club Delphi

Foros Club Delphi (https://www.clubdelphi.com/foros/index.php)
-   Conexión con bases de datos (https://www.clubdelphi.com/foros/forumdisplay.php?f=2)
-   -   como obtiene el ultimo id de una tabla (https://www.clubdelphi.com/foros/showthread.php?t=88280)

giulichajari 14-05-2015 00:37:01

como obtiene el ultimo id de una tabla
 
Ya se que existe el metodo last_insert_id() en mysql pero hay casos en que si falla una transaccion el valor no se restaura.
Por otro lado usa max(idticket) es erroneo porque se puede pisar el valor.
En mi caso tengo la tabla ticket ocn su cabecera y el detalle del ticket en otra tabla venta. Entonces necesito el ultimo id de ticket para la tabla venta que es varios a varios(en venta se guarda el idticket idproducto precio y cantidad).
Me han recomendado tener en el servidor de aplicaciones (ya que estoy trabajando con datasnap) un metodo con un query asociado a una tabla con un solo campo, que sume 1 a este entero y luego seleccione el valor, pero el tema es que tengo que tener un metodo como este para cada tabla.

Es decir la pregunta es como se hace para obtener el ultimo id de ticket a un habiendo transacciones simultaneas (es decir varios users insertando tickets a la vez). Se deberia bloquear el siguiente registro?

Casimiro Notevi 14-05-2015 09:11:37

Si no he entendido mal, puedes valerte de un bucle al grabar.
En plan muy "rústico":
Código Delphi [-]
boolean ok = true
while ok
  ultimo = select max(id) from tabla
  try
    insert into otratabla values (ultimo, ... )
    ok = false
  except
  end
end

giulichajari 14-05-2015 18:30:36

Es que de hecho hice una funcion que obtiene el ultimo id y le suma 1 antes de insertar en la tabla maestro...
Tambien estuve leyendo que FireBird no utiliza campos autoincrementales justamente por este motivo.. en vez de esto es un campo integer pero el valor lo genera el servidor de aplicaciones o el programa, no el motor de base de datos.

Que te parece tener una tabla con un solo campo:

ULTIMO(ultimoidticket)

entonces cada transaccion suma 1 y se queda con el valor.. de esta forma no se pisan los valores, y no hay que esperar a que una transaccion finalize para empezar la otra.

Casimiro Notevi 14-05-2015 18:47:17

No entiendo, ¿quiéres un incremental, aunque se salten números? ¿o quieres un número consecutivo y que no se salte ninguno?

giulichajari 14-05-2015 18:57:21

Cita:

Empezado por Casimiro Notevi (Mensaje 492160)
No entiendo, ¿quiéres un incremental, aunque se salten números? ¿o quieres uno número consecutivo y que no se salte ninguno?

No entiendo porque la pregunta..
Es que si es un entero de 32 bits tiene millones de posiciones... osea con lo anterior se soluciona el problema de conecurrencia, si hay 2 o mas pc s haciendo ticket.. la verdad no interesa si se saltea un numero..
De hecho si una transaccion falla sobre un autoincremental y el ultimo idticket era 17, el 18 no se ocupa nunca mas.. si la proxima transaccion tiene exito sera el 19...
La funcion last_insert_id() retornara 18 para referenciar el ticket en la tabla detalleticket por lo tanto no sirve...
Y la funcion max(idticket) si falla el 18 la proxima vez sera 18, pero no sirve porque si 2 pc generan esa consulta.. ambos detalles de ticket corresponderan al mismo ticket.. porque devolvera 18 en ambos casos...

Casimiro Notevi 14-05-2015 19:02:09

Cita:

Empezado por giulichajari (Mensaje 492162)
No entiendo porque la pregunta...

Porque no se entiende qué quieres exactamente.
Por lo que dices, lo que buscas en un id "normal y corriente", para eso están los generadores autoincrementales, nunca jamás se repiten. Lo que comentas de bloqueos y repetirse, etc. no ocurre con ellos.

giulichajari 14-05-2015 19:17:59

Bueno es que a mi la funcion last_insert_id() me retorna 1.. no se porque.. y la vez pasada insertaba mal el idticket luego de que una transaccion hizo rollback..

Casimiro Notevi 14-05-2015 19:20:50

Si no pones código o explicas en detalle cómo lo haces, solo podemos adivinar :)

giulichajari 14-05-2015 19:24:09

Código Delphi [-]
procedure TServerMethods1.nuevoticket(numero,ids,idcliente:integer;importe,efectivo,vuelto:Double;fechae,horae  ,tipo:string);
var
tr:TDBXTransaction;
begin
       if (SUCURSAL.InTransaction) then
     raise Exception.Create('Hay una transacción pendiente');
     SUCURSAL.Open;
       try
          try
            tr:=SUCURSAL.BeginTransaction();

               begin
                with qticket do
                  begin
                    Close;


                    ParamByName('numero').AsInteger:=numero;
                    ParamByName('importe').AsFloat:=importe;
                    ParamByName('fechae').AsString:=fechae;
                    ParamByName('horae').AsString:=horae;
                    ParamByName('idsucursal').AsInteger:=ids;

                    ParamByName('idcliente').AsInteger:=idcliente;
                    ParamByName('tipo').AsString:=tipo;
                    ParamByName('efectivo').AsFloat:=efectivo;
                    ParamByName('vuelto').AsFloat:=vuelto;
                    ExecSQL();
                  end;

         SUCURSAL.CommitFreeAndNil(tr);
               end;

      except
        SUCURSAL.RollbackFreeAndNil(tr);
        end;
      finally
      SUCURSAL.Close;
       end;
end;

procedure TServerMethods1.nuevodetalleticket(idp:integer;cantidad,preciou:Double);
  var
  tr:TDBXTransaction;
  begin
      if (SUCURSAL.InTransaction) then
     raise Exception.Create('Hay una transacción pendiente');
     SUCURSAL.Open;
       try
          try
            tr:=SUCURSAL.BeginTransaction();
               quticket.Close;
               begin
                with qventa do
                  begin
                    Close;


                 //    ShowMessage(IntToStr(quticket.ExecSQL()));
                    ParamByName('idt').AsInteger:=quticket.ExecSQL();
                    ParamByName('idp').AsInteger:=idp;
                    ParamByName('cantidad').AsFloat:=cantidad;
                    ParamByName('preciou').AsFloat:=preciou;
                    ExecSQL();
                  end;

         SUCURSAL.CommitFreeAndNil(tr);
               end;

      except
        SUCURSAL.RollbackFreeAndNil(tr);
        end;
      finally
      SUCURSAL.Close;
       end;
end;

eso es en DataSnap

y dentro de quticket tengo last_insert_id(), como veras hice un showmessage... y veo un 1..

Casimiro Notevi 14-05-2015 19:47:25

Se supone que está correcto, ¿no?

http://mysql.conclase.net/curso/?sqlfun=LAST_INSERT_ID

Cita:

El último ID que fue generado se mantiene en el servidor en una base por conexión. Esto significa que el valor que devuelve la función para un cliente dado es valor AUTO_INCREMENT más reciente generado por ese cliente. El valor no puede verse afectado por otros clientes, aunque generen valores AUTO_INCREMENT por si mismos. Este comportamiento asegura que se puede recuperar un ID sin preocuparse por la actividad de otros clientes, y sin necesidad de bloqueos o transacciones. El valor de LAST_INSERT_ID() no cambia si se actualiza una columna AUTO_INCREMENT de una fila con un valor no mágico (es decir, un valor que no es NULL ni 0). Si se insertan muchas filas al mismo tiempo con una sentencia , LAST_INSERT_ID() devuelve el valor para la primera fila insertada. El motivo para esto es hacer posible reproducir más fácilmente la misma sentencia de nuevo en algún otro servidor. Si se da un argumento expr a LAST_INSERT_ID(), el valor del argumento será devuelto por la función, y se asigna como siguiente valor a retornar por LAST_INSERT_ID(). Esto se puede usar para simular secuencias:
Primero crear la tabla:
mysql> CREATE TABLE sequence (id INT NOT NULL); mysql> INSERT INTO sequence VALUES (0); Después la tabla se puede usar para generar secuencias de números como esta:
mysql> UPDATE sequence SET id=LAST_INSERT_ID(id+1); Se pueden generar secuencias sin llamar a LAST_INSERT_ID(), pero al utilidad de usar la función de este modo es que el valor ID se mantiene en el servidor como el último valor generado automáticamente (seguro en multiusuario). Se puede recuperar el nuevo ID como si se recuperase cualquier valor AUTO_INCREMENT normal en MySQL. Por ejemplo, LAST_INSERT_ID() (sin argumentos) devolverá el nuevo ID. La función del API C mysql_insert_id también se puede usar para obtener el valor. mysql_insert_id sólo se actualiza después de sentencias y , de modo que no se puede usar la función del API C para recuperar el valor para LAST_INSERT_ID(expr) después de ejecutar otra sentencia SQL como o .



giulichajari 14-05-2015 21:42:58

Cita:

Empezado por Casimiro Notevi (Mensaje 492170)
Se supone que está correcto, ¿no?

Claro me queda descubrir porque me da 1 el server, probe hacer 2 ticketts y devuelve 1.

Se pueden generar secuencias sin llamar a LAST_INSERT_ID(), pero al utilidad de usar la función de este modo es que el valor ID se mantiene en el servidor como el último valor generado automáticamente (seguro en multiusuario) aparte si tengo 2 sucursales son 2 conexiones distintas entonces no va a tomar el id insertado por otro: hay 17 y una conexion inserta 1 sera 18 pero la otra conexion no lo ve.

Pero yendo al grano: ¿cual seria la solucion correcta entonces(a ver si entiendo)? en lo que negrie arriba dice seguro en multiusuario: osea que es correcta la solucion que te plantie de tener los ids en una tabla aparte.

Perdona mi ignorancia..

Al González 14-05-2015 23:08:25

Las ventajas de las secuencias (llamadas generadores en Firebird) es que no son transaccionales y es imposible que dos peticiones "Next Value" obtengan el mismo valor. Te recomiendo que tu aplicación cliente, justo al comenzar la captura de un registro, pida a una secuencia/generador de la base de datos su siguiente valor y lo asignes en ese momento desde la propia aplicación al nuevo registro en memoria. Eso te permitirá un mejor control de relaciones entre registros.

Incluso si el registro no termina siendo guardado, no hay problema, las llaves primarias (campos ID) no tienen por qué ser números consecutivos; para eso son otros campos: NUMERO/CLAVE/CODIGO, para los cuales sí puedes hacer Select Max y tener un índice con restricción unique para impedir que se asigne el mismo valor a una lista de datos que deben ir numerados de forma consecutiva.

Saludos.

giulichajari 15-05-2015 11:55:06

Cita:

Empezado por Al González (Mensaje 492177)
Las ventajas de las secuencias (llamadas generadores en Firebird) es que no son transaccionales y es imposible que dos peticiones "Next Value" obtengan el mismo valor. Te recomiendo que tu aplicación cliente, justo al comenzar la captura de un registro, pida a una secuencia/generador de la base de datos su siguiente valor y lo asignes en ese momento desde la propia aplicación al nuevo registro en memoria. Eso te permitirá un mejor control de relaciones entre registros.

Incluso si el registro no termina siendo guardado, no hay problema, las llaves primarias (campos ID) no tienen por qué ser números consecutivos; para eso son otros campos: NUMERO/CLAVE/CODIGO, para los cuales sí puedes hacer Select Max y tener un índice con restricción unique para impedir que se asigne el mismo valor a una lista de datos que deben ir numerados de forma consecutiva.

Saludos.

Muchas gracias Al, la verdad uno aprende mucho en la practica.. queriendo hacer algo a lo grande como es el sistema de cliente servidor..jeje en mi ciudad todavia usan disco compartido..

Casimiro Notevi 15-05-2015 12:13:19

¿Lo has solucionado?

giulichajari 15-05-2015 12:14:55

Cita:

Empezado por Casimiro Notevi (Mensaje 492186)
¿Lo has solucionado?

Estoy en eso... voy a implementar un id no consecutivo osea no autoincremental... de seguro funcionara.. Gracias

giulichajari 16-05-2015 02:59:34

hice lo siguiente y funciono:
Código Delphi [-]
function TServerMethods1.nuevoid:integer;
begin

  with quticket do
    begin
      SQL.Clear;
      SQL.Add('update identificadores set ticket=ticket+1');
      ExecSQL();
      SQL.Clear;
      SQL.Add('select ticket from identificadores');
      Open;
      result:=FieldByName('ticket').AsInteger;
    end;

end;

nunca falla..


La franja horaria es GMT +2. Ahora son las 09:23:34.

Powered by vBulletin® Version 3.6.8
Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
Traducción al castellano por el equipo de moderadores del Club Delphi