Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Sonstige Fragen zu Delphi (https://www.delphipraxis.net/19-sonstige-fragen-zu-delphi/)
-   -   Delphi SQL-Abfrage im Thread und füllen eines ListView (https://www.delphipraxis.net/163428-sql-abfrage-im-thread-und-fuellen-eines-listview.html)

Captnemo 28. Sep 2011 15:02

SQL-Abfrage im Thread und füllen eines ListView
 
Hallo Leute,

ich schlage mich jetzt schon einen ganze Weile mit einem mit einem Problem rum, für das ich jetzt nur noch eine Lösung sehe: Threads.

Hier mal zum Problem. Ich habe eine Art Terminplanungsprogramm in dem man Termine für bestimmte Ressourcen verwalten kann. Wenn ich dann einen Termin aufrufe, werden dort die Termindetails dargestellt und eine Historie für die ausgewählte Ressource angezeigt. Alles Funktioniert fehlerfrei, nur die Abfrage der Historie wird bei längerer Laufzeit des Programms ein Zeitfaktor. Der Grund dafür ist klar, denn wenn dann mit der Zeit so 40000 Termine eingetragen sind, und für die Historie eine SQL-Abfrage abgesetzt wird, benötigt der SQL-Server schon allein 5-7 Sekunden um die Daten bereit zu stellen.
Leider wartet das Prog ja nach dem Query1.active:=True eben auf die Rückmeldung des Query's (ich verwende ZEOS und MySQL) bevor es dann mit dem Füllen des ListView weitergeht. Letzterer Schritt ist dann wieder uneingeschränkt schnell.

Mein einziger erfolgversprechende Lösungsansatz wäre es, die SQL-Abfrage in einen Thread auszulagern, von diesem dann nach dem Anzeigen der Terminform unabhängig von Usereingaben das Listview füllen zu lassen.
So fällt für den User die Wartezeit nicht mehr so deutlich auf, und im Listview könnte man solange ein "Daten werden abgerufen..." darstellen.

So weit so gut. Aber wie fange ich das an. Leider hab ich von Threads recht wenig Ahnung und alle Tutorials und Bespiele beschränken sich leider auf recht simple Dinge wie eine String übergeben oder ein paar Werte zu berechnen.

Meine erste Frage in diesem Zusammenhang, muß ich für einen Thread eine extra Unit verwenden, oder kann ich das in meiner Termin Unit mit unterbringen?

Die zweite Frage wäre, kann ich aus einem Thread einfach per uses mein Datamodul einbinden und auf die Querys und die Connection zugreifen?

Kann mein Thread dann einfach so auf das Listview zugreifen?

Wie gesagt, ich hab von Thread wenig Ahnung, und ein Tutorial was mir diese Fragen beantwortet hab ich leider noch nicht gefunden.

Bernhard Geyer 28. Sep 2011 15:09

AW: SQL-Abfrage im Thread und füllen eines ListView
 
Zitat:

Zitat von Captnemo (Beitrag 1127277)
Der Grund dafür ist klar, denn wenn dann mit der Zeit so 40000 Termine eingetragen sind, und für die Historie eine SQL-Abfrage abgesetzt wird, benötigt der SQL-Server schon allein 5-7 Sekunden um die Daten bereit zu stellen.

Ist die Datenmenge so groß das es so lange dauert? Sollte normalerweis in << 1s möglich sein wenn Bandbreite genügend da ist bzw. Server korrekt konfiguiert. Evtl. nicht alle Detaildaten laden die man eh nicht sofort sieht.

Zitat:

Zitat von Captnemo (Beitrag 1127277)
Mein einziger erfolgversprechende Lösungsansatz wäre es, die SQL-Abfrage in einen Thread auszulagern, ...

Sollte kein Problem sein. Manche DB-Zugriffskompos erfordern das du dann auch im Thread eine eigene Connection hast.

Zitat:

Zitat von Captnemo (Beitrag 1127277)
... von diesem dann nach dem Anzeigen der Terminform unabhängig von Usereingaben das Listview füllen zu lassen

Die Listview wirst du nicht über den Thread füllen können da die VCL bzw. die zugrunde liegenden Win-Controls eine Thread-Affinität haben. Du darfst sie nur im erzeugenden Thread verwenden.

Sinnvoller ist hier die Daten en block/blockweise zurück an die Hauptapp zu geben und dann die Listview im Virtual Modus zu betreiben.

Union 28. Sep 2011 15:15

AW: SQL-Abfrage im Thread und füllen eines ListView
 
Genau, hol doch nur die 10..100 neuesten History-Einträge pro Ressource und mach wenn der Benutzer weiter nach unten blättert ein Refresh auf alle. Oder einen Button "Alle Anzeigen"

webcss 28. Sep 2011 15:16

AW: SQL-Abfrage im Thread und füllen eines ListView
 
Für eine genaue Antwort müsste ich ein wenig in meiner "Krabbelkiste" kramen, aber aus dem Kopf soviel:

Du erstellst deinen nebenläufigen Thread. Dieser *muss* eine eigene Verbindung aufbauen (Database, Transaction + Query!), er darf nicht die Verbindung des MainThread benutzen sonst knallt's.

Den startest Du mit deiner Abfrage und lässt in nebenher dudeln. Wenn das Abfrageergebnis komplett ist, terminiert sich der Nebenthread idealerweise selbst.

Um die Performance zu steigern, solltest du die ListView im VirtualMode (siehe OnData der ListView) einsetzen.

Gruß
Clemens

Captnemo 28. Sep 2011 16:13

AW: SQL-Abfrage im Thread und füllen eines ListView
 
Zitat:

Zitat von Bernhard Geyer (Beitrag 1127278)
Ist die Datenmenge so groß das es so lange dauert? Sollte normalerweis in << 1s möglich sein wenn Bandbreite genügend da ist bzw. Server korrekt konfiguiert. Evtl. nicht alle Detaildaten laden die man eh nicht sofort sieht

Naja, ich hab festgestellt, dass die Abfrage bei 40000 DS schon so an die 4,6 Sekunden rankommt. Das sind die gesammelten Termine der letzten 3 Jahre. In weiteren 3 Jahren sind es schon 80000 DS und ich denke nicht, dass sich der Zeitbedarf linear zu DS-Anzahl verhält. aber das könnte man ja ausprobieren.

Ich habe auch erst gedacht es länge an meiner Anwendung, aber als ich dann direkt mit dem MySQL-Adminstrator die gleichen Zeiten gemessen habe, denke ich mir das es wohl weniger an meiner Anwendung zu tun hat.

So, ich werd das mal probieren mit ein thread.

Aber meine erste Frage ist noch offen. Kann ich in der Terminunit einfach mit
Code:
Type
   MyThread = TThread
   end;
den Thread mit einfügen, oder muß dafür eine eigene Unit existieren?

wie ich die Daten an die Hauptapp zurückgeben weiß ich auch noch nicht, aber einen Schritt nach dem anderen.

jfheins 28. Sep 2011 17:27

AW: SQL-Abfrage im Thread und füllen eines ListView
 
Zitat:

Zitat von Captnemo (Beitrag 1127296)
Aber meine erste Frage ist noch offen. Kann ich in der Terminunit einfach mit
Code:
Type
   MyThread = TThread
   end;
den Thread mit einfügen, oder muß dafür eine eigene Unit existieren?

Ja, das sollte ohne Probleme gehen. Die eigentliche Arbeit übernimmt ja die Klasse.
Zitat:

wie ich die Daten an die Hauptapp zurückgeben weiß ich auch noch nicht, aber einen Schritt nach dem anderen.
Z.B. ein Array of record erstellen (der record enthält schon genau die felder, die später in die Spalten der Listview kommen) und dann das zurückgeben.

Mal ne andere Frage: Was genau dauert lange? Wenn du von den 40.000 Datensätzen einen auswählst, das abrufen der History oder das übertragen der Daten? Ich bin mir ziemlich sicher dass das schneller gehen muss. Vll. keine Indizies gesetzt? Falsche indizes?

Captnemo 28. Sep 2011 17:49

AW: SQL-Abfrage im Thread und füllen eines ListView
 
Tja, was das explizit so lange dauert kann ich dir nicht genau sagen.
Ich habe versucht das ganz mit Indizies versucht, aber ich habe dadurch keine nennenswerten Verbesserungen festgestellt. Ich mir sogar ein View erzeugt, welches nur die Felder enthält (sind glaub ich 6), aber auch wenn ich das abfrage, bekommen ich keine Geschwindigkeitssteigerung.

Die Tabelle dir ich abfrage hat so 49 Felder, davon sind 2 Blob's der Rest String, Int und Datetime. Die beiden Blob's verwende ich einmal für die Ablage von Richtext und das andere für eine kleine Grafik 20x50 Pixel, welche aber nur bei den wenigsten DS tatsächlich gefüllt ist.

In der View sind aber nur Datetime, String und int.

Ich habe auch schon die Speichergrenzen in den Variablen des MySQL-Server angehoben, auch keine nennenswerten veränderungen. Wenn ich einen einzelnen DS über Lfdnr (primärindex) abfrage, geht das sehr schnell.

Ich hab auch schon mal hier im Forum gefragt, wie MySQL den zu verwendeten Index auswähl. Da hieß es, wenn er existiert wird er automatisch verwendet. Ich weiß aber nicht, ob das stimmt.

Beim Thread habe ich so meine Probleme. Da ich im Thread ein eigene Connection- und Query-Object verwenden soll, muß ich diese ja auch erst einmal erzeugen. Das kann aich aber nicht in der procedure Execute machen, sonder eher in einer Create. Gibt's die auch in einem Thread? Wenn ich in D7 über Datei-Neu-Weitere eine TThread-Unit anlege, dann gibt es dort keine Create-procedure. Und wenn ich eine anlege, dann meckert der Compiler.

Grundsätzlich ist mir jede Lösung recht. Ohne TThread wär's an dieser Stelle einfacher. Andererseits würd ich gerne auch mal endlich diesen TThread-Kram verstehen. Steh da irgendwie auf einem Schlauch. :wall::wall:

Sir Rufo 28. Sep 2011 20:41

AW: SQL-Abfrage im Thread und füllen eines ListView
 
Jede (wirklich jede) Klasse hat eine Constructor und der heißt auch eigentlich Create.

Wenn der Compiler meckert, dann, weil die Klasse abgeleitet wurde (die ist immer abgeleitet, min. von TObject) und im Vorfahr etwas anders deklariert war, bzw. du das mit dem inherited oder inherited Create so nicht passt.

Hilfreich dazu ist es sich die Vorfahr-Klasse mal anzuschauen, dann sieht man auch, wie der Constructor da aussieht ;)

Ansonsten kann ich nur mal den Tip geben, im interface-Teil der neu zu erstellenden Klasse mal Strg-Leertaste zu drücken und dann wundern was da so auftaucht :mrgreen:

FredlFesl 28. Sep 2011 21:24

AW: SQL-Abfrage im Thread und füllen eines ListView
 
Wieso musst Du eigentlich alle 40.000 Einträge anzeigen?
Wie ich das machen würde?
Fetch-on-Demand. Ich lade mir 1000 oder so Einträge, und wenn jemand nach unten scrollt und den 1001sten sehen will, lade ich den nächsten Happen ein. Die Happen (1000 oder so) sind so gewählt, das das Laden sehr schnell geht.

Die Query sieht immer gleich aus:
Code:
select first 1000 * from MyView where SortColumn>:LastColumn
Und der Parameter :LastColumn enthält den Wert der Spalte 'SortColumn' des jeweils untersten Eintrags der breits geladenen Daten.

Eigentlich keine große Sache. Kann aber sein, das dann die Query selbst lahm wird (wenn man die DB nicht richtig gepimpt hat).

Medium 28. Sep 2011 22:41

AW: SQL-Abfrage im Thread und füllen eines ListView
 
Wir haben das SQL Statement zwar nie gesehen bisher (das, und die Tabellendefinition wären nicht ganz unnützlich beim Analysieren hier ;) ), aber ich schmeisse zudem auch einfach mal in den Raum, dass Volltextsuchen der Art "LIKE '%substr%'" jeden Index aushebeln, und öfter mal Ursache für Performanceprobleme sind. Zeig doch mal das SELECT, sowie das aus der Tabelle erzeugte CREATE Statement her. 40k Sätze sind eigentlich nicht wirklich eine große Sache, ausser dein Server geht über eine eher mäßig schnelle Verbindung. Danach wurde übrigens auch schon gefragt: Ist der Server lokal, oder eine andere Maschine? Wenn letzteres: Wie ist er an deinen Client angebunden?

