Datenbankverbindung

Für Themen zu Datenbanken und Zugriff auf diese. Auch für Datenbankkomponenten.
Antworten
Joachim Raap
Beiträge: 57
Registriert: Mo 30. Mär 2020, 12:37

Datenbankverbindung

Beitrag von Joachim Raap »

[gelöst] Hallo, ich habe als Datenbankneuling mal eine Frage an die Experten:
Gibt es eine Regel, wie man Datenbankverbindungen herstellt und Abfragen ausführen muß? Ich habe häufig das Problem, daß ich die Verbindung (MySQL) über eine Procedure herstelle, mittels "FMLogIn.SQLQuery1.SQL.Text:=..." eine Abfrage formuliere und mit "FMLogIn.SQLQuery1.execSQL" losschicke. Zwischen der Verbindungserstellung und der Ausführung liegen mitunter noch einige andere Befehle oder eine Abfrage wird in einer Schleife gemacht mit der Folge, daß ich keine Ergebnisse oder Fehlermeldungen bekomme (z.B., daß keine Felder gefunden worden sind (die es natürlich gibt!)). Falls ich mit zwei Tabellen arbeite werden ebenfalls keine Ergebnisse geliefert.

Muß man also nach jeder Verbindung, der Abfrage und der Ausführung die Verbindung immer wieder schließen und z.B. für die zweite Tabelle wieder eine Verbindung herstellen? Falls in einer Schleife Abfragen erfolgen muß man also immer wieder in der Schleife die Verbindung herstellen, die Abfrage formulieren und ausführen und dann die Verbindung wieder schließen??(hoffentlich konnte ich mich verständlich ausdrücken......)

Vielen Dank für die Hilfe
Zuletzt geändert von Joachim Raap am Fr 15. Mai 2020, 17:20, insgesamt 3-mal geändert.

willi4willi
Lazarusforum e. V.
Beiträge: 136
Registriert: Sa 1. Nov 2008, 18:06
OS, Lazarus, FPC: Windows (10), Linux (debinan) / FPC 3.0.4 / Lazarus 2.0.8
CPU-Target: i386, win64, arm

Re: Datenbankverbindung

Beitrag von willi4willi »

Hallo Joachim,

nein, die Verbindung musst du nicht schließen.

Du baust einmal die Verbindung auf, schickst über diese Verbindung mehrere SQL-Abfragen oder Befehle an die Datenbank und schließt diese anschließend wieder, wenn du sie nicht mehr benötigst.

Falls sich die Verbindung hin und wieder von allein verabschiedet, also trennt, dann prüfe man deine Timeout - Einstellungen bei der Datenbank. Ich hatte da auch hin und wieder Probleme.

Wenn du auf Datenbankabfragen keine Ergenbisse erhältst, ohne Fehlermeldung, dann ist vielleicht die Verbindung noch vorhanden, aber die Abfrage hat ein Problem.
Das Problem ist derart, dass die Syntax der Abfrage richtig ist. Daher keine Fehlermeldung.

Dabei gehe ich einmal davon aus, dass du die Fehlermeldungen nicht abfängst (try -- except).

Mein Tipp:
  • Prüfe vor einer Abfrge, ob die Verbindung noch steht ( if Connection.connected then ...),
  • Prüfe deine Abfragen mal außerhalb deines Codes, indem du sie irgendwie ausgibst (z.B. Writeln(FMLogIn.SQLQuery1.SQL.Text); readln; ) und über einen Mysql-Client ausführst.
Viele Grüße

Willi4Willi

------------

Joachim Raap
Beiträge: 57
Registriert: Mo 30. Mär 2020, 12:37

Re: Datenbankverbindung

Beitrag von Joachim Raap »

Hallo,
mh - ich habe mal die Procedure hierherkopiert

Code: Alles auswählen

