Newsgroup: it.comp.lang.delphi
Titolo: Gestionali e accesso a database

Salve a tutti,
sto cercando informazioni sulle pratiche comuni nell'accesso a database per applicazioni di tipo gestionale.
Sto utilizzando Firebird con dbexpress e ClientDataSet.
So che non è opportuno caricare molti dati nei ClientDataSet per vari motivi e che generalmente conviene limitare i dati iniziali a partire dall'SQL ma come vi comportate con i campi di lookup e la ricerca?
Mi spiego meglio:
ho una tabella Articoli che ha chiavi secondarie che puntano ad altre tabelle come Marche e Categorie. 
Questa tabella, a regime, conterrà anche articoli terminati e che per ovvi motivi verranno nascosti nella visualizzazione del magazzino.
Se volessi dare la possibilità all'utente di filtrare (su cds) i dati per "NomeMarca" e "NomeCategoria" dovrei recuperare quei campi a partire dal dataset iniziale (con una join) ma se l'utente cambia uno di quei campi tramite il campo di lookup sono costretto a ricaricare il CDS oppure ad aggiornare da codice il corrispondente campo fittizio ottenuto dal join. 
Altra soluzione è effettuare il filtraggio sempre in sql sul dataset ma l'operazione è decisamente più lenta e peggiora al crescere dei dati.
In realtà ci sarebbe una terza soluzione: usare come chiave il campo normalmente usato per il lookup e quindi niente ID numerici: l'aggiornamento del campo di lookup sistemerebbe direttamente il dato visualizzato e non ci sarebbe bisogno della join ("funziona" ma non mi piace granché).
Voi, generalmente, che approccio usate?


regola n. 1 EVITARE I LOOCKUP!!!!!!
regola n. 2 leggere non piu 30 di record alla volta
regola n. 3 le tendine non usare i component DBcombo ma i combo normali 
e riempirli dal dataset (insomma gestirli)
regola n. 4 le griglie solo per la visualizzazione con delle query in 
join e mai piu di 30 record alla volta
evitare i client dataset
usare le transazioni
 
questo per un buon C/S ma se preferisci un 3 livelli allora cambiano un 
po di cose ma il principio di far girare poca roba sul client e sulla 
rete...vale sempre....
 
certo.... se però il tuo target è il droghiere sotto casa... allora....
 

 ma se l'utente cambia uno di quei campi tramite il campo di lookup sono 
 
Se parli di oggetti TLokupcombobox: DIMENTICALI.
Se una combobox mostra piu di 20 elementi E' SCOMODA: meglio aprire una griglia di selezione.
 
 
A.


Ciao grazie per la risposta,
intendevo proprio il fatto di usare quei componenti per aggiornare il campo ID e dover aggiornare il corrispondente campo recuperato dalla join (o rifare la query).
Come gestisci la combobox in maniera manuale? Cioè so cosa bisogna fare ma nel caso in cui la relazione venga stabilita tramite un ID numerico, come fai ad associare gli ID ai campi di lookup caricati? Ti crei una struttura dati dedicata per ogni combobox? Oppure hai relazioni senza ID numerici?


Ciao grazie per la risposta,
se la query tira fuori molti più record, utilizzi un "first 30" oppure l'incremental fetching del CDS?
Così dovrei evitare completamente i filtri perchè nel primo caso non avrebbero senso e nel secondo causerebbero il fetching completo della query.
Tuttavia con pochi risultati la ricerca può essere effettuata direttamente tramite sql con prestazioni simili ai filtri quindi il problema si risolverebbe automaticamente.
Per quanto riguarda la scelta dei ClientDataSet, ho preferito limitare per quanto possibile l'accesso al disco e creare qualcosa adattabile ad esigenze superiori a quelle del droghiere sotto casa...


mi correggo, anche limitando il numero di record una query sarà sempre più pesante su una tabella corposa rispetto al filtraggio applicato su un CDS già caricato...

 allora....
 
allora gli fai un gestionale desktop con Paradox, e ci metti 1/5 del
tempo :)
 
-- 
Alessandra
 
Cristiano: seguace degli insegnamenti di Cristo solo finché non
intralcino i peccati a cui si dedica più volentieri - Ambrose Bierce


Ti carichi la lista delle categorie in un dataset a parte, in modo da
poter popolare griglie o combobox per filtrare, eventualmente tenendone una
sola copia come cahce invece di caricarla ogni volta (dipende da quanto
spesso cambia la tabella categorie e da quanto e' grande)
 
A quel punto puoi filtrare modificando la query sql in modo che
filtri la categoria richiesta.
 
Se l'utente modifica un articolo cambiando la categoria, in pratica
devi modificare nel CDS TUTTI i campi della categoria (anche la descrizione
eccetera) in modo che i dati siano coerenti.
 
Se hai la tabella in cache di cui sopra la cosa e' abbastanza semplice e non richiede altre query sul DB.
 
Alla fine quando vai a fare la ApplyUpdates, i campi NomeCategoria eccetera
non vengono riportati indietro al DB, ma solo id_categoria di Articolo.
 
ciao
Giacomo
 
 
 
 
 
 

1) il concetto "lookupcombox" io non lo uso da anni, salvo pe rtabelle che hanno pchi, max 20, record
Questo xche sono IO il PRIMO a incazzarmi quando devo inserire un dato e mi tocca scorrere 100 elementi prima di trovare quello che mi serve.
Ovviamente, in base alla legge di murphy, l'elemento che devo selezionare non è mai tra i primi 20 e negli ultimi 20....
 
 
2) l'utente, se deve scegliere, usa una griglia sulla quale DEVE (NON "PUO": DEVE!)  impostare dei filtri. E SE IMPOSTA UN FILTRO POCO RESTRITTIVO LA QUERY NON PARTE. FACCIO UNA CUNT PREVENTIVA E SE LA COUNT E' MAGGIORE DI UN VALORE UMANO, LA APP ALZA IL DITO MEDIO all'utente e gli dice "il filtro restituisce troppi record..."
 
3) la griglia aperta per selezionare NON permette modifiche
Volendo, potrei farlo ma preferisco evitare per non incasinare gli utOnti.
 
4) salvo casi particolari, ogni mia tabella ha un cosntraint primary key basato su un campo non modificbaile dall'utente, tipicamente calcolato tramite una sequence o meccanismo similare.
 
Domanda cattiva:
non e' che nelle tue app PERMETTI MODIFICHE ALLE PRIMARY KEY?
Spero prp di no, per il tuo bene...
 
A.


 
 
Allo stato attuale utilizzo delle sequence incrementate tramite trigger oppure incrementate direttamente da delphi per gli inserimenti di tipo master detail.
Il problema, appunto, è nel caso in cui ci siano pochi record per cui non vale la pena fare una griglia di selezione.


Ciao grazie per la risposta,
ok chiaro il concetto di dover aggiornare tutti i campi della categoria,
ma se sto usando una combobox il riferimento del record nel dataset "a parte" come lo ottieni?
Perchè se sto filtrando direttamente su una griglia il record ce l'ho e ho tutti i dati, ma se ho caricato la pickup list della combobox ho solo un elenco di stringhe e non so da dove prendere l'ID.
Cambio il RecNo del CDS d'appoggio sfruttando l'ItemIndex della ComboBox?

 Cambio il RecNo del CDS d'appoggio sfruttando l'ItemIndex della ComboBox?
 
Se hai la certezza che e' nell'ordine giusto, allora si, altrimenti ti consiglio di usare Locate usando la stringa come valore da cercare (meglio prima
aver costruito un idnice nel cds)
 