Sir Rufo 28. Sep 2011 23:11

AW: SQL-Abfrage im Thread und füllen eines ListView
 
Liste der Anhänge anzeigen (Anzahl: 2)
Ok, hier mal ein Thread, der Datensätze an eine TListView schickt.

Der passt da noch nicht ganz für Dich (kein ZEOS, keine Query) aber das Prinzip bleibt gleich.

Daten lesen und dann immer blockweise an die ListView schicken.
so lange wie der Thread noch läuft per Queue und der letzte Abgleich erfolgt per Synchronize.
(Damit wird sichergestellt, dass alle Einträge auch die ListView ausgeliefert werden)

Delphi-Quellcode:
unit thread.SqlDataToListView;

interface

uses
  Classes,
  ComCtrls,            // TListView
  DB, DBClient,        // TClientDataSet
  Generics.Collections; // TList<T>

type
  TSqlDataToListViewThread = class( TThread )
  private
    FListView : TListView;
    FData :    TDataSet;
  protected
    procedure SendDataToListView( AListView : TListView; AItems : TList<TStrings>; Sync : Boolean = False );
  protected
    procedure Execute; override;

  public
    constructor Create( AListView : TListView; const AFileName : string; CreateSuspended : Boolean = False );
    destructor Destroy; override;
  end;

implementation

uses
  Windows, SysUtils;

{ TGetSqlDataToListViewThread }

constructor TSqlDataToListViewThread.Create( AListView : TListView; const AFileName : string; CreateSuspended : Boolean );
begin
  inherited Create( CreateSuspended );

  FListView := AListView;
  FData    := TClientDataSet.Create( nil );

  with FData as TClientDataSet do
    begin
      FileName := AFileName;
    end;
end;

destructor TSqlDataToListViewThread.Destroy;
begin
  FData.Free;
  inherited;
end;

procedure TSqlDataToListViewThread.Execute;
var
  lItems : TList<TStrings>;
  lItem : TStrings;
  lField : TField;

begin

  // Datenverbindung öffnen

  try
    FData.Open;
  except
    on E : Exception do
  end;

  if FData.Active
  then

    // Wenn die Datenverbindung gesichert hergestellt ist dann können wir ja ans Werk

    try

      lItems := TList<TStrings>.Create;
      try

        while not Terminated and not FData.Eof do

          // Wir machen hier so lange, bis ...
          // ... der Thread abgebrochen wird
          // ... oder alle Datensätze gelesen wurden

          begin

            // Daten in einen Puffer schieben

            lItem := TStringList.Create;
            for lField in FData.Fields do
              begin
                lItem.Add( lField.AsString );
              end;

            lItem.Add( DateTimeToStr( now ) );

            // Daten in die Sammelliste schreiben

            lItems.Add( lItem );

            // Nächster Datensatz
            FData.Next;

            // Wir tun mal so, als ob das hier gaaaanz lange dauert
            Sleep( Random( 15 ) );


            if ( lItems.Count >= 10 ) or Terminated or FData.Eof
            then

              // Wenn der Block voll ist,
              // oder der Thread abgebrochen wurde
              // oder keine Daten mehr zu lesen sind
              // dann die Daten an das ListView ausliefern

              SendDataToListView( FListView, lItems, Terminated or FData.Eof );

          end;

      finally
        lItems.Free;
      end;

    finally
      FData.Close;
    end;
