Newsgroup: it.comp.lang.delphi
Titolo: transazione con master detail 1 - 1 [un po' lungo per il codice]

sto utilizzando D5 update pack1, Zeos 6.1.5 stable patch 1, firebird 1.0.3
vorrei realizzare una form per l'inserimento di dati in realazione master
detail 1-1
cioč ad un record master corrisponde un solo record detail
se durante il post del record di dettaglio
viene sollevata una eccezione succede che
correggendo  l'inserimento e riapplicando le modifiche
ottengo un errore che questa volta non dovrei avere
"SQLError : violation of FOREIGN KEY connstraint "FK_DETAIL_1" on table
"DETAIL". ErrorCode : -530 violation of FOREIGN KEY constaint """". The SQL
: INSERT INTO DETAIL (IDX_MASTER, NMAC, PRIORITA) VALUES (?,?,?)
non riesco a vedere dove sbaglio (di seguito riporto il codice)
per completezza di informazioni posto anche
la struttura della tabella di dattaglio
============================================================
CREATE TABLE "DETAIL"
(
   "IDX_MASTER"     INTEGER NOT NULL,
   "NMAC"     INTEGER NOT NULL,
   "PRIORITA"     SMALLINT NOT NULL,
CONSTRAINT "PK_DETAIL" PRIMARY KEY ("IDX_MASTER")
);
/*  Index definitions for DETAIL */
CREATE UNIQUE INDEX "UNQ_DETAIL_01" ON "DETAIL"("NMAC", "PRIORITA");
ALTER TABLE "DETAIL" ADD CONSTRAINT "FK_DETAIL_1" FOREIGN KEY 
("IDX_MASTER") REFERENCES "MASTER" ("IDX") ON DELETE CASCADE;
============================================================
facendo debug sulla unit di TZUpdateSQL
(se il debgug l'ho eseguito correttamente)
ho verificato che i valore dei parametri che
vengono presi per costruire lo statment sql sono giusti
per cui se di bug si tratta č ancora piů a valle
Ringrazio anticipatamente per i Vs. suggerimenti
Cordialmente,
Stefano
===========================================================
   TdtmdDati = class(TDataModule)
     ZqryMaster: TZQuery;
     ZqryDetail: TZQuery;
     ZUpdateSQLmaster: TZUpdateSQL;
     ZUpdateSQLdetail: TZUpdateSQL;
     dtsrcMaster: TDataSource;
     dtsrcDetail: TDataSource;
     ZqryMasterIDX: TIntegerField;
     ZqryMasterCODICE: TStringField;
     ZqryMasterDESCRIZIONE: TStringField;
     ZqryMasterNMAC: TIntegerField;
     ZqryDetailIDX_MASTER: TIntegerField;
     ZqryDetailNMAC: TIntegerField;
     ZqryDetailPRIORITA: TSmallintField;
     procedure DataModuleCreate(Sender: TObject);
     procedure ZqryMasterAfterEdit(DataSet: TDataSet);
     procedure ZqryMasterAfterInsert(DataSet: TDataSet);
     procedure dtsrcMasterDataChange(Sender: TObject; Field: TField);
     procedure ZqryMasterAfterPost(DataSet: TDataSet);
     procedure ZqryDetailBeforePost(DataSet: TDataSet);
     procedure ZqryMasterBeforePost(DataSet: TDataSet);
     procedure ZqryDetailAfterInsert(DataSet: TDataSet);
   private
     { Private declarations }
   public
     { Public declarations }
      procedure Apply;
      procedure Cancel;
      procedure Edit;
      procedure New;
      procedure Delete;
      procedure Open;
      procedure Close;
   end;
var
   dtmdDati: TdtmdDati;
implementation
uses
   dtmd_DBGlobal;
var
   FMasterBM : TBookmarkStr;
{$R *.DFM}
procedure TdtmdDati.Apply;
begin
   //
   dtmdDBGlobal.ZConnection1.StartTransaction;
   try
      if ZqryMaster.State in [dsEdit, dsInsert] then
         ZqryMaster.Post;
      if ZqryDetail.State in [dsEdit, dsInsert] then
         ZqryDetail.Post;
      dtmdDBGlobal.ZConnection1.Commit;
   except
      dtmdDBGlobal.ZConnection1.Rollback;
      ZqryMaster.Edit;
      ZqryDetail.Edit;
      raise
   end;
end;
procedure TdtmdDati.Cancel;
begin
   ZqryMaster.Cancel;
   ZqryDetail.Cancel;
   ZqryMaster.Close;
   ZqryMaster.Open;
end;
procedure TdtmdDati.DataModuleCreate(Sender: TObject);
begin
    FMasterBM := '';
end;
procedure TdtmdDati.ZqryMasterAfterEdit(DataSet: TDataSet);
begin
    ZqryDetail.Edit;
end;
procedure TdtmdDati.ZqryMasterAfterInsert(DataSet: TDataSet);
begin
    LinkDetail;
    ZqryDetail.Append;
end;
procedure TdtmdDati.ZqryDetailBeforePost(DataSet: TDataSet);
begin
    ZqryDetailIDX_MASTER.AsInteger := ZqryMasterIDX.AsInteger;
    ZqryDetailNMAC.AsInteger := ZqryMasterNMAC.AsInteger;
end;
procedure TdtmdDati.Delete;
begin
    ZqryMaster.Delete;
end;
procedure TdtmdDati.Edit;
begin
    ZqryMaster.Edit;
end;
procedure TdtmdDati.New;
begin
   ZqryMaster.Append;
end;
procedure TdtmdDati.Open;
begin
    ZqryDetail.Open;
    ZqryMaster.Open;
end;
procedure TdtmdDati.Close;
begin
    ZqryDetail.Close;
    ZqryMaster.Close;
end;

 
Probabilmente il problema č il valore di IDX_MASTER che sulla tabella
master non
č ancora presente sul DB in quando non č ancora committed.
Ciao
Micky
-- 
Posted via Mailgate.ORG Server - 
http://www.Mailgate.ORG

 >>sto utilizzando D5 update pack1, Zeos 6.1.5 stable patch 1, firebird 
1.0.3
 >>
 >>vorrei realizzare una form per l'inserimento di dati in realazione master
 >>detail 1-1
 >>cioè ad un record master corrisponde un solo record detail
 >>
 > se è corretto ciò che hai scritto perche utilizzare il sistema
 > master/detail
 > normalmente il master ha 1 record ed il dettaglio ha n records tu dici
 > di
 > voler avere nel detail solo 1 record (1:1), quindi metti i campi del
 > detail
 > nel master.
questa è una bella osservazione su cui ho riflettuto un po'
perima di optare per questa soluzione
il motivo è che devo schedulare dei processi da inviare a
delle macchine quindi mi sembrava più performante in fase
di caricamento del processo interrogare su una tabella
più piccola seppur con un left join per recuperare tutte le informazioni
necessarie piuttosto che filtrare con clausola where su una tabella
che con il tempo potrebbe diventare molto grande
se sto dicendo una fesseria con questa
considerazione sono pronto a correggere
 >>se durante il post del record di dettaglio
 >>viene sollevata una eccezione succede che
 >>correggendo  l'inserimento e riapplicando le modifiche
 >>ottengo un errore che questa volta non dovrei avere
 >>
 >>"SQLError : violation of FOREIGN KEY connstraint "FK_DETAIL_1" on table
 >>"DETAIL". ErrorCode : -530 violation of FOREIGN KEY constaint """". 
The SQL
 >>: INSERT INTO DETAIL (IDX_MASTER, NMAC, PRIORITA) VALUES (?,?,?)
 >>
 >
 >
 > Probabilmente il problema è il valore di IDX_MASTER che sulla tabella
 > master non
 > è ancora presente sul DB in quando non è ancora committed.
provo a darrmi una risposta anch'io  dopo aver
approfondito il funzionamento degli zeos
e alcune ricerche su google che mettono in evidenza
come lo stesso problema sia presente anche su IBX
zeos effettua dei committ retaining e dei rollback retaining
nel caso specifico quando viene sollevata un' eccezione
eseguendo il rollback i dai sul client non sono più in sincronia con i
dati sul server
riporto un post di Jason Wharton (autore degli IBO)
estratto da un thread su borland.public.delphi.database.desktop
===================================================================
Rollback retaining is quite a bit more difficult to implement than
CommitRetaining. It is especially more difficult to respond to on the client
if record buffering is going on.
By doing a CommitRetaining you are ensuring that which is in the client
buffers matches what is the committed state on the server. Thus, as a result
of it the client and server are in sync with eachother. This is easy to deal
with.
When a RollbackRetaining is performed this causes the server to disregard
all the data manipulation requests that the client has made. This causes the
current state of the buffers to become out of sync with the server. Not os
easy to deal with.
What needs to happen to make this work is one of two things. Either the
client refreshes all of its datasets (in which changes were made since being
opened) after the RollbackRetaining is performed so that they are back in
sync or the client needs to keep track of every change that has been made
and then effectively reverse those changes item by item in the buffers. Or,
it could selecitively refresh just those rows affected. As long as triggers
have done furthur changes the client and server would be back in sync.
In IBO I already have a RollbackRetaining method that makes use of the first
method mentioned. I have to because it actually maps to a Rollback which
ends the transaction. Then, all datasets are refreshed (automatically) and
the record pointer is preserved to the greatest degree possible. It's easy
and works real nice.
Thus, when IB6 actually gives us true RollbackRetaining I'll already have
the plumbing for it. So, if you happen to be using IBO you can start using
the rollback retaining feature right now with its emulation. It remains to
be seen just how well I can take advantage of the true RollbackRetaining
when it is released. I'll have to refresh fewer datasets but I'm not certain
of any other benefits without quite a bit more work going into it.
I'm still debating on whether I should try and implement the second means of
keeping synchronization or not. It will be a lot of work and may not always
work (in the case of trigger intitiated changes getting into the buffer on
the client). Isolation levels may also have an influence here too.
Hope something of this discussion was of use to you.
Jason Wharton
CPS - Mesa AZ
===================================================================
qui porto il link al thread completo 
http://makeashorterlink.com/?M38221B59
a questo punto la prima soluzione molto molto sporca :(
che mi viene in mente è di spostare il rollback sul bottone Cancel
ma in questo modo delego all'utente il potere di chiudere una
transazione che io ho aperto :( . mi sembra molto disdicevole
lasciare una transazione aperta fintanto che l'utente non decida
sul da farsi :(
la seconda soluzione sarebbe quella di acquistare gli IBO
la terza : ricever Vostri preziosi ed iluminati suggerimenti
Ringrazio anticipatamente
Cordialità,
Stefano

Un join è sicuramente + pesante che non una ricerca in una simile tabella...
secondo me una volta che hai stabilito una buona chiave e quindi un buon
indice andrai sicuramente + veloce su una tabella piuttosto che su due...
nota che nulla ti vieta di effettuare una ricerca su un campo solo e poi di
estendere la visualizzazione all'intera tabella con un altro sql
--------------------------------
Inviato via 
http://arianna.libero.it/usenet/

 indice andrai sicuramente + veloce su una tabella piuttosto che su due...
quindi per esprimere la cosa in soldoni :
se l'ipotetica unica tabella fosse di un milione di record
eseguire una select (con le premesse da te indicate) con la sua
bella  clausola where
è sempre meglio che una tabella "master" sempre da un milione di record
una tabella "detail" da qualche decina
su cui poi eseguire qualcosa tipo
select <elenco campi>
from detail
left join detail.campo1 = master.campo1
where detail.campo2 = <valore>
la prima soluzione risulterà sempre migliore ?!?!
Cordialmente,
Stefano

 estendere la visualizzazione all'intera tabella con un altro sql
non ho capito cosa intendi
mi puoi fare un piccolo esempio ?
Cordialmente,
Stefano

 Stefano
Se tu hai una tabella master di un milione di records ed hai una 
relazione 1 a 1 ti ritrovi di conseguenza una tabella detail di
un milioni di records.
Ora se tu fai una select con join su detail verrà effettuata una 
selezione su due tabelle, estrazione del record da master ed 
estrazione del record da detail.
Se invece la tabella è una sola ti basta solo la prima operazione per
ottenere il risultato, quindi come minimo metà tempo.


 ottenere il risultato, quindi come minimo metà tempo.
cmq tengo presente le tue considerazioni
Cordialmente,
Stefano

 
C'è qualche cosa che mi sfugge, per ogni operazione che tu fai crei un
record nel master e di conseguenza un record nel detail, quindi
dovrai per forza avere lo stesso numero di records in entrambe
le tabelle.
Oppure i record master sono già presenti (esempio una anagrafica
alla quale vuoi associare delle lavorazioni)?
Cosa intendi lavorazioni attive, che nella tabella sono presenti n
lavorazioni di cui n attive? (es. un milione di lavorazioni di cui 
100 attive) o cosa?


MdR wrote:
 >> sono un po' ci coccio scusa
 >> la relazione è sì 1 a 1
 >> però solo per i record presenti sul detail
 >>
 >> quindi partirei dal detail per risalire ai dati
 >> che mi servono del master
 >>
 >> sulla tabella detail avrò sempre e solo
 >> le lavorazioni attive o da effettuare
 >> per un totale che al massimo saranno sempre un 100
 >>
 >>
 >>
Ciao Micky
grazie per la pazienza :-)
 > C'è qualche cosa che mi sfugge, per ogni operazione che tu fai crei un
 > record nel master e di conseguenza un record nel detail, quindi
 > dovrai per forza avere lo stesso numero di records in entrambe
 > le tabelle.
 > Oppure i record master sono già presenti (esempio una anagrafica
 > alla quale vuoi associare delle lavorazioni)?
 > Cosa intendi lavorazioni attive, che nella tabella sono presenti n
 > lavorazioni di cui n attive? (es. un milione di lavorazioni di cui 
100 attive) o cosa?
sì la seconda che hai detto (cit. Quelo) :-)
nella tabella master ho delle lavorazioni
che quando vengono inserite verranno segnate come "Da fare"
contestualmente inserico nel dettaglio la macchina su cui eseguire la
lavorazione e la priorità
qundi la macchina interrogherà la tabella di dettaglio per
cercare la lavorazione o commessa da eseguire
terminata la lavorazione elimino il record di dettaglio
ma sul master rimango i dati della partita terminata per mantenere
uno sotorico di quali commesse eseguite e su quali macchine
il fatto di aver scelto questo schema
è dovuto anche dal fatto che può
capitare di dover riordinare le priorità
di esecuzione quindi pensavo fosse meglio eseguire
questi update su una tabella molto più piccola
ma forse il gioco non vale la candela
al momento sono ancora dubbioso
sul fatto di aver scelto in modo corretto
Cordialmente,
Stefano

Allora non ho la minima idea di come siano le tue tabelle però avrai X campi
denominati come Campo1, campo2, campo3.......campox
Facciamo che il 99% delle ricerche verranno fatte su un campo solo ad
esempio Campo99 per far andare forte le cose cosa farai definirai un
bell'indice sul campo99 e userai quel campo li per effettuare le ricerche
quindi
select "qualcosa" from tabella where campo99 = "Condizione"
Ora se hai x campi fare una select * impiegherà molto tempo se tu fai una
select del tipo select "ristretto sottoinsieme" otterrai una query molto +
rapita quando poi aprirai il detail allo scaturire dell'evento di apertura
andrai semplicemente a fare una select di tutto quello che ti serve mettendo
nel were la chiave primaria, in questo modo devi portarti in memoria meno
dati e quindi andrai + veloce.
L'aspetto fondamentale è la decisione dell'indice da usare e quello dipende
molto dall'utilizzo della tabella ad esempio se il 99% delle operazioni sono
di ricerca un indice di tipo cluster aumenterà sensibilmente le prestazioni,
ovviamente a discapito delle operazioni di scrittura.
Spero di essere stato chiaro.
Ciao
       Matteo



     ZqryDetailPRIORITA: TSmallintField;
ho notato che l'unico campo in più che hai su detail è
PRIORITA, quindi mi sembra uno spreco utilizzare 2 tabelle per
ciò che tu devi fare, ti consiglio si semplificare usandone una
sola (MASTER) nella quale aggiungi il campo PRIORITA.
Se hai paura delle prestazioni quando devi fare un Update in presenza
di molti records, considera il fatto di copiare i records chiusi
su una tabella "storico" e successivamente toglierli da quella
principale in modo da lavorare con un ragionevole numero di records.
Ciao
Michy


MdR wrote:
 > Ciao
 > riguardando il tuo primo post
 >
 >
 >>    ZqryMasterIDX: TIntegerField;
 >>    ZqryMasterCODICE: TStringField;
 >>    ZqryMasterDESCRIZIONE: TStringField;
 >>    ZqryMasterNMAC: TIntegerField;
 >>    ZqryDetailIDX_MASTER: TIntegerField;
 >>    ZqryDetailNMAC: TIntegerField;
 >>    ZqryDetailPRIORITA: TSmallintField;
 >
 >
 > ho notato che l'unico campo in più che hai su detail è
 > PRIORITA, quindi mi sembra uno spreco utilizzare 2 tabelle per
 > ciò che tu devi fare, ti consiglio si semplificare usandone una
 > sola (MASTER) nella quale aggiungi il campo PRIORITA.
 >
 > Se hai paura delle prestazioni quando devi fare un Update in presenza
 > di molti records
fosse solo uno non mi preoccuperei
il numero degli update è in funzione
del rioridnamento che si è deciso di fare
(la procedura di riordino l'ho messa lato server
con stored procedure)
 > considera il fatto di copiare i records chiusi
 > su una tabella "storico" e successivamente toglierli da quella
 > principale in modo da lavorare con un ragionevole numero di records.
l'idea di base era proprio quello di trovare un compromesso
che mi consentisse di evitare l'utilizzo di una tabella storico
dove mettere tutte le lavorazioni terminate
così facendo per fare delle ricerche un po' articolate tra
lavorazioni "da fare", "in esecuzione" e "terminate"
dovrei andare ad utilizzare una UNION
Cordialmente,
Stefano

 dati e quindi andrai + veloce.
nel mio caso sarebbe il contrario
1^a select su detail
2^a select su master
comunque prenderò in considerazione anche questa tua osservazione
Cordialmente,
Stefano