Alternative alla combobox con picklist riempita a mano: DBLookupComboBox appoggiato al cds delle categorie, in modo che il cds si posizioni da solo. 
A quel punto visto che la combobox puo' gestire un solo campo alla volta, puoi attaccarti all'evento OnChange del campo del dataset in modifica per copiare quelli che mancano dal cds di lookup
(oppure fai come noi qui che usiamo le combo devexpress in cui puoi dirgli di riportare piu' campi senza dover scrivere codice , ma e' una soluzione un po' costosa :))
 
procedure TMyDM.MyQueryFieldChange(Sender: TField);
begin
  if Sender.FieldName = 'NomeCategoria' then
  begin
    CategorieCDS.Locate('NomeCategoria', Sender.AsString );
    Sender.Datatset('id_categoria'] := CategorieCDS['id'];
    ... eccetera ...
  end;
end;
 
ciao
Giacomo
 


Io sto cercando di capire come affrontare determinate situazioni per una questione di principio. Senza partire dal presupposto di gestire centinaia di client contemporaneamente, se costruisco qualcosa di efficiente per una utenza di 20, 30 client l'eventuale droghiere si troverà bene comunque: è il contrario che sarebbe impossibile...


Perfetto, chiaro. 
In ogni caso se dovessi usare la DBLookupComboBox basterebbe configurarla opportunamente per ottenere l'aggiornamento dell'ID e del campo di lookup in maniera automatica, e per il campo del join da sincronizzare mi basterebbe sfruttare l'OnChange dell'ID (evitando, a seconda dei casi, la locate).
 
Gestendo la combobox non db-aware, invece, la tua soluzione è ideale, anche se probabilmente, assicurandosi di non cambiare l'ordinamento del dataset d'appoggio, il trick del RecNo mi eviterebbe la locate.
 
Grazie ancora.

> 2) l'utente, se deve scegliere, usa una griglia sulla quale DEVE (NON "PUO": DEVE!)  impostare dei filtri. E SE IMPOSTA UN FILTRO POCO RESTRITTIVO LA QUERY NON PARTE. FACCIO UNA CUNT PREVENTIVA E SE LA COUNT E' MAGGIORE DI UN VALORE UMANO, LA APP ALZA IL DITO MEDIO all'utente e gli dice "il filtro restituisce troppi record..."
 
scusami se rispondo nuovamente al tuo post, ma voglio cercare di spiegare meglio il mio problema:
Sto cercando di fare un gestionale che vada bene prima di tutto con database embedded ma che sia scalabile per un numero maggiore di utenti. Più che altro voglio trovare un sistema da adottare in generale e non solo per questo progetto.
Riguardo il concetto di "filtrare", ho capito come ridurre al minimo il numero di record ma facendo vari "stress" test (ho caricato 100.000 record nella tabella articoli) ho notato come l'esecuzione della query ovviamente rallenta di molto.
D'altra parte applicare un filtro (intendo proprio la proprietà Filter) sul CDS è molto più veloce e la differenza si nota anche con 1000 record. Il filtro richiede, però, di caricare l'intero CDS in memoria.
Da qui la domanda, che approccio scegliere?
1 - caricare i cds all'avvio ed evitare, per quanto possibile, i refresh
  PRO: ricerca tramite filtri e/o range, molto veloce (fino a 5000 record è praticamente istantanea)
  CONTRO: caricare il cds all'inizio facendo una query più generale (pesante) possibile (ad es. caricare l'intero magazzino); in modalità c/s ci sarebbero trasferimenti dati esagerati
