Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Datenbanken (https://www.delphipraxis.net/15-datenbanken/)
-   -   Delphi Denkblockade: Wie speicher ich ein Array in einer Datenbank? (https://www.delphipraxis.net/133512-denkblockade-wie-speicher-ich-ein-array-einer-datenbank.html)

Mithrandir 3. Mai 2009 16:31

Datenbank: Firebird Embedded • Version: 2.1.2 • Zugriff über: ZEOS 6.6.4

Denkblockade: Wie speicher ich ein Array in einer Datenbank?
 
Hi ihr,

irgendwie habe ich gerade eine Denkblockade und such mir schon n Wolf:

Ich möchte ein Array, dass bspw. so aufgebaut ist, in eine Firebird-Datenbank schreiben:

Delphi-Quellcode:
  TORPTag = packed record
     Key:  String;
     Value: String;
  end;

  TORPTags = Array of TORPTag;
Ich weiß, dass ich das mittels BLOBs erledigen kann. Dazu gibt es ja auch einige Beispiele, zumindest, wenn es sich um Bilder handelt. Die arbeiten meist alle mit TFileStream/TMemoryStream. Nur, wie bekomme ich ein Array in den Stream? Und wenn die Datenbankabfrage so aussieht:

Delphi-Quellcode:
procedure TORPDataBase.WriteWayToDB(Way: TORPWay);
var
  BlobStr: TStream;
begin
  //Create Query-Object for Way Insert
  fInsertWayQuery := TZQuery.Create(nil);
  fInsertWayQuery.SQL.Text := 'INSERT INTO WAYS (WAY_ID, SUB_NODES, TAGS) VALUES (:way_id, :sub_nodes, :tags) RETURNING "ID"';
  try
    DecimalSeparator := '.';
    with fInsertWayQuery do
    begin
      Connection := fConnection;
      ParamCheck := true;
      ParamByName('way_id').AsInteger := StrToInt(Way.ID);
      ParamByName('tags').AsBlob := ?
      ParamByName('sub_nodes').AsString := EncodeSubNodes(Way.Subnodes);
      ExecSQL;
    end;
  finally
   fInsertWayQuery.Free;
  end;
end;
Wie bekomme ich das Array da drinne unter, ohne, dass mir Zeos das Query um die Ohren haut? Denn ein SQL-Statement ist ja ein Text-Statement, da kann man ja keine Binärdaten unterbringen, oder? :gruebel:

mkinzler 3. Mai 2009 16:36

Re: Denkblockade: Wie speicher ich ein Array in einer Datenb
 
Du musst die einzelnen Komponenten des record mit Write() in den Stream schreiben und das dann in einer Schleife über alle Einträge des Arrays

Dax 3. Mai 2009 16:44

Re: Denkblockade: Wie speicher ich ein Array in einer Datenb
 
Ich glaube, es wäre besser, das Array in einer separaten Tabelle zu speicher. "Tags" klingt, als solle man danach suchen können, und ein Blob ist nicht gerade durchsuchbar :)

mkinzler 3. Mai 2009 16:47

Re: Denkblockade: Wie speicher ich ein Array in einer Datenb
 
Jenach dem wie man die Daten später benötigt. U.U. ist eine weitergehende Normalisierung der Datenbank dann sinnvoll

Mithrandir 3. Mai 2009 17:02

Re: Denkblockade: Wie speicher ich ein Array in einer Datenb
 
@mkinzler, Dax:

:(

Also, ich drösel das Problem mal von Anfang an auf:

Momentan werden die Tags in einer Schleife in die Datenbank geschrieben, und die ID anschließend einem String angehängt. Dieser String wird dann in der Datenbank gespeichert.

Im Quelltext sieht das so aus:

Delphi-Quellcode:
constructor TORPDatabase.Create(DBFileName: string);
begin
{...}
  //Create Query-Object for Tag Insert
  fInsertTagQuery := TZQuery.Create(nil);
  fInsertTagQuery.SQL.Text := 'INSERT INTO TAGS ("KEY", "VALUE") VALUES (:key, :val) RETURNING "ID"';

  //Create Query-Object for Way Insert
  fInsertWayQuery := TZQuery.Create(nil);
  fInsertWayQuery.SQL.Text := 'INSERT INTO WAYS (WAY_ID, SUB_NODES, TAGS) VALUES (:way_id, :sub_nodes, :tags) RETURNING "ID"';
{...}
end;

function TORPDataBase.WriteTagsToDB(Tags: TORPTags): string;
var
  i: integer;
begin
  Result := '';
  DecimalSeparator := '.';
  for i := 0 to Length(Tags) - 1 do
  begin
    with fInsertTagQuery do
    begin
      Connection := fConnection;
      begin
        ParamCheck := true;
        ParamByName('key').AsString := Tags[i].Key;
        ParamByName('val').AsString := Tags[i].Value;
        Open;
        Result := Result + FieldByName('ID').AsString + '|';
      end;
    end;
  end;
end;

procedure TORPDataBase.WriteWayToDB(Way: TORPWay);
var
  BlobStr: TStream;
begin
  try
    DecimalSeparator := '.';
    with fInsertWayQuery do
    begin
      Connection := fConnection;
      ParamCheck := true;
      ParamByName('way_id').AsInteger := StrToInt(Way.ID);
      ParamByName('tags').AsString := WriteTagsToDB(Way.Tags);
      ParamByName('sub_nodes').AsString := EncodeSubNodes(Way.Subnodes);
      ExecSQL;
    end;
  finally

  end;
end;
Ich sehe jetzt 2 Performanceprobleme: Zum einen musste ich gewährleisten, dass mindestens x Unterknoten gespeichert werden können, resp. deren IDs. Dadurch wird das VarChar-Feld ja riesig. Außerdem braucht es für jeden Tag ein "Insert Into" - Statement. Klar, dadurch, dass ich mit Parametern arbeite, geht das schon schneller. Aber durch die Nutzung von Blobs hätte ich mir jetzt einen stärkeren Geschwindigkeitsvorteil und einen geringeren Speicherbedarf auf Kosten der Suchfunktion erhofft. Würde ich mit diesen Mutmaßungen richtige liegen?

mkinzler 3. Mai 2009 17:09

Re: Denkblockade: Wie speicher ich ein Array in einer Datenb
 
Ja. sollte schneller sein als die einzelnen Teile des Arrays getrennt in extra Tabelle zu schreiben

Mithrandir 3. Mai 2009 17:17

Re: Denkblockade: Wie speicher ich ein Array in einer Datenb
 
Zitat:

Zitat von mkinzler
Ja. sollte schneller sein als die einzelnen Teile des Arrays getrennt in extra Tabelle zu schreiben

Ok, danke. Also, oft kann ich mit deinen berühmten "One Linern" ja was anfangen, aber hier:

Zitat:

Zitat von mkinzler
Du musst die einzelnen Komponenten des record mit Write() in den Stream schreiben und das dann in einer Schleife über alle Einträge des Arrays

bin ich etwas überfordert... :mrgreen:


Ein einfaches

Delphi-Quellcode:
fStream.Write(Way.Tags[0], SizeOf(Way.Tags));
Würde als nicht funktionieren?

Aber wie bekomme ich die Daten dann wieder raus? Fragen über Fragen... :gruebel:

Dax 3. Mai 2009 17:26

Re: Denkblockade: Wie speicher ich ein Array in einer Datenb
 
Code:
WriteString (string, stream): stream.Write(string.Length), stream.Write(string[0], string.Length);
WriteRec (rec, stream): WriteStrimg(rec.Key, stream), WriteString(rec.Value, stream);
Und auslesen entsprechend andersrum: Länge lesen, SetLength, Lesen am Stück, Länge... ;)

mkinzler 3. Mai 2009 17:40

Re: Denkblockade: Wie speicher ich ein Array in einer Datenb
 
So hatte ich es gemeint
Delphi-Quellcode:
Writeln( BlobStr, Length( TORPTags)); //Anzahl
for i := Low( TORPTags) to High( TORPTags) do
begin
    WriteLn( BlobStr, TORPTags[i].Key);
    WriteLn( BlobStr, TORPTags[i].Value);
end;

Mithrandir 3. Mai 2009 17:46

Re: Denkblockade: Wie speicher ich ein Array in einer Datenb
 
Hi ihr beiden,

So langsam bröckelt die Mauer vor meinem geistigen Auge. Die herangehensweise über WriteLN find ich dabei interessant. Darauf wäre ich von selbst wohl nicht gekommen... :gruebel:

Danke ihr zwei. ;)

