DBGrid: Suchen und Markieren

Für Fragen von Einsteigern und Programmieranfängern...
Antworten
Aliobaba
Lazarusforum e. V.
Beiträge: 496
Registriert: Di 1. Mai 2012, 09:11

DBGrid: Suchen und Markieren

Beitrag von Aliobaba »

Hallo,

derzeit versuche ich, einen bestimmten Zeileneintrag in einer 1-spaltigen DB-Grid Tabelle möglichst schnell und „effektiv“ zu suchen und mit dem Cursor zu markieren.

Die einfachste, aber wahrscheinlich die am wenigsten elegante Methode ist das schlichte Prüfen eines jeden Datensatzes der Reihe nach von Anfang an. Bei kurzen Datensätzen kein Problem, die Suchdauer wird aber linear zum Anwachsen der Datei immer länger.

Deshalb habe ich mir die unten stehende Routine ausgedacht: Man geht zum mittleren Datensatz, prüft ob dies ein Treffer ist, oder der gesuchte DS kleiner oder größer ist; dann wieder die Hälfte usw. (sh Code-Beispiel unten). Am Ende möchte ich dann noch, dass der Cursor einigermaßen in der Mitte des Feldes „stehen bleibt“. Geht super schnell, da mit jedem Schritt bereits eine Hälfte der Möglichkeiten "verworfen" wird.

Dieser Code funktioniert sehr gut und sehr schnell, einige Fragen habe ich aber noch:

1. Kann man es vermeiden, dass der Cursor während des Suchvorganges in der Datenbank „so wild“ umherspringt; der Cursor also erst nach Beenden des Suchvorganges in der Datenbank an die richtige Stelle des DBGrids springt?

2. Kann man es möglich machen, dass sich der Cursor nach Finden des Datensatzes „in die Mitte“ der Tabelle setzt? („Mein“ Code funktioniert zwar prächtig, elegant ist er aber sicherlich nicht – ich nehme in Kauf, dass die „Profis“ unter Euch schmunzeln werden).

3. Gibt's „bessere“ und vielleicht auch „fertige“ Suchroutinen? (Sorry: "Bessere" ganz sicher!)

4. Und jetzt mein Hauptproblem: Eine Ewigkeit suchte ich nach dem Grund, warum „meine“ Routine manche Datensätze einfach nicht gefunden hat. Natürlich denkt man zunächst an Fehler im eigenen Algorithmus. Es lag aber an einer ganz verrückten Sache: die Sortierreihenfolge von SQL (SQLite/Zeos) und von Lazarus [ v := AnsiCompareText ( x); ] wird bezüglich des Bindestrichs „ - „ anders" behandelt und anders sortiert, was dazu führt, dass „mein“ Algorithmus den richtigen Datensatz in seltenen Fällen nicht findet. Betroffen sind also nur Wörter, die „in der Nähe“ ein anderes(!) Wort als das Gesuchte mit einem „ - „ [Bindestrich] im Text haben. Die Fehlersuche war deshalb so schwierig, weil ja nicht mal das gesuchte Wort selber diesen Bindestrich enthalten muss, sondern auch ein beliebiges anderes Wort, das eben zufällig in der „Mittensuche“ meines Codes angesprungen wird und dann die Info „weitergibt“, das gesuchte Wort sei „größer als“ anstatt „kleiner als“. In einer lange Reihe von Dateieinträgen war bei mir nur ein Wort „betroffen“, nur weil ein Wort mit einem Bindestrich „ in der Nähe“ war. [Die Fehlersuche war "grausam"]

Danke!

A. Geigenberger

<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<

Code: Alles auswählen

 
Procedure TForm2.Trimm (Var WString : string);  // evtl. Leerstellen abschneiden
begin
  Wstring := Trimleft(Wstring);
  Wstring := Trimright(Wstring);
end;
 
procedure TForm2.ZuZeileTextspringen ;
var
   i_a, i_e, i_m, v, i : integer;
   String_2  : string;
   LABEL Sprungpunkt,  Sprungpunkt2;
begin
  Form2.Trimm(Hilfsstring);  // Hilfsstring: glob. Variable = zu suchender Eintrag
  i_a := 1;
  i_e := Form1.QText.RecordCount;
