Club Delphi  
    FTP   CCD     Buscar   Trucos   Trabajo   Foros

Retroceder   Foros Club Delphi > Bases de datos > Firebird e Interbase
Registrarse FAQ Miembros Calendario Guía de estilo Temas de Hoy

Respuesta
 
Herramientas Buscar en Tema Desplegado
  #1  
Antiguo 30-03-2007
Avatar de morta71
morta71 morta71 is offline
Miembro
 
Registrado: may 2006
Ubicación: Girona - España
Posts: 30
Poder: 0
morta71 Va por buen camino
Error Foreyng key en Master-Detail

Hola a todos,

hace un tiempo que vengo aprendiendo con Firebird, y ahora me llego el turno de las tablas maestro-detalle.

Bien el caso es que no sé como evitar el error "violation of FOREYNG KEY constraint".

Esto realizando las pruebas con la siguiente base de datos:

Código SQL [-]
 CREATE DATABASE 'C:\FDBDATA\PRUEBAS.fdb'
 USER 'SYSDBA' PASSWORD 'masterkey';
 
 CREATE GENERATOR ID_MASTER;
 SET GENERATOR ID_MASTER TO 0;
 
 CREATE TABLE LINEAS (
     ID           INTEGER NOT NULL,
     LINEA        INTEGER NOT NULL,
     DESCRIPCION  VARCHAR(20)
 );
 
 CREATE TABLE MASTER (
     ID      INTEGER NOT NULL,
     NOMBRE  VARCHAR(20)
 );
 
 ALTER TABLE LINEAS ADD CONSTRAINT PK_LINEAS PRIMARY KEY (LINEA, ID);
 ALTER TABLE MASTER ADD CONSTRAINT PK_MASTER PRIMARY KEY (ID);
 
 ALTER TABLE LINEAS ADD CONSTRAINT RELATION_200 FOREIGN KEY (ID) REFERENCES MASTER (ID);
 
 CREATE TRIGGER MASTER_BI0 FOR MASTER
 ACTIVE BEFORE INSERT POSITION 0
 AS
 begin
   NEW.ID = GEN_ID(ID_MASTER, 1);
 end;

He creado un formulario con el DBEdit para el campo NOMBRE de la tabla Master, y un DBGrid para la tabla Lineas. Las tablas tienen la propiedad CachedUpdates activada.

En algún sitio lei que daba problemas al actualizar si la propiedad DataSource de la tabla subordinada 'detalles' estaba asisgnada en el momento del post, así que en el evento click del botón grabar hay el siguiente código:

Código Delphi [-]
procedure TForm1.Button3Click(Sender: TObject);
begin
  DataModule2.Lineas.DataSource := nil;

  DataModule2.Master.Post;
end;


El código del DataModule son los siguiente:

Código Delphi [-]
 unit Unit2;
 
 interface
 
 uses
   Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
   IBDatabase, Db, IBCustomDataSet;
 
 type
   TDataModule2 = class(TDataModule)
     IBDatabase: TIBDatabase;
     IBTransaction: TIBTransaction;
     Master: TIBDataSet;
     Lineas: TIBDataSet;
     dsMaster: TDataSource;
     dsLineas: TDataSource;
     procedure DataModuleCreate(Sender: TObject);
     procedure MasterAfterCancel(DataSet: TDataSet);
     procedure MasterAfterDelete(DataSet: TDataSet);
     procedure MasterAfterPost(DataSet: TDataSet);
     procedure LineasBeforeEdit(DataSet: TDataSet);
     procedure DataModuleDestroy(Sender: TObject);
     procedure LineasNewRecord(DataSet: TDataSet);
   private
     { Private declarations }
   public
     { Public declarations }
   end;
 
 var
   DataModule2: TDataModule2;
 
 implementation
 
 {$R *.DFM}
 
 procedure TDataModule2.DataModuleCreate(Sender: TObject);
 begin
   IBDatabase.Open;
   Master.Open;
   Lineas.Open;
 end;
 
 procedure TDataModule2.MasterAfterCancel(DataSet: TDataSet);
 begin
   Master.CancelUpdates;
   Lineas.CancelUpdates;
 end;
 
 procedure TDataModule2.MasterAfterDelete(DataSet: TDataSet);
 begin
   IBDatabase.ApplyUpdates([Lineas, Master]);
   IBTransaction.CommitRetaining;
 end;
 
 procedure TDataModule2.MasterAfterPost(DataSet: TDataSet);
 begin
   Lineas.Post;
 
   IBDatabase.ApplyUpdates([Master, Lineas]);
   IBTransaction.CommitRetaining;
 
   Lineas.DataSource := dsMaster;
 end;
 
 procedure TDataModule2.LineasBeforeEdit(DataSet: TDataSet);
 begin
   Master.Edit;
 end;
 
 procedure TDataModule2.DataModuleDestroy(Sender: TObject);
 begin
   IBTransaction.Rollback;
   IBDatabase.Close;
 end;
 
 procedure TDataModule2.LineasNewRecord(DataSet: TDataSet);
 begin
   Lineas.FieldByName('ID').AsInteger := Master.FieldByName('ID').AsInteger;
 end;
 
 end.


