![]() |
Datenbank: mssql • Version: 2008 • Zugriff über: unidac
Große Datenmenge in LookupCombo zur Verfügung stellen
Hallo,
ich stelle den Benutzern eine DBLookupComboBox zur Verfügung in der 1.4 Mio Datensätze zur Auswahl stehen. In der hinterlegten Query werden nur 3 der 30 Felder der DB-Tabelle geführt (ID, Serien-Nr., Typ). Warum alle Daten: Das Kunden-Netz ist teilweise sehr langsam so dass Echtzeitfilter zu Verzögerungen führen Manchmal will der Nutzer ab der aktuellen Serien-Nr noch ein paar Datensätze vor oder zurück blättern. Sobald die Daten geladen sind, funktioniert das Ganze auch einwandfrei und sehr schnell. Aufgabe: Wie kann ich die Zwangspause beim Laden der Daten beim Start verhindern Wie kann ich die Daten hin und wieder im Hintergrund ohne für den User bemerkbare Aussetzer aktualisieren? Lösungsansatz: Laden und Aktualsierung erfolgt in einem Thread Meine Frage: Zuerst dachte ich es sei eine gute Idee die Daten in einer UniQuery in einem Thread abzurufen und dann an eine VirtualQuery zu übergeben. Beim Test habe ich dann aber festgestellt, dass das UniQuery.Open auf meinem Rechner 1s dauert und das VirtualQuery.Open dann 7s. In beiden Fällen ist FetchAll = True. Im Kundennetzt dauert das Ganze um einiges länger. Wie müsste Execute/Synchronice gestaltet werden, dass die Daten ohne spürbare Verzögerung an den Main-Thread übergebe werden und der User u.U. im Mainthread in den Daten gerade blättert. Ich habe dazu schon einige Foren durchsucht aber nie eine funktionierenden Lösungsansatz gefunden. Wenn einer eine gute Idee hat, wäre ich sehr dankbar. Gerd |
AW: Große Datenmenge in LookupCombo zur Verfügung stellen
Hallo,
Zitat:
Die MB's an Daten müssen ja erst mal übers Netz zu Dir kommen. Ich würde ein eigenes Fenster zur Auswahl bauen und dort muss mindestens ein Filterkriterium eingegeben werden. |
AW: Große Datenmenge in LookupCombo zur Verfügung stellen
Bei 1,4 Mio. Datensätzen macht es für den Benutzer überhaupt keinen Sinn, dass diese bereits vor dem ersten Zeichen zur Verfügung stehen. Je nach Aufbaue der Lookup-Daten wartest du also mal mindestens eine gewisse Anzahl von Zeichen ab, bevor du überhaupt etwas als Lookup zur Verfügung stellst.
Kein User dieser Welt würde durch 1,4 Mio. Lookupwerte blättern. Wenn die Seriennummer z.B. folgendermaßen Aufgebaut ist: 1-123-45678 1-123-45679 1-123-45681 1-124-56789 könntest du nach Eingabe der 1 den Bindestrich automatisch setzen, und die 123 und 124 als Lookup laden. Nach Eingabe oder Auswahl von 123 setzt du wieder den Bindestrich automatisch und lädst dir 45678, 45679 und 45681 ins Lookup (kann man ja ober Group by ganz gut lösen). Das ganze vielleicht über eine Thread, so dass möglichst wenig Beeinträchtigung in der Gui entsteht. Wenn die Seriennummern nur aus Zahlen besteht, dann könntest du das natürlich ähnlich machen. Eine andere Möglichkeit wäre, wenn das Datenbankdesign in deinen Händen liegt, jede Seriennummer mit einer Versionskennung zu versehen. Das könnte ein Int oder ein Timestamp sein, aber auch ein Autoinc-feld. Beim Start des Programms lädst du jetzt alle 1,4 Mio. Datensätze per Thread bereits im Hintergrund ein, merkst dir aber den höchsten Int, Timestamp oder Autoinc-Wert. Kurz vor der dem Bestücken des Lookups schaust du noch mal kurz in die DB und lädst alle Datensätze nach, die eine höhere Versionskennung haben, also seit dem STart des Programms in der Tabelle hinzu gekommen sind. Das dürfte wesentlich weniger sein. Die Pflegst du dann in deine bereits geladenen ein. Diese Möglichkeit ist natürlich bei 1,4 Mio. Datensätze sehr Speicherlastig. So mache ich das in einem Programm, in dem alle Kundennummern zur Verfügung stehen sollen. Sind aber keine 1,4 Mio. und ich mache das, weil ich die auch bei Unterbrechung der DB-Anbindung zur Verfügung haben will. Gruß Hobbycoder |
AW: Große Datenmenge in LookupCombo zur Verfügung stellen
Zitat:
Zitat:
Zitat:
|
AW: Große Datenmenge in LookupCombo zur Verfügung stellen
Häng noch ein LIMIT 1000 an und schon wird es auch nie zuviel.
Dann kannst du die Daten auch problemlos im Haupthtread laden, ohne dass man zu lange warten muß. z.B. erst ab 3 Zeichen automatisch laden (mit Enter sofort) Und das Laden nicht sofort starten, sondern einen Timer 750ms bis 2500ms beim OnChange/KeyPress starten/restarten und bei Ablauf dann erst laden. So kann der User auch mal schneller was eintippen und wird nicht sofort ausgebremst, bis er selber ne "längere" Pause macht. Kommt aber auch darauf an, wie schnell die Daten geladen werden. Man könnte auch alle Daten laden und dann nur noch über einen ClientFilter begrenzen. Oftmals ist es ja so, dass die Daten mindestens genauso schnell da sind, wie für die aktualisierung der GUI brauch, inkl. einer Synchronisierung, wenn das Datenladen im Thread ablief. |
AW: Große Datenmenge in LookupCombo zur Verfügung stellen
Also beim Programmstart würdest du die 1,4 Mio. Datensätze erst mal einlesen. Das würde natürlich auch zu einer Beeinträchtigung der GUI führen, deswegen wäre es auch dort sinnvoll das ganze über einen Thread zu lösen.
Hier mal ein Threadgerüst (ist nur so zusammengekloppt)
Delphi-Quellcode:
aufruf:
unit LoadLookupDataThread;
interface uses classes; Type TOnNewSernr=procedure(sender: TObject; SerNr: string) of object; TOnThreadFinished=procedure(sender: TObject; MaxID: integer) of object; TLoadLookupData=class(TThread) private FDBHost, FDBUser, FDBPass: string; FLastVersionnr: integer; FOnNewSernr: TOnNewSernr; FOnTheadFinished: TOnThreadFinished; procedure DoOnNewSernr(Sernr: string); procedure DoOnThreadFinished(MaxID: integer); public constructor Create(suspended: boolean; DBHost, DBUser, DBPass: string; LastVersionNr: Integer); protected procedure Execute; override; published property OnNewSerNr: TOnNewSernr read FOnNewSernr write FOnNewSernr; property OnThreadFinished: TOnThreadFinished read FOnTheadFinished write FOnTheadFinished; end; implementation { TLoadLookupData } constructor TLoadLookupData.Create(suspended: boolean; DBHost, DBUser, DBPass: string; LastVersionNr: Integer); begin inherited Create(suspended); Self.FreeOnTerminate:=True; Self.NameThreadForDebugging('LoadLookupDataThread'); FDBHost:=DBHost; FDBUser:=DBUser; FDBPass:=DBpass; FLastVersionnr:=LastVersionNr; end; procedure TLoadLookupData.DoOnNewSernr(Sernr: string); begin if Assigned(FOnNewSernr) then Synchronize(procedure begin FOnNewSernr(self, Sernr); end); end; procedure TLoadLookupData.DoOnThreadFinished(MaxID: integer); begin if Assigned(FOnTheadFinished) then Synchronize(procedure begin FOnTheadFinished(self, MaxID); end); end; procedure TLoadLookupData.Execute; var FDBConnection: TDBConnection; FDBQuery: TDBQuery; MaxID: integer; begin MaxID: FLastVersionnr; FDBConnection:=TDBConnection.Create(nil); FDBQuery:=TDBQuery.Create(nil); try FDBConnection.Host:=FDBHost; FDBConnection.User:=FDBUser; FDBConnection.Password:=FDBPass; FDBConnection.connect; if FDBConnection.connected then begin FDBQuery.Connection:=FDBConnection; FDBQuery.SQL.Text:='Select Sernr, Id from Seriennummern where ID>:id'; FDBQuery.Params.parseSQL(FDBQuery.SQL.Text, True); FDBQuery.Params.paramValue['id']:=FLastVersionnr; FDBQuery.Active:=True; while not FDBQuery.Eof do begin DoOnNewSernr(FDBQuery.Fieldbyname('Sernr').AsString); if FDBQuery.Fieldbyname('id').asInteger>MaxID then MaxID:=FDBQuery.Fieldbyname('id').AsInteger; FDBQuery.Next; end; FDBQuery.Active:=False; end; finally FDBConnection.Free; FDBQuery.Free; DoOnThreadFinished(MaxID); end; end; end.
Delphi-Quellcode:
Bevor nun die Form angezeigt wird, wo dann das Lookup benötigt wird, rufst du den Thead erneut auf, übergibts dann aber mit LastID eben die letzte zurückgelieferte LastID.
var
LastID: Ingeger=-1; //irgendwo definieren procedure TForm1.LadeLookupDaten; var tld: TLoadLookupData; begin ComboBox1.Items.clear; tld:=TLoadLookupData.create(false, Hostname, Username, Password, MaxID); tld.OnNewSernr:=NewSernr; tld.OnThreadFinished:=Einlesenfertig tld.Resume; end; procedure TForm1.NewSernr(sender: TObject; Sernr: string); begin ComboBox1.Items.Add(Sernr); end; procedure TForm1.Einlesenfertig(Sender: TObject; MaxID: integer); begin LastID:=MaxID ShowMessage('Alle Lookup-Daten eingelesen'; end; Ist natürlich nur ein Beispiel, dass du jetzt nach deinen Anforderungen anpassen musst. Und die Lookupdaten solltest du natürlich sortieren, sonst nützt das blättern natürlich auch nichts, dass ansonsten ja alle neuen Sernr hinten angefügt würden. |
AW: Große Datenmenge in LookupCombo zur Verfügung stellen
Hallo Hobbycoder,
ja, genau. So was in der Art suche ich und hatte ich auch schon dran rum probiert. Ich hatte allerdings gehofft eine effizientere Methode zu bekommen, wie ich die Daten übertragen kann. Mit Next durch die Daten zu blättern und jeden Wert einzeln zu syncen dauert ziemlich lang. Bei mir ca 12s für 100000 Sätze. Ich selber hatte es mit einer typisierten Listen gemacht. Dauert aber immer noch 7s / 100000. Kann man die Daten aus dem Query nicht noch effektiver übergeben? Und dann später auch neu Datensätze anhängen? Wobei hier das einzeln übertragen nicht mehr ganz so tragisch wäre. Grüße Gerd |
AW: Große Datenmenge in LookupCombo zur Verfügung stellen
Zitat:
Gibt es einen eindeutigen Index? Wenn ja dann zeige einen Datensatz an, soll zum nächsten gesprungen werden dann starte eine neue Abfrage die Dir einen Datensatz liefert dessen Index > dem gerade angezeigten ist, Rückwärts analog... Jede dieser neuen Anfragen wird wenige Millisekunden brauchen von denen der Bediener nichts mitbekommt. Ciao Stefan |
AW: Große Datenmenge in LookupCombo zur Verfügung stellen
Der Trick einer SQL-Datenbank ist ja, dass man nur das holt, was man auch anzeigen will.
Bei mir passen in das Pulldown einer Compobox wenn sie sehr weit oben oder unter am Bildschirm ist, vielleicht 100 Zeilen. Du kannst dir ja ein Control schreiben, dass über entsprechende Events verfügt, welche dann neue Datensätze anfordern, wenn der Benutzer an die oberen oder unteren Grenzen der vorhandenen Daten stößt, und diese dann passend einbauen. Bei 100 Datensätzen wären das laut deiner Rechnung ja nur 12ms. Wie gut und flüssig das dann funktionieren würde weiß ich so nicht, darüber müsste man mal nachdenken. Vielleicht sogar mit einem unteren/oberen Cache. Aber vorstellen könnte ich mir das schon. Allerdings würde ich das wirklich nur dann machen, wenn es keinen anderen Ausweg gibt. Interessant wäre wirklich mal, warum nicht erst einige Zeichen eingegeben werden können, bevor du die Daten lädst. Bzw. was du damit am Ende erreichen willst. |
AW: Große Datenmenge in LookupCombo zur Verfügung stellen
Liste der Anhänge anzeigen (Anzahl: 1)
Ich verstehe nicht warum die Diskussion immer wieder dahin abgleitet ob es sinnvoll ist so viele Daten zu laden. Ich habe nie gesagt, dass irgendjemand durch 100000 oder gar 1.4 Mio Datensätze blättern will.
Als ich das Pulldown vor 30 Jahren eingeführt habe, hat man in SAP noch mühsam über Filter gesucht. Vor ca. 10 Jahren hat Google angefangen nicht nur das anzuzeigen was genau dem Suchbegriff entspricht sondern eine "unscharfe" Suche. Um dies auch bei langsamem Netz zur Verfügung zu stellen, lade ich eben alle Nr. Das "verschwendet" ca. 135MByte. Zugegeben nicht ganz wenig aber wenn ich seh was Office-Anwendungen so verbraten habe ich kein schlechtes Gewissen und viele der Benutzer sind froh, dass sie auch schnell mal 2 Nummern zurück blättern können. Klar müssen die Daten auch übers Netz, aber nur ein mal. Danach würde ich nur noch die geänderten Daten nachladen und die halten sich sehr in Grenzen. Wenn ich laufend rund ums Tipergebnis lade ist komm ich über dne Tag gesehen wohl auf ähnliche Ergebnisse. Und wenn ich meine Netzload mit der von SAP vergeiche, die in derselben Firma eingesetzt wird, dann brauch ich mir wenig Gedanken machen, ob ich mehr optimieren muss. Ein Beispiel ist in dem angehängten Bild zu sehen, wo das SQL nicht ganz einfach wäre um auch die Nummern anzuzeigen die vor dem ersten eingetippten Buchstaben H stehen. Und da die Serien-Nr. vielen unterschiedlichen Systematiken unterliegen, weiß ich nicht was direkt vor H kommt. Ausser ich ladem mal sehr großzügig um H herum. Es geht aber auch gar nicht um 1.4 Mio Datensätze sondern darum wie ich den Inhalt eines Queries schnell und threadsafe an den Maintread übergebe. Und da ist das satzweise übergeben nicht die schnellste Lösung. Wie gut die Lösung letztlich ist, lässt sich eben am einfachsten mit vielen Daten testen. Wenn die schnell gehen dann sind wenig Daten absolut kein Problem. Zur Bearbeitung lade ich immer nur den Datensatz, den ich tatsächlich brauche. |
Alle Zeitangaben in WEZ +1. Es ist jetzt 04:32 Uhr. |
Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz