AGB  ·  Datenschutz  ·  Impressum  







Anmelden
Nützliche Links
Registrieren
Zurück Delphi-PRAXiS Sprachen und Entwicklungsumgebungen Sonstige Fragen zu Delphi Delphi SQL-Abfrage im Thread und füllen eines ListView
Thema durchsuchen
Ansicht
Themen-Optionen

SQL-Abfrage im Thread und füllen eines ListView

Ein Thema von Captnemo · begonnen am 28. Sep 2011 · letzter Beitrag vom 29. Sep 2011
Antwort Antwort
Seite 1 von 2  1 2      
Medium

Registriert seit: 23. Jan 2008
3.688 Beiträge
 
Delphi 2007 Enterprise
 
#1

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

  Alt 28. Sep 2011, 22:41
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?
"When one person suffers from a delusion, it is called insanity. When a million people suffer from a delusion, it is called religion." (Richard Dawkins)
  Mit Zitat antworten Zitat
Benutzerbild von Sir Rufo
Sir Rufo

Registriert seit: 5. Jan 2005
Ort: Stadthagen
9.454 Beiträge
 
Delphi 10 Seattle Enterprise
 
#2

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

  Alt 28. Sep 2011, 23:11
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
Angehängte Grafiken
Dateityp: png ThreadGetSqlData_S1.png (58,2 KB, 43x aufgerufen)
Angehängte Dateien
Dateityp: zip ThreadGetSQLData.zip (834,1 KB, 37x aufgerufen)
Kaum macht man's richtig - schon funktioniert's
Zertifikat: Sir Rufo (Fingerprint: ‎ea 0a 4c 14 0d b6 3a a4 c1 c5 b9 dc 90 9d f0 e9 de 13 da 60)

Geändert von Sir Rufo (28. Sep 2011 um 23:25 Uhr)
  Mit Zitat antworten Zitat
FredlFesl

Registriert seit: 19. Apr 2011
293 Beiträge
 
Delphi 2009 Enterprise
 
#3

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

  Alt 29. Sep 2011, 06:52
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.
Das Bild hängt schief.
  Mit Zitat antworten Zitat
Benutzerbild von Captnemo
Captnemo

Registriert seit: 27. Jan 2003
Ort: Bodenwerder
1.126 Beiträge
 
Delphi XE4 Architect
 
#4

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

  Alt 29. Sep 2011, 06:57
Also als erstes heut morgen möchte ich gleich mal eine Kniefall für Sir Rufo 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.
Dieter
9 von 10 Stimmen in meinem Kopf sagen ich bin nicht verrückt. Die 10. summt dazu die Melodie von Supermario Bros.
MfG Captnemo

Geändert von Captnemo (29. Sep 2011 um 07:03 Uhr)
  Mit Zitat antworten Zitat
FredlFesl

Registriert seit: 19. Apr 2011
293 Beiträge
 
Delphi 2009 Enterprise
 
#5

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

  Alt 29. Sep 2011, 07:07
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? 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?
Das Bild hängt schief.
  Mit Zitat antworten Zitat
Benutzerbild von Captnemo
Captnemo

Registriert seit: 27. Jan 2003
Ort: Bodenwerder
1.126 Beiträge
 
Delphi XE4 Architect
 
#6

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

  Alt 29. Sep 2011, 07:49
Wieso achtest Du beim *schreiben* der Daten nicht auf die Schreibweise? 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.
Dieter
9 von 10 Stimmen in meinem Kopf sagen ich bin nicht verrückt. Die 10. summt dazu die Melodie von Supermario Bros.
MfG Captnemo
  Mit Zitat antworten Zitat
Benutzerbild von Captnemo
Captnemo

Registriert seit: 27. Jan 2003
Ort: Bodenwerder
1.126 Beiträge
 
Delphi XE4 Architect
 
#7

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

  Alt 29. Sep 2011, 07:59
@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:

Generics.Collections -> Kennt D7 ja nicht.

ebenso dieses: TList<TStrings>.Create und 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:

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.
Dieter
9 von 10 Stimmen in meinem Kopf sagen ich bin nicht verrückt. Die 10. summt dazu die Melodie von Supermario Bros.
MfG Captnemo
  Mit Zitat antworten Zitat
Benutzerbild von Union
Union

Registriert seit: 18. Mär 2004
Ort: Luxembourg
3.492 Beiträge
 
Delphi 7 Enterprise
 
#8

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

  Alt 29. Sep 2011, 08:13
Das ist doch nur eine Inline Definition. Schreib die zu synchronisierende procedure einfach normal und ruf sie dort auf. Also aus:
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?
Ibi fas ubi proxima merces
sudo /Developer/Library/uninstall-devtools --mode=all
  Mit Zitat antworten Zitat
Benutzerbild von p80286
p80286

Registriert seit: 28. Apr 2008
Ort: Stolberg (Rhl)
6.659 Beiträge
 
FreePascal / Lazarus
 
#9

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

  Alt 29. Sep 2011, 09:52
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
Programme gehorchen nicht Deinen Absichten sondern Deinen Anweisungen
R.E.D retired error detector
  Mit Zitat antworten Zitat
Benutzerbild von Captnemo
Captnemo

Registriert seit: 27. Jan 2003
Ort: Bodenwerder
1.126 Beiträge
 
Delphi XE4 Architect
 
#10

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

  Alt 29. Sep 2011, 13:08
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)
Dieter
9 von 10 Stimmen in meinem Kopf sagen ich bin nicht verrückt. Die 10. summt dazu die Melodie von Supermario Bros.
MfG Captnemo
  Mit Zitat antworten Zitat
Antwort Antwort
Seite 1 von 2  1 2      


Forumregeln

Es ist dir nicht erlaubt, neue Themen zu verfassen.
Es ist dir nicht erlaubt, auf Beiträge zu antworten.
Es ist dir nicht erlaubt, Anhänge hochzuladen.
Es ist dir nicht erlaubt, deine Beiträge zu bearbeiten.

BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus.
Trackbacks are an
Pingbacks are an
Refbacks are aus

Gehe zu:

Impressum · AGB · Datenschutz · Nach oben
Alle Zeitangaben in WEZ +1. Es ist jetzt 03:11 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-2025 by Thomas Breitkreuz