¿Puede alguien orientarme dónde estoy fallando, o que puedo hacer para solucionar el error "violation of FOREYNG KEY constraint"?

Saludos
Responder Con Cita
  #2  
Antiguo 30-03-2007
[egostar] egostar is offline
Registrado
 
Registrado: feb 2006
Posts: 6.557
Poder: 25
egostar Va camino a la fama
Una pregunta, en el archivo MASTER ¿ya tienes datos?

Por lo poco que he visto si no exiten datos en MASTER te puede enviar un error porque no encuentra la referencia al momento de grabar datos en LINEAS.

Salud OS.
__________________
"La forma de empezar es dejar de hablar y empezar a hacerlo." - Walt Disney
Responder Con Cita
  #3  
Antiguo 31-03-2007
Avatar de morta71
morta71 morta71 is offline
Miembro
 
Registrado: may 2006
Ubicación: Girona - España
Posts: 30
Poder: 0
morta71 Va por buen camino
MASTER viene a ser por ejemplo la tabla FACTURAS, con sus respectivas líneas.

Con Paradox, puedo generar en el mismo formulario tanto la cabecera de MASTER (o Facturas si preferis), e ir añadiendo registros en LINEAS, todo ello con CachedUpdated activado. Al hacer un Post con Paradox funciona sin ninguna queja ... pero con Firebird no es lo mismo.

Os adjunto los fuentes, está realizado en Delphi 5 profesional.

Gracias
Archivos Adjuntos
Tipo de Archivo: zip master.zip (3,5 KB, 21 visitas)
Responder Con Cita
  #4  
Antiguo 31-03-2007
Avatar de Héctor Randolph
[Héctor Randolph] Héctor Randolph is offline
Miembro Premium
 
Registrado: dic 2004
Posts: 882
Poder: 20
Héctor Randolph Va por buen camino
Hola morta71!

Si estás utilizando una transacción con Interbase, supongo que no será necesario por lo pronto usar actualizaciones en Caché.

Olvidando un poco ese detalle, ¿quién te garantiza que dentro de la transacción actual, se guardan primero los cambios en la tabla maestro y después los cambios en la tabla detalle?.

Por ejemplo, si la tabla maestro estuviera dentro de su propia transacción y la tabla detalle en otra, tu podrías determinar el orden en el cuál se aplican los cambios usando primero Commit en el maestro y después en el detalle. Es probable que de la manera que lo haces actualmente primero se intentar guardar los registros del detalle y no encuentra el registro del maestro simplemente porqué aún no han sido guardados.

Saludos
Responder Con Cita
  #5  
Antiguo 31-03-2007
Avatar de morta71
morta71 morta71 is offline
Miembro
 
Registrado: may 2006
Ubicación: Girona - España
Posts: 30
Poder: 0
morta71 Va por buen camino
Hola Héctor, lo que me comentas tiene sentido, pero yo lo que quiero es que se grabe todo en una misma transacción. Es decir, que si falla al grabar en LINEAS no se actualice MASTER.

Sinembargo, lo que me comentas puedo probrarlo. Sería algo así como realizar una transacción dentro de otra, es decir Transaccion1 que englobaría a MASTER y LINEAS, y Tansaccion2 que se refiere sólo a la tabla MASTER.

De ésta manera realizo primero un Commit en Transaccion2 y después el Commit en Transaccion1. Si en Transaccion1 hay algun fallo, supongo que en el Rollback deshace también Transaccion1???????

Lo probaré a ver que sucede.

Gracias
Responder Con Cita
  #6  
Antiguo 31-03-2007
Avatar de gluglu
[gluglu] gluglu is offline
Miembro Premium
 
Registrado: sep 2004
Ubicación: Málaga - España
Posts: 1.455
Poder: 21
gluglu Va por buen camino
Yo no veo el sentido de utilizar dos transacciones diferentes. Como bien dice morta71, si no se graban LINEAS, para qué actualizar MASTER.

A mi me funciona sin problema, al menos en INTERBASE. Lo que si hay asegurarse en cualquier caso es que hagas un POST de MASTER antes de insertar una nueva línea en LINEAS.

Lo que comentas acerca de desasignar el DataSource antes del Post, yo no lo hago nunca, y no tengo problemas.
__________________
Piensa siempre en positivo !

Última edición por gluglu fecha: 31-03-2007 a las 14:24:47.
Responder Con Cita
  #7  
Antiguo 31-03-2007
Avatar de gluglu
[gluglu] gluglu is offline
Miembro Premium
 
Registrado: sep 2004
Ubicación: Málaga - España
Posts: 1.455
Poder: 21
gluglu Va por buen camino
He revisado tu código que adjuntas.

En :
Código Delphi [-]
procedure TForm1.Button1Click(Sender: TObject);
begin
  DataModule2.Master.Append;
end;

lo que estás haciendo es un Append, y junto con el Generador que tienes puesto estás incremento ese generador en 1. Pero no estás realizando el POST correspondiente, que es la razón principal del error que te está dando.

Al pulsar el Button3 y ejecutar :
Código Delphi [-]
procedure TDataModule2.MasterAfterPost(DataSet: TDataSet);
begin
  Lineas.Post;

  IBDatabase.ApplyUpdates([Master, Lineas]);
  IBTransaction.CommitRetaining;

  Lineas.DataSource := dsMaster;
end;
me dá error en la primera línea ya que Lineas no está en Modo Edit y por lo tanto no se puede realizar un Post de Líneas.

O eso es al menos lo que me parece a mi.

La cuestión es que te has liado mucho en tu código cuando es más fácil de lo que te imaginas.

Haciendo algunos cambios en tu código como eliminar todo el código de MasterAfterPost a mi me funciona, aunque mi manera de programar será difente a la tuya.

Todo el error del Foreign Key viene porque te tienes que ASEGURAR que antes de que ejecutes el Post de la tabla LINEAS, tiene que estar hecho el POST de MASTER.

Espero haberte ayudado un poco.
__________________
Piensa siempre en positivo !
Responder Con Cita
  #8  
Antiguo 31-03-2007
Avatar de morta71
morta71 morta71 is offline
Miembro
 
Registrado: may 2006
Ubicación: Girona - España
Posts: 30
Poder: 0
morta71 Va por buen camino
Gracias por vuestras respuestas, pero en cada prueba obtengo el mismo error .

Si hago lo que me indicas, gluglu, no da error porque no hago el Post en Lineas, pero en cuanto intento actualizar Lineas ... zas el error.

Si te funciona, te importa, por favor, enviarme el ejemplo corregido (mi e-mail es francisco@redlocal.org)

Gracias
Responder Con Cita
  #9  
Antiguo 31-03-2007
Avatar de gluglu
[gluglu] gluglu is offline
Miembro Premium
 
Registrado: sep 2004
Ubicación: Málaga - España
Posts: 1.455
Poder: 21
gluglu Va por buen camino
Aquí tienes el código modificado como yo entiendo es más fácil y más simple.