end;

procedure TSqlDataToListViewThread.SendDataToListView( AListView : TListView; AItems : TList<TStrings>; Sync : Boolean );
var
  lItems : TObjectList<TStrings>; // Mal hier schnell geändert, sonst haben wir da ein Speicherleck :o)
  lItem : TStrings;
begin
  if MainThreadID = GetCurrentThreadId
  then

    // Ei jo, wenn wir uns jetzt im MainThread-Kontext befinden,
    // dann können wir ja wieder ganz gemütlich auf das VCL-Gedöns zugreifen

    begin

      if Assigned( AItems )
      then
        begin

          AListView.Items.BeginUpdate;
          try

            for lItem in AItems do
              begin
                with AListView.Items.Add do
                  begin
                    Caption := lItem[0];
                    lItem.Delete( 0 );
                    SubItems.Assign( lItem );
                    SubItems.Add( DateTimeToStr( now ) );
                    SubItems.Add( BoolToStr( Sync, True ) );
                  end;
              end;

          finally
            AListView.Items.EndUpdate;
          end;

          AItems.Free; // ** Hier ist das Free, und ...
        end;

    end
  else
    begin

      // Kopieren der übergebenen Daten-Liste
      lItems := TObjectList<TStrings>.Create; // ** ... hier das Create ... verkehrte Welt :o)
      for lItem in AItems do
        begin
          lItems.Add( lItem );
        end;

      // übergebene Daten-Liste leeren (da schreibt der Thread ja wieder neue Daten rein)
      AItems.Clear;

      // Jetzt rufen wir uns selber nochmal auf, aber ...
      // 1. mit der kopierten Liste
      // 2. im MainThread-Kontext (Synchronized oder Gequeued)

      if Sync
      then
        Synchronize( procedure begin SendDataToListView( AListView, lItems, Sync ); end )
      else
        Queue( procedure begin SendDataToListView( AListView, lItems, Sync ); end );
    end;