Mithrandir 3. Mai 2009 20:52

Re: Denkblockade: Wie speicher ich ein Array in einer Datenb
 
Hmm... WriteLN kann man aber nicht direkt für TStream nutzen, oder? :gruebel: Bei mir kommt immerhin die Meldung:

Zitat:

[Pascal Fehler] ORP_DataBase.pas(232): E2054 Ungültiger Typ in Write/Writeln-Anweisung

mkinzler 3. Mai 2009 21:02

Re: Denkblockade: Wie speicher ich ein Array in einer Datenb
 
Und mit Write()?

Mithrandir 3. Mai 2009 21:09

Re: Denkblockade: Wie speicher ich ein Array in einer Datenb
 
Genau dasselbe. Außerdem ist mir eingefallen, dass ich die Strings in der Länge begrenzen muss. Sonst weiß ich ja gar nicht, wo was anfängt und aufhört, wenn ich das ganze wieder auslesen will, oder?

mkinzler 3. Mai 2009 21:20

Re: Denkblockade: Wie speicher ich ein Array in einer Datenb
 
Versuche es mal mit einem Zwischen-Stream.

String -> StringStream -> BlobStream -> Datenbankfeld

Mithrandir 3. Mai 2009 21:30

Re: Denkblockade: Wie speicher ich ein Array in einer Datenb
 