Hay una cosa que debes de eliminar por tu cuenta, y es el Trigger en tu base de datos sobre la tabla Master.

Cada vez que se añade un registro a Master, actua tanto el incremento del generador que has definido en DataModule2 sobre la tabla Master, como el propio Trigger de la Base de Datos. Esto provoca entre otras cosas que cada vez que añades un registro a Master, el generador ID aumente su valor en 2.

Te darás cuenta que es mucho más simple que lo que tu imaginaste en un principio.

Seguramente (lo he puesto como comentario dentro del propio código) no eras consciente tampoco que cada vez que cambias de registro en un DBGrid, se ejecuta automáticamente un POST por parte de Delphi, y eso provocaba que sobre LINEAS se realizaba un POST sin pulsar el botón de grabar, y por lo tanto si no existía el correspondiente registro en MASTER con el ID correcto daba error.

Espero haberte ayudado.

Si tienes cualquier otra duda, aquí estamos para ayudarte en lo que podamos.

Mientras estoy subiendo el archivo ZIP creo recordar que no he cambiado la ruta de la base de datos. Está para la configuración de mi ordenador. Acuérdate de cambiarla correctamente en tu DataModule2 antes de compilar y ejecutar.
Archivos Adjuntos
Tipo de Archivo: zip Morta71.zip (4,8 KB, 29 visitas)
__________________
Piensa siempre en positivo !
Responder Con Cita
  #10  
Antiguo 31-03-2007
Avatar de morta71
morta71 morta71 is offline
Miembro
 
Registrado: may 2006
Ubicación: Girona - España
Posts: 30
Poder: 0
morta71 Va por buen camino
Mil gracias gluglu, funciona perfectamente. Lo mejor es lo más simple ... tengo que perder ciertos vicios.

Ciertamente al estar dentro de una transacción, el tema de CachedUpdates sobra.

Gracias
Responder Con Cita
  #11  
Antiguo 11-04-2007
niñotaliban niñotaliban is offline
Miembro
 
Registrado: nov 2005
Posts: 13
Poder: 0
niñotaliban Va por buen camino
no lo veo claro

Hola, me veo en una situación muy parecida a la de morta71 y he despejado algunas dudas con este hilo pero en el código corregido por gluglu veo esto al hacer click en añadir:


Código Delphi [-]
procedure TForm1.Button1Click(Sender: TObject);
begin
DataModule2.Master.Append;
// El Generador es el que te incrementa automáticamente
// el valor de ID.
// Tienes que asegurarte que antes de insertar en LINEAS
// se haga el POST con el ID correspondiente.
DataModule2.Master.Post;
// Por ello hago el Post aquí.
end;





Pero de esta manera no estamos insertando un registro vacío (bueno con su id) y luego mediante el autoedit de los dbedit editandolo? y luego vale, añadimos lineas de detalle sin problemas porque nos aseguramos que exista el registro maestro.

Y si no puedo insertar un registro vacío (solo con su id) porque tengo campos obligados?? no puedo hacer un post justo después del append.

He optado por hacer el post del master en el onnewrecord de lineas pero si el usuario no rellena los campos obligados antes de ponerse a insertar lineas en la factura CATACRAC, y eso no puede ser.

No se... no lo veo claro.
Responder Con Cita
  #12  
Antiguo 11-04-2007
Avatar de gluglu
[gluglu] gluglu is offline
Miembro Premium
 
Registrado: sep 2004
Ubicación: Málaga - España
Posts: 1.455
Poder: 21
gluglu Va por buen camino
Evidentemente la cosa se puede complicar todo lo que uno quiera.

Una cosa es el código simple que añadí a este hilo, como corrección del que puso morta71, y otra cosa muy diferente es cualquier otro código que se quiera programar.

Lo único que hay que tener claro es que antes de hacer el Post del detalle, tiene que existir el correspondiente registro en el Maestro (que se habrá confirmado con su correspondiente Post en el Maestro).

Las operaciones y comprobaciones que puedas o tengas que hacer previamente en el maestro, es asunto particular tuyo.

En el código que adjunté, como precisamente no había que hacer ninguna comprobación ni nada parecido, y a fines de comprensión y aclaración, opté por poner el Post justo después del Append, que la verdad es que no se debería hacer nunca en condiciones normales.

A lo mejor te puedo ayudar en algunas cosas más concretas. Si es así, te rogaría exponer un poco más del código que pretendes ejecutar.

Saludos
__________________
Piensa siempre en positivo !
Responder Con Cita
  #13  
Antiguo 11-04-2007
niñotaliban niñotaliban is offline
Miembro
 
Registrado: nov 2005
Posts: 13
Poder: 0
niñotaliban Va por buen camino
Grácias gluglu por tu interés.

Pues mi código es muy similar al de morta71:

tengo una tabla maestra:

create table FACTURA (
FAC_ID IDENTIFICADOR not null,
CLI_ID IDENTIFICADOR not null,
FAC_FECHA FECHA,
etc,
etc,
constraint PK_FACTURA primary key (FAC_ID)
);

alter table FACTURA
add constraint FK_FACTURA_RELATIONS_RECIBO foreign key (REC_ID)
references RECIBO (REC_ID);
alter table FACTURA
add constraint FK_FACTURA_RELATIONS_FORMAPAG foreign key (FPA_ID)
references FORMAPAGO (FPA_ID);
alter table FACTURA
add constraint FK_FACTURA_TIENE_CLIENTE foreign key (CLI_ID)
references CLIENTE (CLI_ID);

y otra tabla detalle:

create table DETALLEFACTURA (
DTF_ID IDENTIFICADOR not null,
FAC_ID IDENTIFICADOR not null,
DTF_DESCRIPCION CHAR(40),
etc,
etc,
constraint PK_DETALLEFACTURA primary key (DTF_ID)
);

alter table DETALLEFACTURA
add constraint FK_DETALLEF_DETALLE_FACTURA foreign key (FAC_ID)
references FACTURA (FAC_ID);

Ambas con identificador autoincremental.

En el formulario muestro en dbedits los campos de FACTURA y en un dbgrid los datos de DETALLEFACTURA. En el formulario están los botones Insertar, Buscar, Modificar, Aceptar, Cancelar y Eliminar que en principio actuan sobre la tabla FACTURA amenos que añada algo para la tabla DETALLEFACTURA, y esta última se gestiona automáticamente mediante el dbgrid con su propiedad dgEditing a true.
Código Delphi [-]
//al clickar en Insert:
IbDataPrincipal.Insert;     //en IbDataPrincipal está cargada la tabla FACTURA
 
//al clickar en Aceptar:
if IbDataPrincipal.State in dsEditModes then IbDataPrincipal.Post;
if IBDataDetalleFactura.State in [dsEdit, dsInsert] then IBDataDetalleFactura.Post;
IBTrans.CommitRetaining;
 
//al clicar en Cancelar:
IbDataPrincipal.Cancel;
IBTrans.RollbackRetaining;
 
//en onnewrecord del dataset de DETALLEFACTURA:
if IbDataPrincipal.State = dsinsert then IbDataPrincipal.Post;
IBDataDetalleFactura.Edit;
IBDataDetalleFacturaFAC_ID.Value:= IbDataPrincipalFAC_ID.Value;

(tengo bastante más código pero no tiene porque afectar a este asunto, en principio)

De esta manera cuando hago clic en insertar e inmediatamente intento insertar una linea en el dbgrid del detalle ma da el error "Field 'CLI_ID' must have a value"

Por qué me da el erro lo tengo claro, que lo podria controlar y mostrarle un mensaje que le digera al usuario que tiene que rellenar el campo CLI_ID antes de añadir lineas en el detalle, también lo tengo claro. Pero, no sería más bonito si el formulario en sí se comportara como el que muestra una simple tabla donde el mensaje de control de datos no se muestraria (si fuera preciso) hasta clicar el botón de Aceptar??

Hay alguna posibilidad para lo que quiero? o soy muy pijotero? jejeje me salió un pareado!

Un saludo.
Responder Con Cita
  #14  