Sprungpunkt:
  i_m := ( i_a +  ((i_e - i_a) div 2 ) );  // zu "Mitte" springen
  Form1.QText.RecNo:=(i_m );
  String_2 := Form1.DBEditTitel.Text;    Form2.Trimm(String_2);
  v := AnsiCompareText ( Hilfsstring , String_2 );     
  if ( i_e - i_a ) < 2 then    //  vorerst also nichts gefunden
  begin
      Form1.QText.RecNo:=( 1 );         //  Anfang und Ende noch Checken
      String_2 := Form1.DBEditTitel.Text;    Form2.Trimm(String_2);
      if Hilfsstring = string_2 then exit;
 
      Form1.QText.RecNo:=( Form1.QText.RecordCount ); //  Anfang und Ende noch Checken
      String_2 := Form1.DBEditTitel.Text;       Form2.Trimm(String_2);
      if Hilfsstring = string_2 then exit;
 
      //  Showmessage( 'Nix gefunden'); 
      Form1.QText.RecNo:=(1);  // nix gefunden -> an den Anfang!
      exit;
  end;
  if v = 0 then     // gefunden!
  begin             // Cursor besser  "in die Mitte" bringen
     v := (Form1.QText.RecNo - 1);
     if v > 7 then v := 7;
     For i := 1 to v do form1.QText.Prior;
     Form1.QText.MoveBy( v );
 
     v := (Form1.QText.RecordCount - Form1.QText.RecNo);
     if v > 7 then v := 7;
     For i := 1 to v do form1.QText.Next;
     Form1.QText.MoveBy( - v );
   exit;
  end;
 
  if v = -1 then   i_e := i_m  else i_a := i_m;   // vorher: -1  nachher: 1
  goto Sprungpunkt;
end;
 
