Newsgroup: it.comp.lang.delphi
Titolo: [Mysql<--dbExpress] Inserimento e campo auto increment

ciao a tutti
uso Mysql 4 con dbExpress per un'applicazione Client/Server.
Devo fare l'inserimento di un record di una tabella "detail"
imagginate una situazione (molto comune)
table anagrafiche(
idAnagrafica autoincrementate,
nome stringa,
cognome stringa,
etc ...
)
table clienti(
idCliente autoincrementate,
idAnagrafica chiave esterna su Anagrafiche,
TassoSconto currency,
etc ...
)
per inserire un cliente si può presentare la situazione in cui
si deve inserire contestualmente una nuova anagrafica da
associargli.
Passo al mio problema:
quando inserisco la nuova anagrafica, la chiave primaria
(unico campo univoco del record) cioè l'idAnagrafica, viene
assegnato dal db poichè è autoinc,
ecco la domanda ...
come faccio a recuperare il giusto 
idAnagrafica di quell'inserimento?
sempre ricordando che in ambiente client/server
sulla stessa tabella potrei avere 2 inserimenti 
"contemporanei" da utenti/connessioni diverse :(
idee, consigli?
mysql mi fornisce una funzione LAST_INSERT_ID()
che restituisce (a quanto pare) una tabella 1x1 con 
il valore dell'ultimo autoinc incrementato 
(esattamente quello che mi serve, poichè torna 
valori diversi per connessioni diverse)
Eseguendo il comando in consol
Select Last_Insert_id();
torna il valore corretto dell'ultimo id
se invece creo un dataset con 
CommandText := 'Select Last_Insert_id();'
e lo apro con l'open
lo esegue ma torna il valore ZERO
qualcuno ha mai già usato una cosa del genere?
	Ciao
	MassimoB
 
	------------------P.S.[OT]-----------------
	Sto usando un newsreader fatto da me
	per qualsiasi problema di leggibilità
	o non convenzionalità del messaggio
	vi prego di comunicarmelo.
	-------------------------------------------

idee, consigli?
Una rapida ricerca su google groups evidenzia che e' un bug, non ancora
risolto, di mysql.
Da quello che ho capito (sempre leggendo velocemente qua e la') hai un
workaoround sicuro: fare la select max(id) sulla tabella anagrafica in modo da
avere l'ultimo id autogenerato.
In un altro post di uno sviluppatore di mysql trovo invece la seguente
risposta (lascio a te il compito di verificare e di comunicarci come hai
risolto :-))))
citazione da:
http://groups.google.it/groups?&selm=b8gir1%241h27%241%40FreeBSD.csie.NCTU.edu.tw
"It's just 'SELECT last_insert_id()', no 'FROM' clause needed. It returns
the last AUTO_INCREMENT value generated for a query on the current
connection. See 
http://www.mysql.com/doc/en/Miscellaneous_functions.html
for the full description on how to use it.
Mark Matthews 
"
ciao!
-- 
Morde. 
morde[at]programmer[.]net

 connection.
Esatto.
Questa si.
Ma forse sarebbe meglio non usare gli autoinc e assegnare l'id sulla
OnNewRecord del dataset calcolando l'id con un generator/sequence.
Bye
A

ciao!
grazie ancora ...


A
grazie dell'input :)


allora...
Intrebase ha i generator
Oracle ha le sequences.
In sintesi fanno la stessa cosa, cioè fornire un id numerico progressivo.
Attenzione: non un autinc sul quale tu non ha controllo!
in interbase:
CREATE GENERATOR GEN_CLIENTI
Se ad esempio vuoi far partire i tuoi id da 100:
SET GENERATOR GEN_CLIENTI TO 100
poi:
SET TERM!!
CREATE PROCURE MAKEID_CLIENTI RETURNS (AID INTEGER) AS
BEGIN
  AID = GEN_ID(GEn_CLIENTI,1);
END!!
Poi, nel data module della tua app, metti un oggetto stored procedure e gli
associ la procedure di cui sopra.
Infine, nell'evento OnNewRecord del tuo dataset metti
Dataset.FieldByname('idCliente').AsInteger := GetIdCliente
function tTuoDataModule.GetidCliente: integer
begin
  spgetidCliente.ExecProc;
  result := spgetidCliente.ParamBybname('AId').AsInteger;