procedure TFMCSV.BtPruefenClick(Sender: TObject);
 begin
  FMLogIn.ConnectDB;
  for i := 0 to FMCSV.LVCSV.Items.Count -1 do
   begin
    s:=FMCSV.LVCSV.Items.Item[i].Subitems[1];   //SubItems[1] enthält die Artikelnummer - Übergabe an s
    FMLogIn.SQLQuery1.SQL.Text:='select * from art_stamm where Sta_ArtLoesch=0 and Sta_ArtNr='''+s+''';';
    FMLogIn.SQLQuery1.execSQL;
    strStaNr:=FMLogIn.SQLQuery1.FieldByName('Sta_ArtNr').AsString;
    if strStaNr=''  then
     begin
      FMLogIn.SQLQuery1.SQL.Text:='insert into Fehler (Nr) values ('''+s+''');';
      FMLogIn.SQLQuery1.execSQL;
     end;
   end;
  FMLogIn.CloseDB;
 end; 
Wenn ich das so mache, dann gibt es den Fehler "Field not found - Sta_ArtNr"
Aufgabe der Procedure ist in der Datenbank "nachzusehen", ob es einen Satz mit der Artikelnummer gibt. Wenn ja, ist alles gut, wenn nein, dann soll es die Artikelnummer ist eine Fehlertabelle übernehmen die hinterher an den User zur Behebung ausgegeben wird.

Wo/wie kann ich den "Timeout" prüfen?

Joachim Raap
Beiträge: 57
Registriert: Mo 30. Mär 2020, 12:37

Re: Datenbankverbindung

Beitrag von Joachim Raap »

Nachtrag

ich habe die Procedure wie folgt umgeschrieben:

Code: Alles auswählen

procedure TFMCSV.BtPruefenClick(Sender: TObject);
 begin
  FMLogIn.ConnectDB;
  for i := 0 to FMCSV.LVCSV.Items.Count -1 do
   begin
    s:=FMCSV.LVCSV.Items.Item[i].Subitems[1];   //SubItem[1] enthält die Artikelnummer
    FMLogIn.SQLQuery1.SQL.Text:='select * from art_stamm where Sta_ArtLoesch=0 and Sta_ArtNr='''+s+''';';
     try
       begin
        FMLogIn.SQLQuery1.execSQL;
        strStaNr:=FMLogIn.SQLQuery1.FieldByName('Sta_ArtNr').AsString;
       end;
      except
       begin
        if strStaNr=''  then
        FMLogIn.SQLQuery1.SQL.Text:='insert into Fehler (Nr) values ('''+s+''');';
        FMLogIn.SQLQuery1.execSQL;
       end;
     end;
   end;
  FMLogIn.CloseDB;
 end;
.....und alles ist gut! Vielen Dank

Benutzeravatar
af0815
Lazarusforum e. V.
Beiträge: 4223
Registriert: So 7. Jan 2007, 10:20
OS, Lazarus, FPC: FPC fixes Lazarus fixes per fpcupdeluxe (win,linux,raspi)
CPU-Target: 32Bit (64Bit)
Wohnort: Niederösterreich
Kontaktdaten:

Re: Datenbankverbindung

Beitrag von af0815 »

Generell: Versuch kein gefrickel mit den Strings, sondern verwende dafür Parameter. Irgendwann landest du sonst in der Hochkommahölle :-)

Ausserdem ist einer der Bausteine zum Schutz gegen SQL-Injections. Die passieren auch mal wenn wer 'or true heisst :shock:
Blöd kann man ruhig sein, nur zu Helfen muss man sich wissen (oder nachsehen in LazInfos/LazSnippets).

willi4willi
Lazarusforum e. V.
Beiträge: 136
Registriert: Sa 1. Nov 2008, 18:06
OS, Lazarus, FPC: Windows (10), Linux (debinan) / FPC 3.0.4 / Lazarus 2.0.8
CPU-Target: i386, win64, arm

Re: Datenbankverbindung

Beitrag von willi4willi »

Das funktioniert tatsächlich so?

Ich hätte nach

Code: Alles auswählen

  FMLogIn.SQLQuery1.SQL.Text:='select * from art_stamm where Sta_ArtLoesch=0 and Sta_ArtNr='''+s+''';';
ein

Code: Alles auswählen

  FMLogIn.SQLQuery1.Open;
  if not FMLogIn.SQLQuery1.EOF() 
  then strStaNr:=FMLogIn.SQLQuery1.FieldByName('Sta_ArtNr').AsString
  else .....
erwartet.

Für das Verhalten der mysql/MariaDB-Datenbank gibt es verschiede Variablen. Die kann man in der Datenbank auch global setzen.

Wenn du den Zugriff darauf nicht hast, dann kannst du das auch in deinem Programm nach der Verbindungsaufnahme für die Session setzen.
Ich mache immer sowas:

Code: Alles auswählen

      Connect;
      ExecuteDirect('SET SESSION wait_timeout = 31536000;');
      ExecuteDirect('SET SESSION net_read_timeout = 1000;');
Viele Grüße

Willi4Willi

------------

Joachim Raap
Beiträge: 57
Registriert: Mo 30. Mär 2020, 12:37

Re: Datenbankverbindung

Beitrag von Joachim Raap »

Ich noch mal....
Ja, funktioniert grundsätzlich schon. Ich meine jedoch in meinem Unwissen, daß der Vorschlag (den mit ....SQLQuery1.EOF() ) nicht verwendbar ist weil ich mir nicht alle Datensätze nacheinander vornehme sondern gezielt einen, der der entsprechenden ArtNr entspricht (den gibt es auch nur einmal)..... :roll:
Dennoch gibt es jetzt einen inhaltlichen Fehler: In die Fehlertabelle werden alle Sätze geschrieben auch die, die es gibt. Das muß irgendwie an dem Befehl "strStaNr:=FMLogIn.SQLQuery1.FieldByName('Sta_ArtNr').AsString;" liegen - die variable bleibt nämlich immer leer.
Kannst Du da einen Fehler entdecken warum der Wert von Sta_ArtNr nicht übertragen wird????? (heul)

Joachim Raap
Beiträge: 57
Registriert: Mo 30. Mär 2020, 12:37

Re: Datenbankverbindung

Beitrag von Joachim Raap »

schön af0815 - dann werde doch mal konkret: Was für Parameter?

Joachim Raap
Beiträge: 57
Registriert: Mo 30. Mär 2020, 12:37

Re: Datenbankverbindung

Beitrag von Joachim Raap »

willi4willi, ich habe die procedure Deinem Vorschlag entsprechend noch mal geändert; sie sieht jetzt so aus:

Code: Alles auswählen

procedure TFMCSV.BtPruefenClick(Sender: TObject);
 begin
  FMLogIn.ConnectDB;
  for i := 0 to FMCSV.LVCSV.Items.Count -1 do
   begin
    s:=FMCSV.LVCSV.Items.Item[i].Subitems[1];   //SubItem[1] enthält die Artikelnummer -> an s übergeben
    FMLogIn.SQLQuery1.SQL.Text:='select * from art_stamm where Sta_ArtLoesch=0 and Sta_ArtNr='''+s+''';';
    FMLogIn.SQLQuery1.open;
    if not FMLogIn.SQLQuery1.EOF then strStaNr:=FMLogIn.SQLQuery1.FieldByName('Sta_ArtNr').AsString;
    if strStaNr=''  then
       begin
        FMLogIn.SQLQuery1.SQL.Text:='insert into Fehler (Nr) values ('''+s+''');';
        FMLogIn.SQLQuery1.execSQL;
       end;
    end;
  FMLogIn.CloseDB;
 end;
Leider aber wird der Wert aus der Datenbank (in Sta_ArtNr) immer noch nicht an die Variable strStaNr übergeben so das strStaNr immer leer bleibt. Kannst Du einen Fehler sehen?

willi4willi
Lazarusforum e. V.
Beiträge: 136
Registriert: Sa 1. Nov 2008, 18:06
OS, Lazarus, FPC: Windows (10), Linux (debinan) / FPC 3.0.4 / Lazarus 2.0.8
CPU-Target: i386, win64, arm

Re: Datenbankverbindung

Beitrag von willi4willi »

Das sollte so funktionieren, ohne zu wissen, wie die Datenbank-Verbindung aufgebaut wird.

Hast du einmal mit einem MySQL-Client getestet, ob deine Abfrage auch wirklich einen Wert zurückgibt?

Ansonsten fallen mir noch folgende Dinge auf:
  • Die Variable strStaNr sollte initialisiert bzw. zurückgesetzt sein.
  • Enthält SubItem[1], die an s übergeben wird, wirklich die nur Artikelnummer? Oder sind da evtl. noch Leerzeichen mit im Spiel?

Code: Alles auswählen

procedure TFMCSV.BtPruefenClick(Sender: TObject);
 begin
  FMLogIn.ConnectDB;
  for i := 0 to FMCSV.LVCSV.Items.Count -1 do
   begin
    s:=FMCSV.LVCSV.Items.Item[i].Subitems[1];   //SubItem[1] enthält die Artikelnummer -> an s übergeben
    s:=trim(s);	//< verhindern, dass da noch unerwünschte Leerzeichen sind
   		 //<  falls die aber in der Datenbank tatsächlich vorhanden sind, dann 
    		 //< darfst du das natürlich nicht machen
    FMLogIn.SQLQuery1.SQL.Text:='select * from art_stamm where Sta_ArtLoesch=0 and Sta_ArtNr='''+s+''';';
    FMLogIn.SQLQuery1.open;
    strStaNr:='';                                                        //< Initialisieren bzw. zurücksetzen, sonst steht ja noch der letzte Wert drin
    if not FMLogIn.SQLQuery1.EOF then strStaNr:=FMLogIn.SQLQuery1.FieldByName('Sta_ArtNr').AsString;
    FMLogIn.SQLQuery1.close;                                 //< Dataset wieder schließen, 
    if strStaNr=''  then
       begin
        FMLogIn.SQLQuery1.SQL.Text:='insert into Fehler (Nr) values ('''+s+''');';
        FMLogIn.SQLQuery1.execSQL;
       end;
    end;
  FMLogIn.CloseDB;
 end;
Übrigens, der Tipp von af0815 lohnt in deinem Fall wirklich nachzugehen. In dem Fall bedarf es nur ein FMLogIn.SQLQuery1.refresh;, wenn die Artikelnummer als Parameter gesetzt ist.
Den SQL.Text definierst du dann nur einmal. Den Insert würde ich dann direkt über die Connection senden.

Wie gesagt es lohn sich, sich mit den Datenbankgrundlagen näher zu befassen. Ich habe da auch lange "herumgestochert", weil es einfach zu viel Durcheinander gibt.

Wegen der Parameter: Schau mal hier!
Viele Grüße

Willi4Willi

------------

Joachim Raap
Beiträge: 57
Registriert: Mo 30. Mär 2020, 12:37

Re: Datenbankverbindung

Beitrag von Joachim Raap »

willi4willi hat geschrieben:
Fr 15. Mai 2020, 14:41

Hast du einmal mit einem MySQL-Client getestet, ob deine Abfrage auch wirklich einen Wert zurückgibt?
[/code]
Hallo,
ja, es wird ein Wert zurückgegeben (ich habe dann die Abfrage auch direkt nach Lazarus kopiert...)

Joachim Raap
Beiträge: 57
Registriert: Mo 30. Mär 2020, 12:37

Re: Datenbankverbindung

Beitrag von Joachim Raap »

mh - Deine Version funktioniert!
Ich verstehe nicht wirklich, warum noch in die Tabelle "Fehler" geschrieben wird, weil doch die Verbindung vorher geschlossen worden ist - und dann zu Schluß noch einmal...
Es scheint wohl aber ein Unterschied zu geben - SQLQuery1.close ist wohl was anderes als CloseDB.........

Ansonsten hast Du recht - ich muß noch viel über DB lernen; bislang habe ich meine Kenntnisse durch "abgucken" gewonnen (reicht wohl nicht)

DANKE

Benutzeravatar
af0815
Lazarusforum e. V.
Beiträge: 4223
Registriert: So 7. Jan 2007, 10:20
OS, Lazarus, FPC: FPC fixes Lazarus fixes per fpcupdeluxe (win,linux,raspi)
CPU-Target: 32Bit (64Bit)
Wohnort: Niederösterreich
Kontaktdaten:

Re: Datenbankverbindung

Beitrag von af0815 »

Schaut bei mir meist so aus

Code: Alles auswählen

  ...  
  SQL := ''+LineEnding
  +'  SELECT Top 1 [DateTime],'+ LineEnding
  +'       [Fehlergruppe], [Fehler]'+ LineEnding
  +'    FROM [dbo].[mytable]' + LineEnding
  +' WHERE TRIM(Produkt) like TRIM(:Param1)' + LineEnding
  +' ORDER BY [DateTime] DESC';
  ...  
  Query.Active := false;
  Query.SQL.Clear;
  Query.SQL.Add(SQL);
  Query.Params.CreateParam(ftString,'Param1',ptInput);
  Query.Params.ParamByName('Param1').Value := aParam;
  Query.Active := True;
  Query.First;
  ....
für so Abfragen verwende ich oft eine temporär erzeugte Query, die oft nur eine View am Server abfragt. Weil die View, wenn komplexer bereits am Server vorkompiliert gespeichert werden kann.

Übrigends ist der Unterschied zwischen ExecSQL und Open der, das bei ExecSQL normalerweise kein Resultset zurückerwartet wird, sondern nur ein Ergebnis. Wird normalerweise nur dann verwendet, wenn die Abfrage nur eine Zeile mit einem Wert beinhaltet oder für Update, Insert und Delete, da hier kein Resultset zurückkommt, sondern maximal ein Result (affected lines).

Faustregel, wenn Select, dann ist ein Open zwingend, bei Update, Insert und Delete ist es ExecSQL.

Übrigend sagt nach einen Open die Abfrage "Query.BOF and Query.EOF" ob es ein Resultset gibt. Das sollte man Abfragen bevor man die Felder des Resultsets abfragt. Man erspart sich Ärger damit bei der Abfrage.
Blöd kann man ruhig sein, nur zu Helfen muss man sich wissen (oder nachsehen in LazInfos/LazSnippets).

Joachim Raap
Beiträge: 57
Registriert: Mo 30. Mär 2020, 12:37

Re: Datenbankverbindung

Beitrag von Joachim Raap »

danke af0815.
Aber das verstehe ich vielleicht in einem halben Jahr........
Wie gesagt, ich bin Anfänger (wie Du weißt)!

Antworten