Entwickler-Ecke

Datenbanken - Feld aus Textdatei in Tabelle einlesen


colaka - So 24.01.10 09:29
Titel: Feld aus Textdatei in Tabelle einlesen
Hallo,

ich möchte aus einer Textdatei den Preis auslesen, um ihn in meine Artikeltabelle zu übernehmen. Das habe ich mit dem folgenden Code gemacht:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
procedure TForm2.Button1Click(Sender: TObject);
var
  Tr : Char;
  d : TextFile;
  Zeile, Rest, Artikelnummer  : String;
  Artikelpreis : Integer;
begin
  Tr := ';';
  assignFile(d, ExtractFilePath(ParamStr(0))+ '519786_CustSpecific_20100123.txt');
  Reset(d);                   // Datei öffnen
  Gauge.Visible := true;
  Gauge.ForeColor := $0099EE;
  Gauge.Progress := 0;
  Gauge.MaxValue := FileSize(d);
  ReadLn(d,Zeile);
  while not EOF(d) do
  begin
    ReadLn(d,Zeile);
    Rest := copy(Zeile, POS(Tr, Zeile)+1, length(Zeile)-7);
    Artikelnummer := copy(Rest, 0, POS(Tr,Rest)-1);
//    Label1.Caption := Artikelnummer;
    Rest := copy(Rest, length(Artikelnummer)+2, (length(Rest)-length(Artikelnummer)));
    Rest := copy(Rest, 0, POS(Tr,Rest)-1);
    Artikelpreis := round(StrToFloat(Rest)*100);  // kalkuliert wird in Cent
//    Label2.Caption := IntToStr(Artikelpreis);
//    label3.Caption := Zeile;
    Table1.Locate('BestNr', Artikelnummer, []);
    Table1.Edit;
    Table1Preis.AsInteger := Artikelpreis;
    Gauge.Progress := FilePos(d);
    Application.ProcessMessages;
  end;
  Table1.Post;
  CloseFile(d);
end;


Das Ganze funktioniert zwar, doch da es sich um ca. 60.000 Artikel handelt, und ich anschließend auf die gleiche Art auch noch den Artikelbestand aus einer anderen Textdatei einlesen muß, dauert es ziemlich lange (geschätzte 30 Minuten). Deshalb habe ich die Frage an die Experten:
Könnte man das vielleicht irgendwie beschleunigen?

Der Aufbau der Textdatei sieht so aus:
Cust_id;Matnr;CustBestPrice;Currency_code;Surcharge
519786;1000039;410,36;EUR;0,00
519786;1000180;26,50;EUR;0,00
519786;1000181;9,64;EUR;0,00
519786;1000186;2304,78;EUR;0,00
519786;1000193;222,11;EUR;0,00
519786;1000209;24,00;EUR;0,00
usw.

Ich bin für jeden Vorschlag dankbar.
Gruß Ebi


Xion - So 24.01.10 10:51

Soweit ich das sehe, ist der Locate-Befehl die Schwachstelle. Du könntest z.B. alle Artikelpreise erstmal in ein Array speichern und am Ende die Tabelle einmal komplett durchlaufen und die Preise setzen (kommt drauf an wieviele Artikel da drin sind und wieviele davon du verändern willst).

Application.ProcessMessages bremst definitv auch.

*stöber*
*such*
*wart*
*grübel*

Achja, da ists ja.

Ich hab das auch mal gemacht, und hab mich gewundert warum der Datenbank-Import so langsam ist. Ich habs dann wie ich gerade sehe durch Table.Filter ersetzt.


Delphi-Quelltext
1:
2:
Table.Filter:='ID = 5230';
Table.Filtered:=True;


Das .Post hab ich übrigens in die Schleife mit rein, trau der Sache sonst nicht.

Edit:
Achja, das mit dem ProcessMessages hab ich so gemacht:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
  if ModalForm<>nil then
    begin
      C:=GetTickCount;
      if (C>=LastUpdate+100)and(ModalForm.Showing) then
        begin
          ModalForm.SetFocus;
          Application.ProcessMessages;
          LastUpdate:=C;
        end;
    end;

(vom Prinzip her wird halt nur maximal alle 0.1 sec ein Update gemacht.)


colaka - So 24.01.10 12:42

Hallo,

ich habe das mit dem Filter probiert, aber das dauert ungefähr 3 mal solange wie meine ursprüngliche Lösung.

Den Vorschlag mit dem Array habe ich leider nicht kapiert, aber es sind ca. 60.000 Artikel und ich muß alle Preise ersetzen. In der Tabelle befinden sich Listenpreise, die ich durch meine kundenspezifischen Preise ersetzen muß.

Könntest Du mir vielleicht ein kurzes Beispiel geben, wie das mit dem Array gehen soll?

Außerdem habe ich soeben festgestellt, daß mein Programm nach dem Ersetzen der Preise ca. 50% mehr Speicherplatz belegt als vorher. Wie kann denn das sein?

Danke Ebi


Xion - So 24.01.10 12:55

user profile iconcolaka hat folgendes geschrieben Zum zitierten Posting springen:

ich habe das mit dem Filter probiert, aber das dauert ungefähr 3 mal solange wie meine ursprüngliche Lösung.
...
Außerdem habe ich soeben festgestellt, daß mein Programm nach dem Ersetzen der Preise ca. 50% mehr Speicherplatz belegt als vorher. Wie kann denn das sein?