end;

end.
Im Anhang ScreenShot, Exe-Datei und der gesamte Source

FredlFesl 29. Sep 2011 06:52

AW: SQL-Abfrage im Thread und füllen eines ListView
 
Hmm.. Bisserl kompliziert (zu viel Rumkopiererei), wenn man bedenkt, das man die Daten beim virtual Mode gar nicht in die ListView schreiben muss. Ich würde die Daten im Hintergrund einfach in eine TObjectList<TStringList> füllen und immer dann, wenn ein weiterer Block befüllt wurde, einfach per Synchronize die ListView.Items.Count neu setzen.

Aber als allgemeingültiges Proof-Of-Concept natürlich ok.

Captnemo 29. Sep 2011 06:57

AW: SQL-Abfrage im Thread und füllen eines ListView
 
Also als erstes heut morgen möchte ich gleich mal eine Kniefall für Sir Rufo :hello: machen. Das ich im Create gleich das Zeilobject mit übergeben kann, da wär ich im Leben nicht drauf gekommen. Wenn ich das so richtig verstanden habe, dann kann in der Create-Procedure dann ein Connection und Query object erzeugen. Das SQL-Statement könnte ich anstelle von afilename übergeben.
Und das Execute wird nur einmal ausgeführt.

Damit werd ich das jetzt mal versuchen. Was ich bisher aus den verschieden Beispielen zusammengebastelt hatte hatte schon eine gewisse ähnlichkeit mit dem was du hier gepostet hast. Nur hab ich versucht, die Paramter für die Abfrage per Property zu übergeben, aber das hat noch nicht funktioniert. Wenn ich das gleich im Create machen kann spar ich mir noch code.

Okay, ich frage mich grad noch, wann und wie sich der Thread dann zerstört? Und ich müßte die Objekte, die ich im Thread erzeuge ja auch noch freigeben. Dafür würde ich den destructor Destroy nehmen, wenn das geht.


Zur Tabelle hier das das Create
Code:
CREATE TABLE `main` (
  `Lfdnr` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `Datum` datetime NOT NULL,
  `Zeit` varchar(45) DEFAULT NULL,
  `Da` varchar(45) DEFAULT '0',
  `Erl` varchar(45) DEFAULT '0',
  `Name` varchar(45) DEFAULT NULL,
  `Wer` varchar(45) DEFAULT NULL,
  `Termin` varchar(45) DEFAULT NULL,
  `Typ` varchar(45) DEFAULT NULL,
  `KZ` varchar(45) DEFAULT NULL,
  `Arbeiten` varchar(250) DEFAULT NULL,
  `Telefon` varchar(45) DEFAULT NULL,
  `Bemerkung` longtext,
  `Farbe` varchar(45) DEFAULT 'clScrollBar',
  `ZT` varchar(45) DEFAULT '0',
  `TB` varchar(45) DEFAULT '0',
  `TD` varchar(45) DEFAULT '0',
  `MA` varchar(45) DEFAULT NULL,
  `EW` varchar(45) DEFAULT '0',
  `Erinnerung` varchar(45) DEFAULT NULL,
  `FormatArbeiten` blob,
  `ID` varchar(45) DEFAULT NULL,
  `Mondruck` int(10) unsigned DEFAULT '0',
  `Auftrnr` varchar(10) DEFAULT NULL,
  `InArbeit` varchar(45) DEFAULT NULL,
  `rg` varchar(45) DEFAULT '0',
  `kmstand` int(10) unsigned DEFAULT '0',
  `recall` int(11) DEFAULT '-1',
  `lkukon` datetime DEFAULT NULL,
  `nachrep` longtext,
  `FD` varchar(45) DEFAULT '0',
  `TBKomm` longtext,
  `TDKomm` longtext,
  `FDKomm` longtext,
  `Gestrichen` varchar(45) DEFAULT '0',
  `HBKomm` longtext,
  `first` int(11) DEFAULT '0',
  `prior` int(11) DEFAULT '0',
  `next` int(11) DEFAULT '0',
  `erster` int(11) DEFAULT '0',
  `voheriger` int(11) DEFAULT '0',
  `naechster` int(11) DEFAULT '0',
  `weitergabe` int(11) DEFAULT '-1',
  `Firmenname` varchar(50) DEFAULT NULL,
  `Bild` blob,
  `debitor` varchar(45) DEFAULT NULL,
  `gruppe` int(11) DEFAULT '-1',
  `zegeplant` int(11) unsigned DEFAULT NULL,
  `frueher` int(11) DEFAULT '-1',
  `EWKomm` longtext,
  `TerminKomm` longtext,
  PRIMARY KEY (`Lfdnr`),
  KEY `Datum` (`Datum`,`Zeit`)
) ENGINE=InnoDB AUTO_INCREMENT=587 DEFAULT CHARSET=latin1;
Und hier mal mein SQL-Statement
Code:
Select Datum, Zeit, Wer, Arbeiten from main where REPLACE(KZ," ","")=:kennzeichen and lfdnr=erster
Das Replace ist leider notwendig, damit alle Schreibweise auch erfasst werden.