"MyMemoryDB" ( https://www.heise.de/download/product/mymemorydb-89626 )

Michl
Beiträge: 2511
Registriert: Di 19. Jun 2012, 12:54

Re: DBGrid: Suchen und Markieren

Beitrag von Michl »

So viel Arbeit :shock:

Was ich nicht ganz verstehe, wie willst du nach dieser Suchmethode eine unsortierte Liste durchsehen bzw. müsstest du noch eine zweite Routine schreiben, wenn, statt ASC, DESC (oder umgekehrt) definiert ist?!

Statt dem Query kann man das DataSet nutzen und mit DataSet.Locate() nach Daten suchen. Beim Fund wird der Focus automatisch in die entsprechende Zeile der DBGrid gesetzt (bei doppelten Einträgen, bleibt der Focus auf dem ersten stehen, weitersuchen geht mit locate leider nicht).

z.B:

Code: Alles auswählen

procedure TForm1.Button1Click(Sender: TObject);
var
  ColFieldName:string;
  SuchStr:string;
begin
  ColFieldName:='Spalte1';                       //Column.Fieldname
  SuchStr:='Eintrag10';                          //Zu suchender Wert
                                                 //DataSource1 ist die zu DBGrid zugewiesene DataSource
  if DataSource1.DataSet.Locate(ColFieldName,SuchStr,[loCaseInsensitive]) then
    Caption:='Eintrag gefunden!'                 //Wenn SuchStr gefunden wurde, zeigt DBGrid den Wert an!
  else
    Caption:='Eintrag nicht gefunden!';
end;
Ob´s schneller/langsamer ist, kannst du ja mal probieren und uns mitteilen.

Zu deinen Fragen:
Aliobaba hat geschrieben:1. Kann man es vermeiden, dass der Cursor während des Suchvorganges in der Datenbank „so wild“ umherspringt; der Cursor also erst nach Beenden des Suchvorganges in der Datenbank an die richtige Stelle des DBGrids springt?
schwer, da du mit

Code: Alles auswählen

Form1.QText.RecNo:=(i_m );
den Focus auf diesen Eintrag setzt. Solange du DBGrid mit Dataset und DataSet mit Query verbunden hast, wird sich das afaik nicht ändern lassen. Trennst du eine der Verbindungen, kommt es zu Problemen, wenn du Ereignisse von der Query nutzt.
Aliobaba hat geschrieben:2. Kann man es möglich machen, dass sich der Cursor nach Finden des Datensatzes „in die Mitte“ der Tabelle setzt?
Das könntest du wie folgt realisieren:

Code: Alles auswählen

  Query.RecNo:=Fundstelle-10; //Angenommen, DBGrid zeigt 20 Zeilen an und Funstelle ist>10
  DataSource.MoveBy(10);  
Aliobaba hat geschrieben:4. Und jetzt mein Hauptproblem: Eine ...
Bin jetzt nicht der Zeichensatz-Profi, da gibts hier wesentlich belesenere als mich. Evtl. kommt der String in einer anderen Form als Ansi rein (evtl. UTF8), mit "AnsiCompareText" werden zwei Strings nach gleichen Ansi-Zeichen verglichen. Evtl. hilft eine Stringumwandlung (z.B. UTF8ToAnsi) oder evtl. nur "CompareStr()"?! Sind aber nur Mutmaßungen.

Code: Alles auswählen

type
  TLiveSelection = (lsMoney, lsChilds, lsTime);
  TLive = Array[0..1] of TLiveSelection;  

Aliobaba
Lazarusforum e. V.
Beiträge: 496
Registriert: Di 1. Mai 2012, 09:11

Re: DBGrid: Suchen und Markieren

Beitrag von Aliobaba »

Hallo Michl,
Michl hat geschrieben:So viel Arbeit :shock:
Meinst Du die Deine Arbeit, Dich durch meinen Code zu quälen, oder
meinst Du meine, diesen Code zu schreiben und über ihn zu „brüten“?

Case 1 : Danke aber, dass Du dies auf Dich genommen hast
Case 2 : Stimmt, hätte mir viel ersparen können!
Michl hat geschrieben: Was ich nicht ganz verstehe, wie willst du nach dieser Suchmethode eine unsortierte Liste durchsehen bzw. müsstest du noch eine zweite Routine schreiben, wenn, statt ASC, DESC (oder umgekehrt) definiert ist?!
Statt dem Query kann man das DataSet nutzen und mit DataSet.Locate() nach Daten suchen. Beim Fund wird der Focus automatisch in die entsprechende Zeile der DBGrid gesetzt (bei doppelten Einträgen, bleibt der Focus auf dem ersten stehen, weitersuchen geht mit locate leider nicht).
Die Liste war nicht unsortiert, da die SQL-Select-Abfrage ein sortiertes Listenfeld lieferte. ABER: Die Sortierreihenfolge war anders als es "AnsiCompareText ( x,y );“ erwartet. Deshalb ja auch meine Probleme mit den sporadischen Fehlern bei der Suche.
„Data.Set.Locate()“ war der ultimative Tip!!! So leicht kann's sein, wenn man von Lazarus was versteht!! Ich hätte mir viel ersparen können (habe aber immerhin auch eine Menge gelernt). Das „Herumspringen“ des Cursors findet ja mit „Data.Set.Locate()“ nicht mehr statt, da der gesuchte Datensatz ja sofort angesprungen wird.

Um den Cursor in die Mitte der Liste zu bringen, bin ich immerhin ähnlich vorgegangen wie Du es vorschlägst. Ich dachte nur, man müsste den Cursor „zeilenweise“ verschieben. Mit 2* Move geht’s auch. Danke!

Code: Alles auswählen

  v := (Form1.QText.RecNo – 1);
  if v > 7 then v := 7;
  Form1.QText.MoveBy( - v );
  Form1.QText.MoveBy( v );
  v := (Form1.QText.RecordCount – Form1.QText.RecNo);
  if v > 7 then v := 7;
  Form1.QText.MoveBy( - v );
  Form1.QText.MoveBy( v );     
Ich mach das „von beiden Seiten“ der Liste, da ich ja nicht weiß, wo der Cursor nach „Data.Set.Locate()“ zunächst in der Liste „landet“

Trotzdem würde es mich noch interessieren, wie man "AnsiCompareText ( x,y )" auf eine mit SQL-sortierte Liste „losschicken“ kann, da die Sortierreihenfolge sich ja unterscheidet. AnsiCompareText meldet einen Suchstring in einer mit SQL-sortierten Liste ja gelegentlich als „davor“ liegend, obwohl er „dahinter“ liegt, was ja zu meinen Schwierigkeiten geführt hat.

Nochmal: Vielen, vielen Dank für Deine Hilfe!!

A. Geigenberger
"MyMemoryDB" ( https://www.heise.de/download/product/mymemorydb-89626 )

Michl
Beiträge: 2511
Registriert: Di 19. Jun 2012, 12:54

Re: DBGrid: Suchen und Markieren

Beitrag von Michl »

Aliobaba hat geschrieben:Case 2 : Stimmt, hätte mir viel ersparen können!
...das meinte ich. "Dataset.Locate" hättest du dir wahrscheinlich in 2 Minuten mit den richtigen Suchbegriffen ergoogeln können. Ansonsten gern geschehen!
Aliobaba hat geschrieben:trotzdem würde es mich noch interessieren, wie man "AnsiCompareText ( x,y )" auf eine mit SQL-sortierte Liste „losschicken“ kann
Weiss ich jetzt leider nicht. Wie gesagt, ich würde erstmal noch "comparestr" und "UTF8toAnsi" probieren. Evtl. kann Dir da jemand anders noch einen Tip geben.

Code: Alles auswählen

type
  TLiveSelection = (lsMoney, lsChilds, lsTime);
  TLive = Array[0..1] of TLiveSelection;  

Antworten