Hmm. Am besten du kommentierst mal abwechselnd Teile aus (Locate, Schreiben in die DB, ...) und dann siehst du prima, was wirklich die Zeit beansprucht und den Speicher braucht.

user profile iconcolaka hat folgendes geschrieben Zum zitierten Posting springen:

Den Vorschlag mit dem Array habe ich leider nicht kapiert, aber es sind ca. 60.000 Artikel und ich muß alle Preise ersetzen. In der Tabelle befinden sich Listenpreise, die ich durch meine kundenspezifischen Preise ersetzen muß.

Könntest Du mir vielleicht ein kurzes Beispiel geben, wie das mit dem Array gehen soll?

Hmm, 60.000...ob das in ein Array passt...

Also vom Prinip:

Delphi-Quelltext
1:
2:
3:
4:
type TArtikelPreis=record
  ArtikelNr: integer;
  ArtikelPreis: integer;
end;

dann ein Array

Delphi-Quelltext
1:
ArtikelPreise: array [0..60000of TArtikelPreis;                    

Jetzt so im nachhinein wird mir irgendwie klar, dass es damit nicht schneller gehen wird, wenn dann müsste man das array dann noch sortieren nach der Artikelnr oder so :P

Edit:
In deinem Beispiel ist die Liste schon nach ArtikelNr sortiert...ist das immer so? dann wärs noch einfacher


colaka - So 24.01.10 13:05

Hallo,

ja, das ist schon das Locate, das die Zeit beansprucht. Aber mit dem Filter dauerts erst recht ewig. Das Programm läuft schon eine halbe Stunde und ist erst bei 27%.

Die txt-Datei ist schon nach Artikelnummern sortiert, aber meine Tabelle nicht. Wie könnte man es denn dann vereinfachen?

Danke Ebi


Xion - So 24.01.10 14:04

Also wenn die Datei sortiert ist und die Tabelle nicht, würd ichs mal so probieren:

1. Query erstellen

Delphi-Quelltext
1:
2:
Query:=TQuery.Create(Form);
Query.DatabaseName:='Database.db';


2. Tabelle sortieren

Delphi-Quelltext
1:
2:
3:
4:
Query.SQL.Clear;
Query.SQL.Add('Select ArtikelNr,ID FROM Database ORDER BY ArtikelNr');
Query.Active:=True;
Query.First;


3. Einlesen

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
//ArtikelNr und ArtikelPreis aus Datei lesen
while Query.FieldByName(ArtikelNr).AsInteger<ArtikelNr do
  Query.Next;
if Query.FildByName(ArtikelNr).AsInteger<>ArtikelNr then
  ShowMessage('Artikel '+inttostr(ArtikelNr)+' nicht in DB')
else
  begin
     Table1.First;
     while Table1.FieldByName('ID').AsInteger<Query.FildByName('ID').AsInteger do
       Table1.Next;
     if Table1.FieldByName(ArtikelNr).AsInteger<>Query.FildByName('ID').AsInteger then
       ShowMessage('Artikel '+inttostr(ArtikelNr)+' nicht in DB - eigentlich nicht möglich, da ID ja im Query')
     else
       Table1.FieldByName('ArtikelPreis').AsInteger:=ArtikelPreis;
   end


Damit würdest du dir das Locate oder Filtern sparen...hierbei muss die Tabelle allerdings nach ID sortiert sein...

Code ist ungetestet

Edit:
mir ist grad aufgefallen, dass ist auch nicht so das wahre...bin irgendwie unkreativ heute

Konnte man nicht auch in einem Query schreiben? Glaube schon, dann könntest du den ganzen Table-Bereich weglassen:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
//ArtikelNr und ArtikelPreis aus Datei lesen
while Query.FieldByName(ArtikelNr).AsInteger<ArtikelNr do
  Query.Next;
if Query.FieldByName(ArtikelNr).AsInteger<>ArtikelNr then
  ShowMessage('Artikel '+inttostr(ArtikelNr)+' nicht in DB')
else
  Query.FieldByName('ArtikelPreis').AsInteger:=ArtikelPreis;
Query.Post;


colaka - Mo 25.01.10 08:22

Hallo,

das mit der Query scheint mir doch der beste Weg zu sein. Deshalb habe ich Deinen Code ausprobiert, doch dabei erscheint die Fehlermeldung: "Eine Datenmenge, die nur zum Lesen geeignet ist, kann nicht verändert werden".

Wie kann man denn mit einer Query den Preis überschreiben?

Danke Ebi


Xion - Mo 25.01.10 09:19

user profile iconcolaka hat folgendes geschrieben Zum zitierten Posting springen:

"Eine Datenmenge, die nur zum Lesen geeignet ist, kann nicht verändert werden".


Hmm, das geht wohl nicht per Query...es gibt zwar auch Write Befehle für SQL aber...

http://www.delphipraxis.net/post161366.html
Die lösen es per Index...eigentlich ist deine ArtikelNr ja auch sowas wie ein Index (nur einmal, ein integer).
Alternativ könnte man evtl auch in den Table.Filter sowas wie order by unterbringen.


colaka - Mo 25.01.10 21:33

Hallo,

ich habe jetzt die Tabelle umgestellt und die Artikelnummer zum Primärindex gemacht. Damit werden die 60.000 Datensätze in etwa 1 Minute aktualisiert.

Danke Ebi