FredlFesl 29. Sep 2011 07:07

AW: SQL-Abfrage im Thread und füllen eines ListView
 
Zitat:

Zitat von Captnemo (Beitrag 1127347)
Code:
Select Datum, Zeit, Wer, Arbeiten from main where REPLACE(KZ," ","")=:kennzeichen and lfdnr=erster
Das Replace ist leider notwendig, damit alle Schreibweise auch erfasst werden.

Jo, da muss man sich nicht wundern, wenns ein bisserl länger dauert.
Wieso achtest Du beim *schreiben* der Daten nicht auf die Schreibweise? :gruebel: Dann brauchst Du das komische 'REPLACE' bei der Abfrage nämlich nicht mehr.

Auch immer wieder gerne gefragt und durchaus interessant: Wer scrollt eigentlich durch die 40.000 Datensätze?

Sir Rufo 29. Sep 2011 07:45

AW: SQL-Abfrage im Thread und füllen eines ListView
 
Zitat:

Zitat von Captnemo (Beitrag 1127347)
Also als erstes heut morgen möchte ich gleich mal eine Kniefall für Sir Rufo :hello: machen. Das ich im Create gleich das Zeilobject mit übergeben kann, da wär ich im Leben nicht drauf gekommen. Wenn ich das so richtig verstanden habe, dann kann in der Create-Procedure dann ein Connection und Query object erzeugen. Das SQL-Statement könnte ich anstelle von afilename übergeben.
Und das Execute wird nur einmal ausgeführt.

:thumb:
Zitat:

Zitat von Captnemo (Beitrag 1127347)
Okay, ich frage mich grad noch, wann und wie sich der Thread dann zerstört? Und ich müßte die Objekte, die ich im Thread erzeuge ja auch noch freigeben. Dafür würde ich den destructor Destroy nehmen, wenn das geht.

Ja, alles was der Thread für sich erzeugt wird in diesem auch wieder freigegeben.

Der Thread selber wird dann wie folgt gestartet: (siehe auch den restlichen Quelltest in der zip)
Delphi-Quellcode:
MyThread := TMyThread.Create( {[Parameter],} True {CreateSuspended} );
MyThread.OnTerminated := ThreadIstAmEnde; { Eine Methode die beim Beenden des Threads aufgerufen wird }
MyThread.FreeOnTerminate := True; { Ist der Thread am Ende, dann soll er sich auch gleich vom Acker machen }
MyThread.Start; { Jetzt geht es los }
Zitat:

Zitat von Captnemo (Beitrag 1127347)
Zur Tabelle hier das das Create
Code:
CREATE TABLE `main` (
  `Lfdnr` int(10) unsigned NOT NULL AUTO_INCREMENT,
  ...
  `KZ` varchar(45) DEFAULT NULL,
  ...
  PRIMARY KEY (`Lfdnr`),
  KEY `Datum` (`Datum`,`Zeit`)
) ENGINE=InnoDB AUTO_INCREMENT=587 DEFAULT CHARSET=latin1;

Leg doch auf das Feld KZ einen Index
Zitat:

Zitat von Captnemo (Beitrag 1127347)
Und hier mal mein SQL-Statement
Code:
Select Datum, Zeit, Wer, Arbeiten from main where REPLACE(KZ," ","")=:kennzeichen and lfdnr=erster
Das Replace ist leider notwendig, damit alle Schreibweise auch erfasst werden.

Und dann das Select so
Code:
Select Datum, Zeit, Wer, Arbeiten from main where lfdnr=erster AND REPLACE(KZ," ","")=:kennzeichen
Ja und am besten für das Feld KZ einen Trigger (Insekt und Update) erstellen, der die Leerzeichen schon direkt beim Eintrag in die DB entfernt.

Captnemo 29. Sep 2011 07:49

AW: SQL-Abfrage im Thread und füllen eines ListView
 
