Foros Club Delphi

Foros Club Delphi (https://www.clubdelphi.com/foros/index.php)
-   Firebird e Interbase (https://www.clubdelphi.com/foros/forumdisplay.php?f=19)
-   -   Trigger no funciona (https://www.clubdelphi.com/foros/showthread.php?t=84952)

bulc 03-01-2014 22:07:57

Trigger no funciona
 
Saludos,
Cuando uso Firebird por comandos sí me funciona el trigger, pero cuando lo uso con Delphi en el DBGrid me da un error de campo no lleno y tengo que escribir el numero de incremento a mano.
¿Alguien sabe por qué ocurre esto? Si hacen falta más explicaciones me lo decis.

Casimiro Notevi 03-01-2014 22:10:51

Cita:

Empezado por bulc (Mensaje 471285)
Si hacen falta más explicaciones me lo decis.

Hacen falta más explicaciones.

Caminante 03-01-2014 22:16:21

Hola

Bueno adivinando la situacion creo que lo que pasa es que mediante un trigger asignas la clave primaria a la tabla. El campo del lado de la aplicacion tiene la propiedad required en true. Para evitar ese error cambialo a false. Pero debes hacer un refresh despues de grabar para poder leer la clave que se asigno.

Espero que sea lo que necesitabas.

Saludos

bulc 04-01-2014 13:01:53

Cita:

Empezado por bulc (Mensaje 471285)
Saludos,
Cuando uso Firebird por comandos sí me funciona el trigger, pero cuando lo uso con Delphi en el DBGrid me da un error de campo no lleno y tengo que escribir el numero de incremento a mano.
¿Alguien sabe por qué ocurre esto? Si hacen falta más exp"licaciones me lo decis.

Pues que usando comandos en modo texto en el prompt "SQL>" el trigger actúa correctamente disparando el generador para incrementar un campo de clave primaria en +uno. En cambio al hacerlo en un TDBGrid con Delphi, dejo ese campo vacío y al clicar en Guardar, Firebird da un error de "no hay dato en el campo Num. ¿Se entiende mejor ahora?

Casimiro Notevi 04-01-2014 13:46:06

Pero qué sentencia sql usas en cada caso y qué parámetros, etc.

bulc 04-01-2014 13:58:40

Pues no uso ni uno ni otro. Estoy en un DBGrid en el que introduzco nuevos datos. Y luego le doy a guardar del DBNavigator.

bulc 04-01-2014 14:01:51

Hola Caminante. He ido al Form y no he encontrado la propiedad Required ni en el DBGrid ni en sus columnas. Tal vez deberías decirme dónde está o si voy descaminado. Por otro lado, basado en tu idea, hallé en el componente TIBQuery una propiedad GenaratorField que usé colocando el nombre del GENERATOR. También contiene una prop. llamada ForceRefresh. La puse a True...
Pero al meter un nuevo dato en el DBGrid sigue saliendo el error: "Field 'num' must have a value".
¿Alguna otra sugerencia?
Gracias por el esfuerzo.
Bulc

jafera 04-01-2014 14:35:54

Hola Bulc.

Mi sugerencia es tal y como te indica Casimiro es que nos des más datos de dónde se produce el problema y que lo puede proiducir.
En tu explicación no se acaba de entender bien lo que te sucede, tu intentas explicarte al máximo pero tienes la ventaja de que ves en cada momento lo que sucede, nosotros no.
Comentas algo de un dbgrid, recuerda que un dbgrid no es más que la representación ordenada de los datos de una tabla o consultao sea que si realizas cualquier acción en este grid, realmente estas trabajando en los datasets, post, append, cancel, update, etc.

Pon algo de código, (si es que este no es un secreto de estado), bueno bromas aparte intenta colocarnos la sentencia que te da el error y seguro que recibirás la respuesta adecuada.

Saludos

Josep

Caminante 04-01-2014 15:31:36

Cita:

Empezado por bulc (Mensaje 471309)
no he encontrado la propiedad Required ni en el DBGrid ni en sus columnas.
Bulc

La propiedad required pertenece a los campos persistentes del dataset que uses.

Saludos

bulc 04-01-2014 16:33:09

Cita:

Empezado por jafera (Mensaje 471311)
Hola Bulc.

Mi sugerencia es tal y como te indica Casimiro es que nos des más datos de dónde se produce el problema y que lo puede proiducir.
En tu explicación no se acaba de entender bien lo que te sucede, tu intentas explicarte al máximo pero tienes la ventaja de que ves en cada momento lo que sucede, nosotros no.
Comentas algo de un dbgrid, recuerda que un dbgrid no es más que la representación ordenada de los datos de una tabla o consultao sea que si realizas cualquier acción en este grid, realmente estas trabajando en los datasets, post, append, cancel, update, etc.

Pon algo de código, (si es que este no es un secreto de estado), bueno bromas aparte intenta colocarnos la sentencia que te da el error y seguro que recibirás la respuesta adecuada.

Saludos

Josep


!Es que no hay código apenas! Sólo hemos hecho la conexión. Usamos IBDataBase, IBTransaction, IBQuery, IBProvider, ClientDataSet, TDataSource y DBGrid. La conexión es correcta.
Hemos creado la DBase por código en el prompt de la aplicación en modo de comandos ISQL de Firebird (Antiguo DOS command). La Base tiene una tabla.
En ella hay cuatro campos el primero NUM es numérico; es clave primaria, not null. Luego hemos creado un generator y un trigger para que se autoincremente el camp NUM. Hemos hecho un insert y funciona el Trigger perfectamente. Sólo hay una tabla con un registro y este lleva un 1 en NUM.
El TDBGrid tiene una barra TDBNavigator. Pues bien, escribimos un dato nuevo para el registro nº 2 (el primero se ve en el Grid) del DBGrid. Al guardar el dato con el TDBNavigator, surge el error de "No hay valor para columna NUM". Entonces colocamos un botón con el código necesario para que el ClientDataSet guarde los datos. Usamos Post y ApplyUpdates, lo normal.
Pues bien, si se coloca un 2 en el campo NUM del DBGrid los datos se guardan sin problema, pero si se deja ese campo en blanco (a la espera de que el Trigger) haga su trabajo, entonces salta el error de nuevo. O sea, hay un error si se deja NUM en blanco.
Bueno, lo he intentado. Puedo poner el código pero repito solo tiene un botón de Guardar y un DBNavigator asociado al DBGrid.
El código del generator y del trigger funcionan correctamente.
Dime si hay algo que pueda ponerte más y te lo pongo.
Gracias.
Bulc

mRoman 04-01-2014 16:50:53

Hola....creo que hace falta definir a "arAutoInc" en la propiedad AutoGenerateValue del DataSet que tengas asociado a tu DbGrid.....esta propiedad las puedes observar si seleccionas el componente DataSet y le das click derecho para q te muestre un menú contextual y puedas seleccionar la opción "Fields Editor". Cuando te muestre la pantalla das nuevamente click derecho y seleccionas "Add all fields" cargara todos los campos del dataset y es ahi donde al seleccionar un campo encontraras la propiedad q te comento.....

Espero te funcione...SALUDOS.

fjcg02 04-01-2014 22:38:09

Hola, que yo sepa, eso que te ocurre es porque no pones ningún valor en la pk. Para solventarlo, haz una función que te devuelva un nuevo valor del generador. En el evento beforpost del dataset le das valor a la pk con esa función y asunto solucionado.

Por eso cuando pones valor a mano, te funciona. Es una pequeña pega, pero es lo que hay.

Nos cuentas el resultado.

Saludos

bulc 05-01-2014 12:44:56

Cita:

Empezado por fjcg02 (Mensaje 471322)
Hola, que yo sepa, eso que te ocurre es porque no pones ningún valor en la pk. Para solventarlo, haz una función que te devuelva un nuevo valor del generador. En el evento beforpost del dataset le das valor a la pk con esa función y asunto solucionado.

Por eso cuando pones valor a mano, te funciona. Es una pequeña pega, pero es lo que hay.

Nos cuentas el resultado.

Saludos

Hola. ¿Qué es la pk? No lo pillo. Gracias...

Casimiro Notevi 05-01-2014 12:47:57

Cita:

Empezado por bulc (Mensaje 471336)
Hola. ¿Qué es la pk? No lo pillo. Gracias...

primary key

bulc 05-01-2014 13:02:20

Bueno, está bien. Pero en esa función, ¿como saco el valor del Generador? Hay algo que se me escapa... Y me quedo corto.
Lamento darte tantas idas y vueltas. Uno se queda con cara de tonto y no sabe si volver a preguntar. Al menos que por eso no quede.
Gracias.

bulc 05-01-2014 13:05:34

Cita:

Empezado por mRoman (Mensaje 471317)
Hola....creo que hace falta definir a "arAutoInc" en la propiedad AutoGenerateValue del DataSet que tengas asociado a tu DbGrid.....esta propiedad las puedes observar si seleccionas el componente DataSet y le das click derecho para q te muestre un menú contextual y puedas seleccionar la opción "Fields Editor". Cuando te muestre la pantalla das nuevamente click derecho y seleccionas "Add all fields" cargara todos los campos del dataset y es ahi donde al seleccionar un campo encontraras la propiedad q te comento.....

Espero te funcione...SALUDOS.

Creo que eso he hecho. Usé el Query, como te decía. Buscaré esa propiedad AutoGenerateValue. Supongo que estará en el IBQuery o en el ClienteDataSet...

bulc 05-01-2014 13:48:32

He hallado esa prop. en el ClientDataSet (CDS). Cargué los campos y elegí NUM. Asigné a la prop. AutoGenerateValue :=arAutoInc y la prop. Required to False. Pero sigue sin funcionar. Mi pregunta es, ¿el CDS efectúa autoincrementos por su cuenta, si usar ningún Trigger?
es que además el comp. IBQuery (que uso) también activa Generators mediante la prop. GeneratorField, etc.
¿Debo efectuar el Refresh antes del Post, mediante código?
Saludos y gracias.
Bulc

fjcg02 05-01-2014 14:59:27

Hola,
Respecto a la función, básicamente es la ejecución de una query que te devuelva el valor de esta select

SELECT GEN_ID( generador,1) FROM RDB$DATABASE

Pones un parámetro que sea el nombre del generador, y te vale para todos.

Como ya te decía anteriormente, en el caso de los tclientdatasets hay que hacerlo así. En el evento beforepost asignas el valor al campo pk ( num en tu caso ) y solucionado el problema.

Saludos
Pd: no estoy escribiendo desde el portátil, o sea, que no tengo el código para pegarlo. A ver sí me acuerdo y te lo paso.

fjcg02 05-01-2014 23:26:38

Hola,
Esta es la función que te vale para cualquier generador

Código Delphi [-]
function Generador( generador: string): integer;
begin
SQLGenerador.CLose;
SQLGenerador.SQL.Clear;
SQLGenerador.SQL.Add('select  first 1 GEN_ID('+generador+',1)  from RDB$DATABASE');
SQLGEnerador.Open ;
Result:= SQLGenerador.Fields[0].AsInteger;
end;

La llamo de la siguiente manera

Código Delphi [-]
procedure TDM.CDSClienteBeforePost(DataSet: TDataSet);
begin
// si es nulo, es registro nuevo, informo pk con generador, usuario y timestamp del alta
if Dataset.FieldByName('idCliente').IsNull then
  begin
    Dataset.FieldByName('idcliente').AsInteger:= Generador('CLIENTE');
    Dataset.FieldByName('USUARIOALTA').AsString:= WUsuario;
    Dataset.FieldByName('LOGA').AsDateTime:= now;
  end;
ActualizarLog(Dataset); // informo usuario y timestamp de actualizacion
end;

function TDM.ActualizarLog(Dataset: TDataset): boolean;
begin
  Dataset.FieldByName('USUARIOMODIF').AsString:= WUsuario;
  Dataset.FieldByName('LOGM').AsDatetime:= now;
  Result:= True;
end;

Todas las tablas tienen los campos USUARIOALTA, LOGA, USUARIOMODIF y LOGM para saber quién ha dado de alta el registro y quién es el último que lo ha modificado

Espero que te sirva de ayuda.

Un saludo

bulc 06-01-2014 15:40:22

Cita:

Empezado por fjcg02 (Mensaje 471345)
Hola,
Esta es la función que te vale para cualquier generador

Código Delphi [-]
function Generador( generador: string): integer;
begin
SQLGenerador.CLose;
SQLGenerador.SQL.Clear;
SQLGenerador.SQL.Add('select  first 1 GEN_ID('+generador+',1)  from RDB$DATABASE');
SQLGEnerador.Open ;
Result:= SQLGenerador.Fields[0].AsInteger;
end;

La llamo de la siguiente manera

Código Delphi [-]
procedure TDM.CDSClienteBeforePost(DataSet: TDataSet);
begin
// si es nulo, es registro nuevo, informo pk con generador, usuario y timestamp del alta
if Dataset.FieldByName('idCliente').IsNull then
  begin
    Dataset.FieldByName('idcliente').AsInteger:= Generador('CLIENTE');
    Dataset.FieldByName('USUARIOALTA').AsString:= WUsuario;
    Dataset.FieldByName('LOGA').AsDateTime:= now;
  end;
ActualizarLog(Dataset); // informo usuario y timestamp de actualizacion
end;

function TDM.ActualizarLog(Dataset: TDataset): boolean;
begin
  Dataset.FieldByName('USUARIOMODIF').AsString:= WUsuario;
  Dataset.FieldByName('LOGM').AsDatetime:= now;
  Result:= True;
end;

Todas las tablas tienen los campos USUARIOALTA, LOGA, USUARIOMODIF y LOGM para saber quién ha dado de alta el registro y quién es el último que lo ha modificado

Espero que te sirva de ayuda.

Un saludo

Parece muy claro. Lo probaré en cuanto pueda. Muchas gracias.

bulc 11-01-2014 19:28:13

Estoy en ello. fjcg02, te diré el resultado.

fjcg02 12-01-2014 00:50:03

Aver, a ver...

compruebo que no soy el único friki trabajando :D

Saludos

bulc 12-01-2014 15:02:26

Bueno, no estás sólo.
He aquí lo que he hecho. Activé la llamada a tu función de Usuario GENERADOR, en el evento onKeyDown, Key = VK_INSERT, y la impido para VK_DOWN and CDS1.EOF. A la función GENERADOR le paso el parámetro IBQuery1 y como devolución el GEN_ID (sin modificarlo), tal como proponías. No he entendido la línea NumGet:= Query1.Fields[0].AsInteger. Yo usaba esta: NUmGet := Query1.Params[0].AsInteger; pero me daba "Arguement out of Range". ¿Por qué ocurre ésto?.
En fin que luego coloco ese valor en el campo NUM del DBGrid y luego lo quito con BeforePost para que el Trigger haga su trabajo. Ha sido un placer aprender de ti. Espero que te resulte grato. Saludos. Bulc.
Espero que el moderador no me suelte el perro por no usar formáto de código estándar y que se entienda.

bulc 12-01-2014 15:17:23

Código Delphi [-]
Function GET_GEN ( IBQuery1 : TIBQuery ) : Integer;  // You can add more parameters like: GEN_NAME : String, and TDBGrid, etc...
// Called by KeyDown event using Key = VK_INSERT  
Begin
    IBQuery1.Close;
   IBQuery1.Clear;
   IBQuery.SQL.Add( 'Select GEN_ID( 'GEN_NUM_TALLER', 0) FROM RDB$DATABASE' ); //Extract without alteration
   IBQuery.Open;
   Result := IBQuery1.Fields[0].AsInteger; // ¿Se podría extraer el dato de algún otro modo?
End;

bulc 12-01-2014 18:40:16

Funciona a la pefección
 
Código Delphi [-]
 // Código que he usado. Llamado por el evento OnKeyPress al detectar la tecla VK_INSERT
 FUNCTION GENERADOR( IBQuery1: TDBQuery; Gen_Name: String) : Integer;
 Begin
     IBQuery1.Close;
     IBQuery1.Clear;
     IBQuery1.Add('Select GEN_ID( Gen_Name,0) FROM RDB$DATABASE');
     IBQuery1.Open;
     Result: = IBQuery1.Fields[0].AsInteger;
     // Usaba Result:= IBQuery1.Params[0].AsInteger; pero daba: "Arguement Out of Range" Error. ¿Sabes por qué?
    // ShowMessage(' GEN_ID =  ' + IntToStr (Result ) );
 End;
Gracias por tu ayuda. Bulc

bulc 12-01-2014 18:43:10

//Llamada a la función Generador (con Posterioridad a la función)
// en el evento OKeyDown o en BeforePost...
GenNUm:= GENERADOR( IBQuery1, 'GEN_NUM_TALLER');

Casimiro Notevi 12-01-2014 19:53:29

Código Delphi [-]
     Result: = IBQuery1.Fields[0].AsInteger;      // Usaba Result:= IBQuery1.Params[0].AsInteger; pero daba: "Arguement Out of Range" Error. ¿Sabes por qué?
Porque Params[] es para pasarle parámetros antes de ejecutar y Fields[] es para recoger valores después de ejecutar

bulc 12-01-2014 23:05:10

Agradecido
 
Agradecido por tu información. Gracias.

fjcg02 13-01-2014 12:28:02

Cita:

Empezado por bulc (Mensaje 471588)
//Llamada a la función Generador (con Posterioridad a la función)
// en el evento OKeyDown o en BeforePost...
GenNUm:= GENERADOR( IBQuery1, 'GEN_NUM_TALLER');

En principio la llamada sólo es necesaria en el evento BeforePost y cuando el campo que alimenta el generador es nulo. Es decir, cuando aceptas el cambio en un registro, si lo estás editando, ya tiene un valor, el que haya traído de la tabla al leer el registro. Cuando haces un post y el campo que alimenta el generador es nulo, entonces es un registro nuevo, por loq ue debes disparar el generador y darle el valos al campo que hece de primary key.

Un saludo

bulc 16-01-2014 18:33:19

¿Cómo asignar a NULL un campo? o algo así....
 
Me funciona el GENERATOR y el trigger asociado a él. Pero como el campo NUM Integer lleva la clave primaria, resulta que si se deja vacío da error al salir de la edición del DBGrid.
Ejecuto el generador (sin modificarlo) con:
SELECT GEN_ID(GEN_NAME_TABLE, 0) FROM RDB$DATABASE;
//Recojo el dato
//Luego lo asigno al campo NUM del DBGrid. Para ello uso el DBDataSource con la línea:
DataSourcex.DataSet.FieldByName('NUM').AsIngeger := Value_Generator;

Lo compruebo en el evento BeforePost. Y es ahí cuando salta el error motivado por la asignación a NULL del campo.
DataSourcex.DAtaSet.FieldByName('NUM').AsInteger := NULL,
Lo hago así para no entregar al TIBQuery ese campo en el intento de que así salte el Trigger y rellene el campo con el número siguiente al que guarda el generador.
Entiendo que dicho campo se ha de dejar vació para ello.
Osea que estoy en un lío.
Con anterioridad había usado una tabla de control secundaria para llevar a cabo los autoincrementos y me iba bien. Pero he querido usar los triggers y ya ves.
Reitero:
Osea primero pongo un nº en el DBGrid y luego lo quito para llevarlo al servidor y que salte el Trigger. ¿Es lo correcto?
Saludos, bulc

bulc 16-01-2014 19:08:51

Quitar el valor al campo primary key
 
Primero relleno el campo en el DBGrid para que no salte el error debido a ausencia de dato en clave primaria (NUM).
Para ello saco el valor del generador sin alterarlo. Uso un Query con:
Select gen_id( Gen_Name,0) from rdb$database
Luego lo guardo como valor de salida con:
Result :=Query1.Fields[0].AsInteger // en una función.
Luego aplico el evento BeforePost para controlar los valores que van al ClientDataSet.
En el intento de que el Trigger haga su papel, quiero vaciar el campo NUM que el de clave primaria.
Utilizo en ese evento esto:
IBDataSourcex.DataSet.FieldByName('NUM').AsInteger := NULL;
pero me da un error. Y si lo dejo lleno -el valor está incrementado- y además es correcto.
Pero el generador no salta (+1) por insertar dato en ese campo.
¿Entiendes lo paradógico del caso?
Uno parece que se explica pero luego el lector no se aclara. Muy de risa.
¿Qué se te ocurre? ¿Por dónde me voy?
Gracias por tu tiempo.
Un saludo, bulc

fjcg02 16-01-2014 21:52:41

Creo que va a ser difícil que desde el programa delphi dejes el valor de la pk a nulo. Creo que siempre te dará error. Los triggers que ponen la clave con el valor de un generador te funcionarán desde el ibexpert o desde el isql.

Si desde el programa cliente quieres recuperar el valor de la pk generado por el trigger, le tienes que decir que el campo no es requerido y después de hacer la excritura hacer un refresh de ese registro. Ahora, no sé si será capaz de recuperar el registro, ya que lo guardas con la pk en blanco , el motor le asigna un valor y luego te lo devuelve.

No sé si habrá alguien que pueda aportar otra experiencia, esta es la mía.

Saludos

ecfisa 16-01-2014 23:45:25

Hola bulc.

Solo a fin de responder al título de tu mensaje #30, el método Clear lo hace.

Un ejemplo:
Código Delphi [-]
procedure TForm1.Button1Click(Sender: TObject);
begin
  with IBDataSet1 do
  begin
    Edit;
    FieldByName('PRODUCTO').Clear;
    if VarIsNull(FieldByName('PRODUCTO').Value) then
      ShowMessage('El valor del campo ahora es NULL')
    else
      ShowMessage('Debería dedicarme a otra cosa...');
    Cancel;
  end
end;

Saludos :)

bulc 17-01-2014 00:13:54

Gracias egfisa
 
Muchas gracias por tu apunte, egfisa
bulc

bulc 09-03-2014 21:54:23

Lo he probado y me ha ido a la perfección...
 
Bueno, por fin he comprendido como se efectúa la llamada al Trigger enviando y recibiendo parámetros.
El basado en el Trigger lo he completado usando cero (0) en el momento de la inserción. Así, el Trigger compara para valores null y para cero. En ese caso asigna el Trigger que toca sin problemas. Luego, en otra DBF, he optado por usar una función que llama al generador para que autoincremente el nº de registro que uso como índice de clave primaria.
En el momento del nuevo registro (ALTA) uso el generador sin cambiarlo y le añado un uno. Cuando el Alta se guarda entonces se efectúa otra llamada al generador con aumento +1. Hay una variable global EsAlta:=False/True según que la llamada sea definitiva o no, en el botón de altas. Se pasa a la funcion como segundo parámetro (SeRevisa).
En número del generador se pasa a un TLabel para estar informado en todo momento del nº de final de registros; el último GEN_ID.
Si se necesita el código completo...a pedir.

nRec:=REVISA_GEN( IBQuery1:TIBQuery; SeRevisa: Boolean; Label1: TLabel): Integer;

Gracias a todos los participantes. Ha sido muy ilustrativo. bulc

bulc 09-03-2014 22:05:31

Lo he probado y me ha sido de gran ayuda...
 
Además de usar esa técnica(la del Trigger) he probado otra con una función.
En la del Trigger he usado la inserción provisional de un cero. Así se llena el campo que por ser NOT NULL no puede dejarse vació. Luego el Trigger lo detecta e inserta el nº que toca en la serie.
Como segunda técnica, en otro .FBD he usado una función de usuario que llama al GEN_ID.
SELECT GEN_iD( GEN_JOT, 0) FROM RDB$DATABASE
El botón de ALTAS usa la variable EsAlta (boolean). Así si el ALTA se confirma con el botón GUARDAR, se pasa al generador un +1 y si no se queda como está
La función es
nRec:=REVISA_GEN( IBQuery1: TIBQuery; SeRevisa: Boolean; TLabel1:TLabel): integer;
El nº se ve en el TLabel para controlar los registros de la tabla y el resto es historia.
Si alguien quiere el código...
Saludos y gracias a todos los que han intervenido. Ha sido un mazazo saber de los parámetros de entrada y salida en IBQuery... Muy bueno.
Doy, pues, el hilo por acabado.


La franja horaria es GMT +2. Ahora son las 01:24:46.

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