Zitat:

Zitat von mkinzler
Versuche es mal mit einem Zwischen-Stream.

String -> StringStream -> BlobStream -> Datenbankfeld

Weißt du aus dem Kopf, ob folgende Idee auch klappen könnte?

Delphi-Quellcode:
//Größe des Records dank String[] bekannt
TORPTag = packed record
     Key:  String[255];
     Value: String[255];
  end;

  TORPTags = Array of TORPTag;

procedure WriteToDB(Tags: TORPTags);
var
 MemoryStream: TMemoryStream;
 i: integer;
begin
 MemoryStream := TMemoryStream.Create;
//...
 for i := Low(Tags) to High(Tags) do
  MemoryStream.Write(Tags[i], Sizeof(TORPTags));
//...
end;
Ich kanns grad leider nicht überprüfen... :stupid:

//Edit: Oder müsste dann das "Packed" raus? :gruebel:
//Edit2: Ist mal eben so aus dem Kopp getippt...

Dax 3. Mai 2009 21:45

Re: Denkblockade: Wie speicher ich ein Array in einer Datenb
 
Das kann man ja nich mit ansehen :(
Delphi-Quellcode:
type
  TTag = record
    Key, Value: string;
  end;

  TTags = array of TTag;

procedure WriteInt(i: Integer; stream: TStream);
begin
  stream.Write(i, sizeof(i));
end;

function ReadInt(stream: TStream): Integer;
begin
  stream.Read(result, sizeof(result));
end;

procedure WriteString(s: string; stream: TStream);
begin
  WriteInt(Length(s), stream);
  stream.Write(s[1], Length(s));
end;

function ReadString(stream: TStream): string;
begin
  SetLength(result, ReadInt(stream));
  stream.Read(result[1], Length(result));
end;

procedure WriteTags(tags: TTags; stream: TStream);
var i: Integer;
begin
  WriteInt(Length(tags), stream);
  for i := 0 to High(tags) do begin
    WriteString(tags[i].Key, stream);
    WriteString(tags[i].Value, stream);
  end;
end;

function ReadTags(stream: TStream): TTags;
var i: Integer;
begin
  SetLength(result, ReadInt(stream));
  for i := 0 to High(Result) do begin
    tags[i].Key := ReadString(stream);
    tags[i].Value := ReadString(stream);
  end;
end;

Mithrandir 3. Mai 2009 21:48

Re: Denkblockade: Wie speicher ich ein Array in einer Datenb
 
Zitat:

Zitat von Dax
Das kann man ja nich mit ansehen :(

Ist das mit mir wirklich so ein Trauerspiel? Ich weiß, ich konnt's mal besser... :cry:

Danke ;)

Mithrandir 7. Mai 2009 10:30

Re: Denkblockade: Wie speicher ich ein Array in einer Datenb
 
Dax,

irgendwie funktioniert ein Teil deines Code nicht. Wenn ich mit ReadTags die Tags auslesen möchte, dann alloziiert er im ersten Durchlauf ~600 MB, den zweiten Durchlauf schafft er gar nicht mehr, weil er dann nicht genug Arbeitsspeicher hat.

Wenn ich mir diese Funktion ansehe, dann sollte man meinen, dass er 4 Byte liest:

Delphi-Quellcode:
function TORPDataBase.ReadInt(Stream: TMemoryStream): Integer;
begin
  stream.Read(result, sizeof(Integer));
end;
Stattdessen springt der Speicherverbrauch auf 600 MB... :gruebel:

[edit=mkinzler]Delphi-Tag eingefügt Mfg, mkinzler[/edit]

Dax 7. Mai 2009 14:05

Re: Denkblockade: Wie speicher ich ein Array in einer Datenb
 
Vorweg: natürlich kann ich mich hinsichtlich der APIs irren, aber ich glaube, dass Read und Write Parameter (Pointer,Länge) hatten ;)