Zitat:

Zitat von FredlFesl (Beitrag 1127350)
Wieso achtest Du beim *schreiben* der Daten nicht auf die Schreibweise? :gruebel: Dann brauchst Du das komische 'REPLACE' bei der Abfrage nämlich nicht mehr.

Auch immer wieder gerne gefragt und durchaus interessant: Wer scrollt eigentlich durch die 40.000 Datensätze?

Leider muß ich, was die Schreibweise angeht, davon ausgehen, dass sie eben nicht immer absolut im gleichen format vorhanden ist. Sonst hätte ich das nicht so machen müssen.

Kein Scrollt durch 40000 Datensätze. Darum ja auch die Abfrage. Bleiben ja nur so 20 übrig.

Captnemo 29. Sep 2011 07:59

AW: SQL-Abfrage im Thread und füllen eines ListView
 
@Sir Rufo: Ich hab jetzt mal versucht, den Code von dir zu übernehmen. Leider stoße ich da mal wieder an meine Grenzen. Das Projekt ist ein D7 geschrieben. Und teile deines Codes funktionieren unter D7 noch nicht.
Im Grund sollte das kein Problem sein, aber durch meine begrenzte Erfahrung mit Threads bedeuten die Änderungen für mich schon ein Problem.

Zum einen sind da Sachen wie:

Delphi-Quellcode:
Generics.Collections
-> Kennt D7 ja nicht.

ebenso dieses:
Delphi-Quellcode:
TList<TStrings>.Create
und
Delphi-Quellcode:
for lField in FData.Fields do
(ich denke for .. in kennt D7 auch nicht).

Aber das krieg ich schon hin.

Aber hiermit hab ich wirklich probleme:

Delphi-Quellcode:
Synchronize( procedure begin SendDataToListView( AListView, lItems, Sync ); end )
geht wohl so in D7 auch noch nicht.

Ich kann jetzt aber auch nicht mit dem Projekt auf D2010 umziehen.

Union 29. Sep 2011 08:13

AW: SQL-Abfrage im Thread und füllen eines ListView
 
Das ist doch nur eine Inline Definition. Schreib die zu synchronisierende procedure einfach normal und ruf sie dort auf. Also aus:
Delphi-Quellcode:
Synchronize( procedure begin SendDataToListView( AListView, lItems, Sync ); end )
wird dann
Delphi-Quellcode:
procedure TMeinThread.SyncProc
begin
   SendDataToListView( AListView, lItems, Sync );
end;

...

Synchronize(SyncProc)
Das ganze hier ist aber aus meiner Sicht komplett überflüssig, da das Problem in der Datenbankdefinition und der Abfrage liegt. Du treibst hier einen affenartigen Aufwand um Kosmetik zu betreiben.

Dein KennZeichen Feld sollte normalisiert werden. Wo kommt der Abfrage Parameter her? Und wie sieht die Mastertabelle aus?

jobo 29. Sep 2011 08:58

AW: SQL-Abfrage im Thread und füllen eines ListView
 
Ich kann mich nur union anschließen.

Statt die Ursache in der Datenhaltung zu beseitigen, wird das "Problem" in einem Thread versteckt.

Wieviel Nutzer verwenden diese Maske? Wie oft am Tag?
Sicher, es sind nur ein paar Sekunden pro Aufruf. Aber ich vermute, dass Euer Hardwarehandler vielleicht bald mit einem großen Paket vorbeikommt, wenn Du den Stil beibehälst.

Sir Rufo 29. Sep 2011 09:30

AW: SQL-Abfrage im Thread und füllen eines ListView
 
Ok, unter D7 ist das so tatsächlich nicht möglich ;)

Mal sehen ob ich da eine D7-taugliche Version dir bereitstellen kann.

Allerdings solltest du tatsächlich mal das Kennzeichen in den Index aufnehmen und mit einem Trigger sicherstellen, dass dort keine Leerzeichen auftauchen.

Dann geht diese Abfrage bestimmt wie der Teufel
Code:
Select Datum, Zeit, Wer, Arbeiten from main where lfdnr = erster AND KZ = :kennzeichen
EDIT: Da fällt mir doch gerade auf, dass du das Feld erster auch in den Index aufnehmen solltest ;)

p80286 29. Sep 2011 09:52

AW: SQL-Abfrage im Thread und füllen eines ListView
 
Zitat:

Zitat von Captnemo (Beitrag 1127360)
Kein Scrollt durch 40000 Datensätze. Darum ja auch die Abfrage. Bleiben ja nur so 20 übrig.

