Zeos: Zur Laufzeit neue Spalte in einer DB anlegen

Für Themen zu Datenbanken und Zugriff auf diese. Auch für Datenbankkomponenten.
Antworten
Aliobaba
Lazarusforum e. V.
Beiträge: 496
Registriert: Di 1. Mai 2012, 09:11

Zeos: Zur Laufzeit neue Spalte in einer DB anlegen

Beitrag von Aliobaba »

Hallo,

mittlerweile ist es mir gelungen, eine neue Spalte in einer Datenbank anzulegen (Zeos, SQLite):

Code: Alles auswählen

  try
    form1.ZConn1.ExecuteDirect( ' Alter TABLE "tText" ADD "NeueSpalte" ');
  except
  end;
 

Und wenn die Spalte bereits angelegt ist, erscheint mit obigen Code wegen des "try" auch keine Fehlermeldung.
ABER: Verwenden kann man die neue Spalte noch nicht. Dazu muss man diese Spalte erst manuel in der " QText:TZQuery " in das Forumular "Felder bearbeiten" aufnehmen, was ja auch problemlos zu bewerkstelligen ist.

Was aber, wenn ich die neue Spalte zur Laufzeit generiere: Kann ich diese neue Spalte auch zur Laufzeit mit "Lazarus bekannt machen", falls noch nicht bekannt?

Damit hätte ich es probiert, funktioniert aber nicht.

Code: Alles auswählen

form1.QText.LinkedFields:='NeueSpalte';

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

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

Re: Zeos: Zur Laufzeit neue Spalte in einer DB anlegen

Beitrag von Michl »

Welche Zeos-Version nutzt du? Ich hatte da mal einen Bug entdeckt, der aber mittlwerweile beseitigt ist: http://www.lazarusforum.de/viewtopic.php?f=17&t=7691

Ansonsten könntest du das auch so machen (eben mit SQLite getestet):

Code: Alles auswählen

procedure TForm1.AddNewColClick(Sender: TObject);
begin
  if not Assigned(ZQuery1.FindField('newcol')) then begin
    ZQuery1.Close;
    ZConnection1.ExecuteDirect('ALTER TABLE sometable ADD COLUMN newcol VARCHAR;');
    ZQuery1.Open;
  end;
end;

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: Zeos: Zur Laufzeit neue Spalte in einer DB anlegen

Beitrag von Aliobaba »

Hallo Michl,

das Anlegen einer neuen Spalte gelingt mit dem von Dir geposteten Code.
Wenn die Routine aber ein zweites mal aufgerufen wird, dann erfolgt die Fehlermeldung:

SQL Error: duplicate COLUMN...

Man kommt um das "try" wohl doch nicht herum? Außerdem kann mit der "neuen" Spalte immer noch nicht gearbeitet werden, da diese Spalte unter "Felder bearbeiten" (sh. Bild) nicht aufgeführt wird. Genau das würde ich ja gerne zur Laufzeit machen.

Aliobaba

Screenshot_SQL.png
Screenshot_SQL.png (23.93 KiB) 1960 mal betrachtet
"MyMemoryDB" ( https://www.heise.de/download/product/mymemorydb-89626 )

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

Re: Zeos: Zur Laufzeit neue Spalte in einer DB anlegen

Beitrag von Aliobaba »

.... ach ja, sorry: Zeos 7.1.4
"MyMemoryDB" ( https://www.heise.de/download/product/mymemorydb-89626 )

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

Re: Zeos: Zur Laufzeit neue Spalte in einer DB anlegen

Beitrag von Michl »

Aliobaba hat geschrieben:Man kommt um das "try" wohl doch nicht herum?
Bei mir funktioniert das, allerdings nutze ich die 7.2beta Version.

Aliobaba hat geschrieben:Außerdem kann mit der "neuen" Spalte immer noch nicht gearbeitet werden
Das stimmt, man muss die Connection neu setzen, dann geht dies (hat etwas mit der internen Speicherverwaltung zu tun):

Code: Alles auswählen

procedure TForm1.NewColClick(Sender: TObject);
begin
  if not Assigned(ZQuery1.FindField('newcol')) then begin
    ZQuery1.Close;
    ZConnection1.ExecuteDirect('ALTER TABLE personen ADD COLUMN newcol VARCHAR(100);');
    ZConnection1.Disconnect;
    ZConnection1.Connect;
    ZQuery1.Open;
  end;
end;
Zuletzt geändert von Michl am Sa 3. Okt 2015, 11:08, insgesamt 1-mal geändert.

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: Zeos: Zur Laufzeit neue Spalte in einer DB anlegen

Beitrag von Aliobaba »

Halio,

bei mir kommt eine Fehlermeldung; auch "newcol" wird nicht angezeigt.
Lt "SQLite Manager" wird "newcol" aber angelegt.

Mein Code:

Code: Alles auswählen

procedure TForm1.Button1Click(Sender: TObject);
begin
   if not Assigned(Form1.QText.FindField('newcol')) then
   begin
    QText.Close// ZQuery1.Close;
    ZConn1.ExecuteDirect('ALTER TABLE "tText" ADD COLUMN "newcol" VARCHAR(100)');
    ZConn1.Disconnect;
    ZConn1.Connect;
    QText.Open; // ZQuery1.Open;
  end;
end;       


Screenshot_SQL2.png


Screenshot_SQL3.png
Screenshot_SQL3.png (20.75 KiB) 1948 mal betrachtet


Screenshot_SQL4.png
Screenshot_SQL4.png (4.77 KiB) 1948 mal betrachtet
"MyMemoryDB" ( https://www.heise.de/download/product/mymemorydb-89626 )

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

Re: Zeos: Zur Laufzeit neue Spalte in einer DB anlegen

Beitrag von Michl »

Ich habe leider kein 7.1.4 mehr installiert um das zu testen. Der Bug wurde in der 7.2 gefixt, aber evtl. nicht erst in dieser Umbaumaßnahme eingepfelgt, sodaß dieser in der 7.1.4 auch vorhanden sein könnte.

Du könntest noch probieren, deine Connection frei zu geben und eine neue zu erstellen, ich denke mal, dadurch wird der Cache der Connection geleert. Du müsstest dabei aber darauf achten, dass alle Querys ihre Daten ordentlich weggeschrieben haben und danach diese wieder mit der neuen Connection verbinden.

Meine Empfehlung wäre aber, die 7.2beta zu installieren. Sie läuft bei mir sehr stabil und man könnte bei einem Bug auf einen schnelleren Fix hoffen.

Code: Alles auswählen

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

hde
Beiträge: 556
Registriert: Mi 11. Aug 2010, 02:56

Re: Zeos: Zur Laufzeit neue Spalte in einer DB anlegen

Beitrag von hde »

@Michl
Wenn ich das richtig sehe, hat aliobaba die Felder als Objekt in Lazarus (DBGrid) angelegt
Gruß hde

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

Re: Zeos: Zur Laufzeit neue Spalte in einer DB anlegen

Beitrag von Michl »

@hde: Danke, jetzt verstehe ich die Frage.

@Aliobaba:
Es geht dir darum, dass falls eine neue Spalte zur Laufzeit hinzugefügt wurde, diese zur Designzeit ebenfalls dargestellt wird?!

Bei mir kann ich zur Designzeit die Connection trennen, die Connection wieder verbinden und die Query neu verbinden, dann mit DBGrid1 -> Felder bearbeiten -> + Felder hinzufügen alle verherigen Felder löschen und neu laden. Dann ist auch die neue Spalte mit vorhanden. Allerdings gehen alle zuvor vorgenommenen Einstellungen der Columns verloren.

Dann verstehe ich auch, warum die neue Spalte zur Laufzeit nicht angezeigt wird. Das Problem ist, dass du zur Designzeit die Connection schon offen bzw. erstellt hast, daher wird der Cache der Connection selbst bei einem Disconnect zur Laufzeit nicht gelöscht. Daher erscheint die neue Spalte auch nicht in der Tabelle zur Laufzeit.

Ich persönlich erledige alle derartigen Aufgaben per Code und lasse den Designer (Lazarus) komplett außen vor. Somit erhalte ich idealerweise auch sämtliche Fehlermeldungen zur Laufzeit und kann gezielt (mit dem Debugger oder meinem Log) den Fehler suchen. Eine Lösung für dein Problem, ohne dass du alle Einstellungen deiner Columns verlierst kenne ich nicht. Evtl. könntest du die .lfm per Hand editieren, dann hat das Zufügen einer Spalte zur Laufzeit für mich aber keinen Sinn.

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: Zeos: Zur Laufzeit neue Spalte in einer DB anlegen

Beitrag von Aliobaba »

Hallo,

erstmal Danke für Eure Mühen (und Geduld!)!

Mein Problem ist folgendes: Ich habe ein Programm veröffentlicht, bei dem mir zwar noch nicht jetzt, aber vielleicht irgendwann die Datensatzspalten in einer Tabelle knapp werden könnten. Um die Kompatibilität zu erhalten wäre es natürlich schon recht gut, wenn nach einem Update auch die zugrunde liegende Datenbank automatisch und vom Anwender unbemerkt um einige Spalten erweitert werden könnte.

Und das Anlegen einer neuen Spalte gelingt ja auch mit folgendem Code: ("X1" und "X2" sind die neuen Spalte)

Code: Alles auswählen

       try
         form1.ZConn1.ExecuteDirect( ' Alter TABLE "tText" ADD "X1" VARCHAR(100)');
         form1.ZConn1.ExecuteDirect( ' Alter TABLE "tText" ADD "X2" VARCHAR(100)');
       except
       end;
 

oder natürlich auch mit dem Code von Michl:

Code: Alles auswählen

procedure TForm1.AddNewColClick(Sender: TObject);
begin
  if not Assigned(ZQuery1.FindField('newcol')) then begin
    ZQuery1.Close;
    ZConnection1.ExecuteDirect('ALTER TABLE sometable ADD COLUMN newcol VARCHAR;');
    ZQuery1.Open;
  end;
end;

Alledings kommt es bei beiden Varianten zu der oben mit Screenshot bereits gezeigten Fehlermeldung: "Duplicate column ..." . Dies ist aber wenig tragisch, man kann die Fehlermeldung ja ignorieren, bzw. während der Programmerstellung festlegen, diesen Fehler nicht mehr zu melden. (Ob das langfristig aber klug ist, weiß ich nicht)
Jedenfalls wird offenbar folgende Abfrage (@Michl) ignoriert:

Code: Alles auswählen

if not Assigned(Form1.QText.FindField('newcol')) then


Aber wie gesagt: Die neuen Spalten werden angelegt und sind auch manchmal !? nutzbar.
Manchmal deshalb, weil man mit folgendem Code völlig problemlos etwas in die Datenbank schreiben kann: ("X1" ist die neue Spalte)

Code: Alles auswählen

procedure TForm1.Bt_lock_setzenClick(Sender: TObject);
begin
        With form1.QText_ID do  // QText_ID !!!!
        Begin
          SQL.Clear;
          SQL.Text := 'UPDATE tText SET X1 = :sneu';
          SQL.Add(' WHERE rID = :sID');
          ParamByName('sneu').AsString  :=  Inttostr(ID_Zufall) ;
          ParamByName('sID').AsString := Form1.QText.FieldByName('rID').AsString;   // von QText! nicht QText_ID !!
          ExecSQL;
          Open;
       end;
end;       

... mit folgendem Code aber einen Feldinhalt leider nicht auslesen kann!! ("X1" ist die neue Spalte)

Code: Alles auswählen

Procedure TForm1.Meine_Procedur;
var
  sss : string;
begin
  sss := Form1.QText_ID.FieldByName('X1').AsString;
end;         
 

Und das ist ausgesprochen schade!!!
Gibt es tatsächlich keine Möglichkeit, zur Laufzeit bei einer Datenbank eine Tabellenspalte neu anzulegen und uneingeschränkt mit dieser Tabelle zu arbeiten?

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

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

Re: Zeos: Zur Laufzeit neue Spalte in einer DB anlegen

Beitrag von Michl »

Aliobaba hat geschrieben:Gibt es tatsächlich keine Möglichkeit, zur Laufzeit bei einer Datenbank eine Tabellenspalte neu anzulegen und uneingeschränkt mit dieser Tabelle zu arbeiten?
Doch, das hatte ich doch weiter oben geschrieben. Du musst deine aktuelle Connection auflösen und eine neue erstellen. AFAIK wird nur so eine leere Connection erstellt, die keine Tabellenstruktur in ihrem Cache gespeichert hat.

MMn die beste Möglichkeit ist dafür, wenn du die Connection nicht schon zur Designzeit aktivierst, sondern erst zur Laufzeit. Dann sind nach einem Update und Neustart deines Programms auch definitiv alle Spalten wie gewohnt verfügbar. Ob Tabellen mit vielen Spalten generell sinnvoll sind, steht auf einem anderen Blatt.

Code: Alles auswählen

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

hde
Beiträge: 556
Registriert: Mi 11. Aug 2010, 02:56

Re: Zeos: Zur Laufzeit neue Spalte in einer DB anlegen

Beitrag von hde »

ZQuery .. Felder bearbeten .. Felder zufügen
legt für jedes Feld im Quellcoide ein Object an: ZQuery1.Feld1; ZQuery1.Feld2 usw .. jedes Feld hat seine Properties ..
das ändern der Datenbankstruktur ändert dies nicht, legt dort nichts ab, creiert keine ZQuery.Felder. Du kannst dann mit den neuen Feldern zwar arbeiten (mit einer anderen Query), aber die vorhandene ZQuery kennt diese neuen Felder zunächst nicht, das muss getrennt behandelt werden.
Gruß hde

Antworten