Ich bezweifle aber stark, dass es an der Methode liegt. Tritt das Problem denn immer auf, oder erst ab einer gewissen Tagmenge?

Mithrandir 7. Mai 2009 14:34

Re: Denkblockade: Wie speicher ich ein Array in einer Datenb
 
Zitat:

Zitat von Dax
Vorweg: natürlich kann ich mich hinsichtlich der APIs irren, aber ich glaube, dass Read und Write Parameter (Pointer,Länge) hatten ;)

Ich bezweifle aber stark, dass es an der Methode liegt. Tritt das Problem denn immer auf, oder erst ab einer gewissen Tagmenge?

Hi,

ja, das Verhalten tritt immer auf.

So schreibe ich die Knoten und die dazugehörigen Tags in die Datenbank:

Delphi-Quellcode:
procedure TORPDataBase.WriteNodeToDB(Node: TORPNode);
var
  Blob: TMemoryStream;
begin
  Blob := TMemoryStream.Create;
  Blob.Seek(0,soFromBeginning);
  try
    DecimalSeparator := '.';
    with fInsertNodeQuery do
    begin
      Connection := fConnection;
      ParamCheck := true;

      WriteTags(Node.tags, Blob);

      Params.CreateParam(ftBlob, 'tags', ptInput);
      ParamByName('tags').LoadfromStream(Blob, ftBlob);

      ParamByName('lon').AsFloat := StrToFloat(Node.Lon);
      ParamByName('lat').AsFloat := StrToFloat(Node.Lat);
      ParamByName('node_id').AsInteger := StrToInt(Node.ID);
      ExecSQL;
    end;
  finally
    FreeAndNil(Blob);
  end;
end;
Und so lese ich sie wieder aus:

Delphi-Quellcode:
function TORPDataBase.ReadNodesFromDB(): TORPNodes;
var
  BlobStream: TStream;
  Blob: TMemoryStream;
  fSelectNodeQuery: TZQuery;
  i: integer;