Antiguo 11-04-2007
Avatar de gluglu
[gluglu] gluglu is offline
Miembro Premium
 
Registrado: sep 2004
Ubicación: Málaga - España
Posts: 1.455
Poder: 21
gluglu Va por buen camino
Como tu dices, está claro que no puedes añadir líneas de detalle hasta que no exista el correspondiente registro en el maestro. Y ya sabes por qué te dá el error y como lo podrías evitar.

Respecto a como sería mejor hacerlo e indicar sólo el error en el momento de pulsar el botón aceptar ? ... pues cada uno tendrá una manera de programar diferente y cada uno entenderá una mejor forma de hacerlo.

Yo expongo dos opiniones :

1. Si tienes el campo CLI_ID con la condición NOT NULL, pues no lo dejes a Null cuando insertes líneas en el detalle. Reemplázalo por ejemplo en el Insert del Master con el valor 0. De esta manera la Base de Datos no te dará error.

Al pulsar el botón compruebas si el valor de CLI_ID es 0, y si es así lanzas el mensaje de error.

Si le pides al usuario posteriormente introducir un valor de CLI_ID, basta que en la definición de tu tabla DETALLE en la Base de Datos incluyas junto con la Foreign Key una condición de 'On Update Cascade' por ejemplo.

2. No pongas la condición NOT NULL para el campo CLI_ID, y lo que haces en esta ocasión es conprobar precisamente si ese campo está a Null al pulsar el botón Aceptar.

Esto en cambio te impide utilizar CLI_ID como clave referencial, que creo haber visto que sí la utilizas como tal clave referencial.

Pero podrías utilzar Triggers que te actualizaran las demás tablas al realizar un cambio sobre CLI_ID.


Lo dicho, cada programador tendrá una visión diferente de como solucionar un problema identificado, tal y como tu ya lo has identificado.

Saludos,
__________________
Piensa siempre en positivo !
Responder Con Cita
  #15  
Antiguo 11-04-2007
niñotaliban niñotaliban is offline
Miembro
 
Registrado: nov 2005
Posts: 13
Poder: 0
niñotaliban Va por buen camino
bueno... me sirve de muchisima ayuda ver diferentes maneras de solventar un problema y al final he optado por mostrar un mensaje al intentar añadir una linea en el detalle advirtiendo que hay que indicar primero un cliente, y para que el usuario no se moleste en buscar el campo que tiene que rellenar, justo al cerrarse el mensaje de aviso le abro el formulario de busqueda de cliente para que lo elija y ale.. a correr. jejeje no és lo que queria en un principio pero ha quedado apañao.

de las 2 opciones que me das:
1- me obliga a tener un registro en la tabla cliente con todos los campos vacios y el id = 0, para que no me de un "violation key" y no me gusta.
2-no se corresponde a mi planteamiento inicial.

...como bien dices, sobre gustos no hay nada escrito

Muchas grácias, otra vez, gluglu por tu atención.

Saludos.
Responder Con Cita
Respuesta



Normas de Publicación
no Puedes crear nuevos temas
no Puedes responder a temas
no Puedes adjuntar archivos
no Puedes editar tus mensajes

El código vB está habilitado
Las caritas están habilitado
Código [IMG] está habilitado
Código HTML está deshabilitado
Saltar a Foro

Temas Similares
Tema Autor Foro Respuestas Último mensaje
Dá error "Master has detail records" cuando no debería darlo! JuanBCT Tablas planas 3 06-08-2005 03:48:37
DBLookupComboBoxs Master/Detail o Detail/Master yusnerqui OOP 5 29-07-2005 18:40:20
master/detail, imprimiendo master en cada hoja acalderonr Impresión 4 29-11-2003 14:46:07
interbase:¿como crear un master-detail-detail? ElSanto24 Firebird e Interbase 2 22-10-2003 10:24:45
Master/Detail/Detail/Detail/etc... hgiacobone Tablas planas 2 24-07-2003 17:20:31


La franja horaria es GMT +2. Ahora son las 10:05:06.


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
Copyright 1996-2007 Club Delphi