2 - caricare i cds ad ogni ricerca, effettuando il filtraggio tramite sql
  PRO: più efficiente in c/s
  CONTRO: più lento nella ricerca (voi usate cappare le query con "first X" nell'sql?)
Scusate se mi sono dilungato, ma amlo suggeriva di non usare proprio i ClientDataSet e quindi mi sto facendo problemi esistenziali :D

 Voi, generalmente, che approccio usate?
 
Tempo fa avevamo discusso delle performance di firebird.
Avevo poi prodotto un piccolo programmino di test per verificare che, 
caricata l'intera divina commedia, una ricerca FULL TEXT durava meno di 
mezzo secondo.
Questo per dirti che alla fine io proverei a spostare il carico di 
lavoro direttamente al DB, con query di ricerca full text del tipo LIKE 
'%frammento di stringa%'
 
Ovviamente il db va ottimizzato con opportuni indici, anche se non sono 
del tutto necessari (il lookup sui 33mila versi della divina commedia 
non usava indici).
 
 
-- 
morde
Android
QT 4.7
Delphi (5,6,7.. expired)
Firebird Database
 

 per le griglie, che vanno limitate.
 
Ormai lo standard de facto di tutti i combo è quello di incorporare i 
lookup.


 Se una combobox mostra piu di 20 elementi E' SCOMODA: meglio aprire una griglia di selezione.
 
Oggi si usano i lookup con la ricerca integrata, e cosa ci si mette a 
caricare 10mila record in memoria?
 
Io lavoro su una app client server che esegue una select * su una 
tabella di dominio, e si ciuccia 25mila record in un secondo.
Il db è firebird, il client è nel mio ufficio, il server è una macchina 
virtuale che si trova su internet (a 50 km di distanza geografica).
 
Il cliente deve poter fare dei filtri e delle ricerche su tutto il 
dominio, e caricare tutto in memoria all'inizio è una cosa dovuta e non 
costosa.


 mezzo secondo.
 
Me lo ricordo, ma non ricordo un dettaglio importante: come implementavi la 
ricerca full text in firebird?
 
Enrico

 Scusate se mi sono dilungato, ma amlo suggeriva di non usare proprio i ClientDataSet e quindi mi sto facendo problemi esistenziali :D
 
Carica tutto in memoria, non riesco a crederci che 100 mila record da 
una sola tabella un CDS non se li succhi in un secondo..
Hai creato degli indici ragionevoli?
 
Se non li conosci già, documentati ed usa i PLAN di firebird: con quelli 
sai esattamente come viene costruito ogni risultati da query e puoi 
intervenire eliminando i colli di bottiglia.
 
Voglio farti un esempio:
Se faccio una select * from tabella order by CAMPO e CAMPO non è chiave, 
il PLAN ti dirà che verrà eseguito il sort su tutti i record prima di 
rispedirti il resultset:
PLAN SORT ((A NATURAL))
 
Se invece metti l'indice sul CODICE vedrai che il plan trova l'indice e 
lo usa, velocizzando al massimo la query:
PLAN (A ORDER IDX_G_SCHEDA_MANUTENZIONE)
che significa che il DB trova e usa un indice che ho creato opportunamente.
 
Riassumendo: con un DB serio il problema di caricare 100 mila record da 
una singola tabella in un CDS proprio non riesco a vederlo..
 
E' chiaro che sto generalizzando, perchè se per caricare 100 mila record 
fai 5 outer join,allora il problema è sulla tua data logic, non sul db.


 ricerca full text in firebird?
 
usavo SQL e la clausola LIKE, a quanto mi ricordo.
vado a ripescare il post e ti dico.


Cerco di spegarmi meglio.
 
Caricare cose all'avvio, salvo casi particolari, E' FOLLIA.
L'utente deve scegliere un articolo tra 100.000? Bene.
TEdit con accanto un bel pulsante.
Al click del pulsnate si apre una griglia.
La grgilia NON MOSTRA ALCUN RECORD.
L'utente DEVE impostare dei criteri di filtro usando dei tedit o altro.
Poi preme un tasto.
Alla pressione del tasto viene generato uno primo statement sql.
Questo primo statement sql fa select count(1) from articoli 
usando delle where costruite usando i valori inseriti dall'utente.
Se la count restuisce più di un numero SENSATO di righe, l'utente si vede un bel msg "ti fotti! devi impostare condizioni di filtro piu' restrittive".
Se la count restituisce un numero sensato di righe, viene generato
uno statement sql select articoli.* from articoli join etc che usa le stesse condizioni where usate per calcolare la count preventiva.
A quel punto la query si apre e magicamente la griglia si riempie.
L'utente fa doppio click sulla riga e la finestra si chiude portando "fuori" i dati dell'articolo selezionato.
La finestra che mostra la griglia ovviamente (ma non per tutti) e MODALE.
 
 
Giusto per essere ancora piu' chiaro:
 
 
ribadisco che caricare le cose all'avvio, salvo casi molto particolari, E' SBAGLIATO.
 
Quando parlo di filtro non parlo delle property di TDataset ma dell'insieme di dati che l'utente DEVE inserire per vedere qlc fottuto record in una fottuta griglia. Quindi il msg e' "dimentica le property filter, filtered etc di tdataset!"
 
la griglia di selezione e' a sola lettura.
L'utente non puo' fare modifiche ai dati.
 
le combobox vanno bene fino a max 20 elementi.
Se sono di piu' NON VANNO USATE.
Se oggi sono 20 ed esiste una minima possibilita che diventino 30, la combobox non la usi.
Ovviamente, se dvi scegliere un mese, essendo i mesi 12, la como puo andare.
 
 
Riguardo il lientdataset..
1) se usi ADO puoi farne a meno
2) se usi DBX, almeno con d7, DEVI USARE un cds se vuoi mettere i record in una griglia. il xche e' spiegato indovina dove??? :-D
 
 
 