end;
In questo modo l'id viene calcolato lato server come è giusto che sia, ma
assegnato dalla tua app e quindi visibile ad essa.
Se proprio non ti va di sciupare id, 'sta cosa la puoi fare nella
BeforePost, aggiungendo un check:
if dataset.State = dsInsert then
  Dataset.FieldByname('idCliente').AsInteger := GetIdCliente
Bye
A

Attenzione: non un autinc sul quale tu non ha controllo!
stupendo ... 
infatti sto pensando di spostarmi su postGre 
in quanto mysql non ha ne sequences ne generetors :((
ed in più non ha le stored procedures
sul manuale di mysql ho trovato questa cosa per ottenere
una simil sequences
------------------------------------------------------------------------
mysql> CREATE TABLE sequence (id INT NOT NULL);
mysql> INSERT INTO sequence VALUES (0);
Then the table can be used to generate sequence numbers like this: 
mysql> UPDATE sequence SET id=LAST_INSERT_ID(id+1);
You can generate sequences without calling LAST_INSERT_ID(), 
but the utility of using the function this way is that the ID value is maintained 
in the server as the last automatically generated value (multi-user safe). 
 ----------------------------------------------------------------
il problema è che comunque dovrei fare un lock della
tabella sequence fra l'UPDATE (come sopra) ed il SELECT
che userò per prelevare il valore dalla stessa tabella
ammeno di riusare la funzione Last_insert_id() 
per avere il valore meorizzato sul server (id+1).
ma last_insert_id chiamato da dataset delphi 
(come già detto) mi torna sempre zero ... :((
allora sono "punto e da capo" o mi sbaglio?!?
comunque continurò a provare e a cercare ....
grazie ancora


finalmente ho risolto ...
sul manuale di mysql ho trovato questa cosa per ottenere
una simil sequences
------------------------------------------------------------------------
mysql> CREATE TABLE sequence (id INT NOT NULL);
mysql> INSERT INTO sequence VALUES (0);
Then the table can be used to generate sequence numbers like this: 
mysql> UPDATE sequence SET id=LAST_INSERT_ID(id+1);
You can generate sequences without calling LAST_INSERT_ID(), 
but the utility of using the function this way is that the ID value is maintained 
in the server as the last automatically generated value (multi-user safe). 
 ----------------------------------------------------------------
chiamando "Select last_insert_id()" dopo aver eseguito l'update della tabella
sequence e impostando la prop KeepConnection della sqlConnection a false,
che mantiene una sola connessione per tutte le operazioni,
si ottiene in giusto valore (>0) questo valore rappresenta 
un id ormai "bruciato" per il DB,
e quindi disponibile per un inserimento (multiuser-safe).
Questo perchè l'update successivo di un secondo utente alla tabella sequence restituirà
l'id successivo (id+1). 
Quindi eseguendo l'UPDATE di sequence e leggendo la last_insert_id(),
valore che risiede nella cache della mia connessione e che quindi nessun altro 
mi può toccare, posso avere a disposizione un nuovo id per inserire senza 
problemi il mio record.
In più poichè sono sicuro del suo valore sia prima che 
dopo l'inserimento posso gestire "tranquillamente"
tabelle relazionate etc etc
non so se sono stato comprensibile :)
poi il codice che server è ;)
      { richiede l'ultimo id inserito }
      With dsLastId Do
      Begin
        CommandText := 'UPDATE Sequence SET id=LAST_INSERT_ID(id+1)';
        ExecSQL(true);
        CommandText := 'Select LAST_INSERT_ID()';
        Open;
        { salva l'ultimo id nella proprietà NewId  }
        Self.NewId := Fields[0].Value;
        Close;
      End;
dove dsLastId è un TSqldataSet
di tipo ctQuery
e NewId è una prop del form di tipo Integer.
se non mi sono spiegato o qualcuno 
volesse spiegazioni migliori sono a disposizione ;) 