begin
  try
    DecimalSeparator := '.';
    fSelectNodeQuery := TZQuery.Create(nil);
    with fSelectNodeQuery do
    begin
      Connection := fConnection;
      ParamCheck := true;
      SQL.Text := 'SELECT * FROM NODES';
      Open;
      While not fSelectNodeQuery.Eof do
      begin
        Blob := TMemoryStream.Create;
        Blob.Seek(0,soFromBeginning);

        SetLength(Result,Length(Result)+1);
        i := High(Result);

        Result[i].ID := FieldByName('ID').AsString;
        Result[i].Lat := FieldByName('Lat').AsString;
        Result[i].Lon := FieldByName('Lon').AsString;

        BlobStream := CreateBlobStream(FieldByName('Tags'),bmRead);

        try
          Blob.CopyFrom(BlobStream, 0);
        finally
          FreeAndNil(BlobStream);
        end;

        Result[i].Tags := ReadTags(Blob);

        fSelectNodeQuery.Next;
      end;
    end;
  finally
    FreeAndNil(Blob);
  end;
end;
Jede wette, da steckt nur irgendein kleiner dämlicher Fehler drin? :gruebel:

Ich hatte tatsächlich die Tags vergessen? Oh man.. :gruebel:

Mithrandir 7. Mai 2009 18:05

Re: Denkblockade: Wie speicher ich ein Array in einer Datenb
 
:wall:

Dax ist vermutlich unschuldig, ich bin nur zu dämlich, BLOBs zu schreiben. Denn mit der U_Store_Dynamic_Params.pas von sirius kann ich das Array in einem FileStream speichern und auch wieder lesen. Nur mit den BLOBs nicht.. :wall:

Kennt jemand ein Tutorial, dass die Verwendung von BLOBS mit Zeos und Firebird en Detail beschreibt? Am Besten ein richtiges Anfängertutorial, "Blobs for Dummies", mit allen Fehlern, die man machen kann... :stupid:

Nu' gehts:

Schreiben:
Delphi-Quellcode:
procedure TORPDataBase.WriteNodeToDB(Node: TORPNode);
var
  Blob: TMemoryStream;
  i: integer;
begin
  Blob := TMemoryStream.Create;
  Blob.Seek(0,soFromBeginning);
  try
    DecimalSeparator := '.';
    with fInsertNodeQuery do
    begin
      Connection := fConnection;
      ParamCheck := true;

      SaveToStream(Node.Tags, Blob, TypeInfo(TORPTags), SizeOf(Node.Tags));

      Params.CreateParam(ftBlob, 'tags', ptInput);
      ParamByName('tags').LoadfromStream(Blob, ftBlob);

      ParamByName('lon').AsFloat := StrToFloat(Node.Lon);
      ParamByName('lat').AsFloat := StrToFloat(Node.Lat);
      ParamByName('node_id').AsInteger := StrToInt(Node.ID);
      ExecSQL;
    end;
  finally
    FreeAndNil(Blob);
  end;
end;
Lesen:
Delphi-Quellcode:
function TORPDataBase.ReadNodesFromDB(): TORPNodes;
var
  BlobStream: TStream;
  fSelectNodeQuery: TZQuery;
  i: integer;
begin
    DecimalSeparator := '.';
    fSelectNodeQuery := TZQuery.Create(nil);
    with fSelectNodeQuery do
    begin
      Connection := fConnection;
      ParamCheck := true;
      SQL.Text := 'SELECT * FROM NODES';
      Open;
      While not fSelectNodeQuery.Eof do
      begin
        SetLength(Result,Length(Result)+1);
        i := High(Result);

        Result[i].ID := FieldByName('ID').AsString;
        Result[i].Lat := FieldByName('LAT').AsString;
        Result[i].Lon := FieldByName('LON').AsString;

        BlobStream := CreateBlobStream(FieldByName('TAGS'), bmRead);

        LoadFromStream(BlobStream, Result[i].Tags, TypeInfo(TORPTags), SizeOf(Result[i].Tags));

        FreeAndNil(BlobStream);

        fSelectNodeQuery.Next;
      end;
    end;
end;
Benötigt wird noch diese Unit von sirius


Alle Zeitangaben in WEZ +1. Es ist jetzt 19:41 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