Aggiungo che calcolare gli ID da trigger non va bene.
Adesso rifletti e dimmi tu xche.
 
Infine, qlc domanda giusto per aiutarti a cmabiare prospettiva...
 
1) forse quando digiti 
google.it
 lui si carica 40 miliardi i siti in una lista e poi ti fa fare la ricerca?
 
2) forse quando vai su trenitalia per cercare un treno il sito si carica tutto l'elenco di tutti i treni con eventuali cambi e poi ti fa fare la ricerca?
 
 
Infine, 100.000 record per me non sono uno stress test.
Ho svilupapto un aggeggio anni fa che inseriva in tabella
50.000 RECORD AL GIORNO.
Non e' un iperbole. intendo prp 50.000.
 
 
A



https://groups.google.com/d/msg/it.comp.lang.delphi/lXt0QlG2zh0/InOsoY_5PYkJ
 



ciao grazie per la risposta,
ecco una cosa che non ho fatto è generare gli indici direttamente sul db
cioè ho lasciato quello sulla chiave primaria e per questioni di ordinamento in delphi ho creato indici sul cds che ovviamente non influenzeranno mai la query
quindi la differenza di prestazioni tra rifare la query e usare la proprietà filter del cds probabilmente si assottiglierà di molto...


Avevo capito la questione della ricerca e delle combobox solo che banalmente riscontravo differenze >notevoli< con il "filter"
E con dbexpress i cds sono obbligatori perché i dataset sono unidirezionali, la domanda era riferita all'eventuale possibilità di usare qualcosa di diverso da dbexpress
Calcolare gli ID da trigger è scomodo perchè ovviamente non riesco da delphi a capire che ID Firebird ha deciso di mettere finché non aggiorno i dati. Ho detto "per adesso" proprio perché sto valutando, in generale, quanto è pesante fare il refresh dei dati...


Ho generato gli indici ed effettivamente le prestazioni sono migliorate.
Scusa se ne approfitto ma a questo punto ho altre domande :D
1 - se la query è una join, devo generare indici su tutti i campi delle altre tabelle o solo sull'ID? È più una curiosità visto che in realtà li creerò comunque per altre query di ricerca. A occhio credo che servano sui campi usati nella where o per la join indipendetemente da quelli selezionati, giusto?
2 - È accettabile un tempo di 0,160 sec per una join (senza clausola where) su una tabella di circa 5000 record con altre 4 tabelle che però hanno solo 3, 4 record? Appena metto qualcosa nella where arrivo a tipo 0,032. Praticamente gli stessi tempi del filter diretto sul cds :D
3 - Pensavo di creare un filtraggio tramite un tokenizer per generare la clausola where in maniera da fare qualcosa tipo: 
((Campo1 LIKE '%parola1%') or (Campo2 LIKE '%parola1%') or (Campo3 LIKE '%parola1%')) and
((Campo1 LIKE '%parola2%') or (Campo2 LIKE '%parola2%') or (Campo3 LIKE '%parola2%'))
diventa troppo pesante così?
4 - Se il cds fa anche da Master in una relazione Master-Detail definita tra i cds, la query ne risente? Oppure il detail viene calcolato solo sul record master corrente?
 
basta così, accetto insulti da tutti per la rottura di maroni di oggi :D

 diverso da dbexpress
 
cioe'?
 


Ho letto sul ng che c'è ZeosLib di cui non avevo mai sentito parlare e che può interfacciarsi con db Firebird.
Immaginavo che forse ci fossero alternative valide che non utilizzavano i cds, tutto qui.

dbx ha l'enorme vantaggio di usare cursori unidirezionali e quindi molto veloci.
Poi, se ti serve mostrare i dati in una griglia che richiede la bidirezionalita' usi i cds.
ma xche sei cosi contrario ai cds? :-|
 
A.


puoi usare anche le ZeosLib (Rel. 6.6.5 stable o 7.0.0 dev), le ho usate 
di recente per agganciare un Db Oracle XE da D2007 senza riccorrere alle 
DevArt o simili (costano circa 399 Euri..., il cliente conosce solo una 
parola ... agratis ..., ed allora beccati le Zeos).
 


No ma non sono contrario :D
Quando ho scritto quel messaggio non avevo creato tutti gli indici come suggerito da Morde dopo, quindi riscontravo una differenza evidente sulla velocità della query rispetto al filter diretto sul cds.
Per questo, siccome amlo aveva suggerito di non usare i cds mi è venuto il dubbio che dbexpress + cds avesse al massimo quelle prestazioni ed ero aperto ad eventuali alternative

 basta così, accetto insulti da tutti per la rottura di maroni di oggi :D
 
Più che altro era importante che capissi che puoi lavorare con la 
potenza del database, invece che spostare logiche di fetching ecc lato 
applicativo. Io userei questi approcci applicativi solo e solo se con il 
DB non riesci a risolvere i problemi del lookup.


 alternative valide che non utilizzavano i cds, tutto qui.
 
Ciao,
uso da un bel po' l'accoppiata firebird+zeoslib con una certa 
soddisfazione, se può esserti d'aiuto.
un saluto e buon lavoro!
 
-- 
Emanuele Desideri
 
 

> Quando ho scritto quel messaggio non avevo creato tutti gli indici 
 
Se non hai le basi di come funziona un database non vai da nessuna parte.
Lato applicativo, puoi avere la migliore architettura possibile con il migliore strumento di sviluppo con ottimizzazioni spinte...
Ma tutto cio' cade nel vuoto se il db e' progettato in modo cinofallico.
Adesso sei tornato sulla retta via.
Prima eri fuori strada: cercavi di risolvere il problema lato applicativo quando il PRIMO problema era lato db....
 
A.
 


Grazie a te e Maurizio, 
per adesso avendo migliorato le prestazioni delle query preferisco restare su dbexpress e cds
in futuro magari farò qualche prova con le zeoslib


Ok grazie mille!

 beccati le Zeos
 
l'ultimo che ho beccato che conosceva solo quella parola, pur di non
pagare è tornato a lavorare con la carta (parole sue).. buon per lui.
 
-- 
Alessandra
 
Io sono circondato da preti che ripetono incessantemente che il loro
regno non è in questo mondo, eppure allungano le mani su tutto quello
che possono prendere - Napoleone

 1) forse quando digiti 
google.it
 lui si carica 40 miliardi i siti in una lista e poi ti fa fare la ricerca?
 
E pero', quando fai 'freccia giu`' nella barra indirizzi del tuo
browser, non ti mostra comunque qualcosa? e se digiti delle lettere
raffina via via la ricerca?
E non è roba che presumibilmente ha in memoria?
 
Quanto a google, apparentemente, fa una ricerca ad ogni carattere che
scrivi, presentandoti i 4 candidati più probabili (secondo i suoi
criteri) di quel che cerchi.
 
A me sembra del tutto ragionevole che un utente possa cercare da una
edit + list box anche se non sa cio' che cerca. La funzione è li'
proprio per quello, anzi *soprattutto* per quello.
 
Sta poi alla fase di progetto stabilire i criteri di ricerca e
presentazione dei suggerimenti (ad esempio, presentare solo i 6
risultati corrispondenti alla chiave di ricerca inserita dall'utente
che hanno la massima occorrenza, oppure i più venduti, o i più
ricercati negli ultimi 30 giorni). Di più, la ricerca dovrebbe essere
estensiva; ad esempio, se hai un codice e una descrizione, nel lookup
dovresti avere i risultati della ricerca operata sia sul codice sia
sulla descrizione.
Ovviamente va valutato l'effort necessario all'implementazione, ma
davvero, l'aspetto delle prestazioni lo lascerei perdere.
 
Se poi sia più facile farlo in python (o in jscript, o in ajax, o in
php) che in Delphi, o con sqlite piuttosto che con firebird è da
vedere.
 
Saluti
Paolo Arosio