Und für 20 Datensätze machst Du so einen Aufstand?
Investiere eine Stunde Arbeit und bring Deine DB auf Vordermann. Auch Benutzer sollten sich an ein wenig Datenerfassungsdiziplin gewöhnen können. oder willst Du auch noch alle "/" durch "7" ersetzen? Aber nur wenn die Daten am Freitag erfasst wurden.....

Gruß
K-H

Captnemo 29. Sep 2011 13:08

AW: SQL-Abfrage im Thread und füllen eines ListView
 
Ihr habt ja alle Recht. Und dank jfheins, der mich mit dem Befehl Explain auf die Richtige spur gebracht hat, hab ich das Problem mit der langen Abfrage mittlerweile auch lösen können.

Und...tatsächlich lag es an einem Index, den MySQL nicht verwendet hatte, weil ich dort ein Feld zuviel drin hatte. Ich wußte nur nicht, wie ich sehen kann, welchen Index MySQL denn bei einer Abfrage verwendet. Jetzt ist bezüglich der Abfrage alles wieder top. Liegt jetzt bei 30-40 Milisekunden.

Aber die Thread-Problematik wollte ich trotzdem bis zum Ende durchziehen, denn es hilft mir vielleicht mal an andere Stelle, wenn ich jetzt das ganze hinbekomme und verstehe. Deswegen, würde ich gerne an dieser Stelle noch probieren, bis ich die Abfrage über den Thread hinbekommen habe. Auch wenn ich es später so nicht im Code verwenden werde (weil ja jetzt nicht mehr notwendig)

Medium 29. Sep 2011 14:54

AW: SQL-Abfrage im Thread und füllen eines ListView
 
Im Grunde ist das mit den Threads recht einfach, vor allem wenn man TThread als Basis nimmt. Kurzform: Alles was in der überschriebenen Methode "Execute()" passiert bzw. da heraus aufgerufen wird, passiert in einem eigenen Thread. Bamms. Abgesehen davon ist ein TThread-Nachfahre eine Klasse wie jede andere auch.

Was das ganze etwas verkompliziert, ist das Zusammenspiel mehrerer Threads, so z.B. mit dem Hauptthread deiner Anwendung, in dessen Kontext u.a. alles was mit der VCL GUI zu tun hat läuft. Da kommen dann so Spiele wie Synchronisation hinzu. Als einfachst-mögliche Fausregel könnte man eventuell sagen: Nutze in einem Thread niemals Dinge, die nicht innerhalb des Threads erstellt wurden. "Innerhalb des Threads" heisst bei TThread, dass es entweder in dessen Kontruktor, oder der Execute-Methode.
Gemein ist dabei, dass es an sich prinzipiell geht, und 1000 Mal auch ohne Probleme, aber auch das wir einem eines schönen Tages in den Hintern beissen. Daher gleich rigoros sein, und nicht "rumhacken".

Sobald man mit anderen Threads kommunizieren will (z.B. einem VCL Control eine StringList unterjubeln), muss man threadsichere bzw. threadsichernde Wege wählen, wozu das direkte Beschreiben von Properties (oder aufrufen von Methoden) nicht zählt, es sein denn, man tut dies in einer Critical-Section (TCriticalSection), die die beteiligten für die Dauer der Operation zusammenführt.
Alternativ, und das ist mein Favorit, Windows-Messages vom Thread an ein Formular schicken, und dort dann mit einem Handler reagiern.

Btw: Schön, dass wir dich doch noch zur "Wurzelbehandlung" an der DB bekommen haben - das beruhigt meinen Magen ;)

Captnemo 29. Sep 2011 17:41

AW: SQL-Abfrage im Thread und füllen eines ListView
 
Zitat:

Zitat von Medium (Beitrag 1127508)
Btw: Schön, dass wir dich doch noch zur "Wurzelbehandlung" an der DB bekommen haben - das beruhigt meinen Magen ;)

Es war nie so, dass ich mich dagegen gewehrt hätte. Nur hatte ich die Frage bzgl. der DB unter Datenbank schon gestellt und war nicht weiter gekommen. Und durch die Tatsache, dass ich nicht wußte, wie ich prüfen kann, welchen Index MySQL verwendet, kam ich auch nicht weiter. Jetzt wo ich von jfheins den Tipp mit Explain bekommen habe, war die Sache nach 5 Min erledigt. :-)


Alle Zeitangaben in WEZ +1. Es ist jetzt 03:27 Uhr.

Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz