Discussion:
Datenbanken in Delphi7/Kylix3
(zu alt für eine Antwort)
Matthias Hanft
2011-03-16 17:19:10 UTC
Permalink
Hallo,

ich habe da ein sehr umfangreiches Projekt, das ich damals von vornherein
höchst betriebssystemunabhängig geschrieben habe und das ich tatsächlich
bis heute ohne Änderung (naja, ein paar gaaanz wenige "$IFDEF WIN32/LINUX"
gibts) sowohl unter Delphi 7 als auch Kylix 3 compilieren kann. Und das
soll auch so bleiben :-)

Jetzt käme allerdings die Erfordernis dazu, eine kleine Datenbank dazu-
zubauen. Nichts großes, steht auch nie viel drin. Im Prinzip könnte man
das auch in selbergebaute Text- oder INI-Dateien o.ä. schreiben (oder
natürlich ganz simpel ein File of Record), allerdings müßte man dann
halt die ganzen Speicher- und Suchalgorithmen etc. selber programmieren,
und wozu gibt es denn die ganzen schönen Datenbanken, wo man einfach
"SELECT irgendwas WHERE DINGS=1234" schreibt und das kriegt, was man
will.

Mitliefern wollte ich Firebird Embedded, das geht unter Windows auf
jeden Fall, und unter Linux wird es nicht "offiziell" unterstützt;
es gibt aber wenigstens eine "offizielle" "How-To"-Seite dafür.

Unter Delphi 7 verwende ich (mit einem Firebird 2.0 Server) i.d.R. die
mitgelieferte IBSQL-Komponente (jaja, weiß schon, überholt, buggy etc.,
aber bei mir tut die eigenartigerweise bis heute völlig problemlos),
aber bei Kylix gibts datenbankmäßig *nur* den "dbExpress"-Tab in der
IDE.

Also gut, dachte ich mir, den gibts in Delphi 7 ja auch, und da scheint
dann zwischen meinem EXE und GDS32.DLL einfach nur noch ein DBEXPINT.DLL
dazwischengeschaltet zu sein, was ich dann mit meiner Software auslie-
fern müßte. (Unter Linux ist es wohl ähnlich - habe ich noch nicht bis
ins letzte eruiert.)

Ich bin jetzt gerade dabei, die dbExpress-Sachen erst mal unter Windows
einzubauen. Im Prinzip geht das mit der TSQLConnection und der TSQLQuery
auch (inkl. Transactions); mißtrauisch wurde ich jetzt allerdings, als
die Parameterübergabe fehlschlug:

with SQLQueryInsert do begin
ParamByName('MANDANT').AsInteger:=1;
ParamByName('KUNDE').AsInteger:=100;
ParamByName('RENR').AsString:='102011039001';
ParamByName('BIC').AsString:='TESTDEFFXXX';
ParamByName('IBAN').AsString:='DE99123456781234567890';
ParamByName('SEQTP').AsSmallInt:=0;
try
ExecSQL(True) // Direct=True, keine Ergebnismenge (Result=RowsAffected)
except
on E: Exception do
ShowMessage(E.Message)
end
end; (* with SQLQueryInsert *)

bringt Fehler 101: "SQL-Server-Fehler: Incorrect values within SQLDA structure".

Schreibt man die Parameter dagegen direkt in die SQL-Anweisung:

SQLQueryInsert.SQL.Clear;
SQLQueryInsert.SQL.Append('INSERT into TESTTABLE (MANDANT, KUNDE, RENR, BIC, IBAN, SEQTP)');
SQLQueryInsert.SQL.Append('values (1, 100, ''102011039001'',
''TESTDEFFXXX'', ''DE99123456781234567890'', 0);');
try
SQLQueryInsert.ExecSQL(True) // Direct=True, keine Ergebnismenge (Result=RowsAffected)
except
on E: Exception do
ShowMessage(E.Message)
end;

dann funktioniert's perfekt.

Nun könnte ich das natürlich so tun (auch bei SELECT-Abfragen), aber mittler-
weile beschleichen mich leise Zweifel, ob ich nicht doch ganz furchtbar auf
dem Holzweg bin, indem ich so alte und evtl. buggy Komponenten benutze (und
die "offiziell nicht unterstützte Firebird-Embedded-Installation" auf Linux
klingt auch nicht besonders einfach - unter Windows nimmt man halt Install-
shield oder sowas und fertig).

(Natürlich habe ich nach dieser SQLDA-Fehlermeldung gegooglet, aber außer
andere Leute mit dem gleichen Problem nichts weiter gefunden.)

Jetzt bin ich ein bißchen am Grübeln: Sollte ich...
a) ...den eingeschlagenen Weg weitermachen? Müßte dann halt alle SQL-
Queries ohne Parameter schreiben (wenn ich nicht noch herausbekomme, wo
die Flinte im Pfeffer... äh... der Hase im Korn... liegt), hätte aber
ohne besondere Programmierung den großen Komfort eines SQL-Servers.
b) ...andere/aktuellere Komponenten verwenden? Da wird es aber schwer werden,
heutzutage noch etwas zu finden, das in Delphi 7 und Kylix 3 gleicher-
maßen funktioniert.
c) ...eine andere/moderne Datenbank (inkl. "Zubehör") nehmen? SQLite scheints
für Windows und Linux zu geben, sicher findet man da auch was für Delphi,
aber wie kommt man da von Kylix aus ran?! Irknwie libsqlite3.so aufrufen?
d) ...aufgeben und den "Datenbank-Teil" meines Projekts nur für Windows an-
bieten? (Da könnte ich mit meinem gewohnten, stabilen und robusten IBSQL
und FB-Embedded ja weitermachen.) Wäre aber schade, weil gerade die
"Zweisprachigkeit" (Windows/Linux) meiner Software schon was Besonderes
ist und ich davon nur ungern abrücken würde.
e) ...nur den Datenbankgedanken aufgeben und alles in ein File of Record
schreiben und dann eben selber suchen etc.? Ich kann noch nicht genau
abschätzen, wie groß das alles wird, aber die Recordgröße seht Ihr oben
(ca. drei Integer, drei Strings, evtl. noch ein Timestamp o.ä.), und die
Anzahl der Records nachher in Gebrauch wird vermutlich normalerweise in
der Größenordnung "ein paar hundert", maximal "ein paar tausend" liegen.
(Natürlich würde man das File of Records dann in eine eigene "Datenbank-
klasse" kapseln, wo es Methoden wie "GetRecordsByKunde(const aKunde: Integer)"
o.ä. gäbe.) Das würde zwar anfangs etwas Schreibarbeit erfordern, aber
sicher zum gewünschten Ziel führen (und man hätte jegliche Abhängigkeit
von irgendwelchen Datenbanken, Treibern etc. eliminiert). Ich weiß halt
bloß nicht, wie schnell das geht, z.B. 1000 Records von einem TFileStream
einzulesen. Aber es liefe 100% identisch unter Windows und Linux (und die
$IFDEF beschränken sich dabei höchstens noch auf das Verzeichnis: Hier
CSIDL_COMMON_APPDATA, dort /var/db o.ä.).
Ok, man müßte das mit dem gleichzeitigen Zugriff regeln. Der kommt aber
im gegebenen Anwendungsfall i.d.R. eh nicht vor. Oder man schreibt in die
Bedienungsanleitung, daß man das Programm nur einmal gleichzeitig starten
darf :-)
f) oder gibts noch irgendwelche Möglichkeiten, an die ich gerade nicht denke?

Momentan tendiere ich am ehesten in Richtung e) (es muß auch mal fertig
werden, und bei allen anderen Varianten sehe ich immer noch mögliche
Unwägbarkeiten auf mich zukommen). Auf jeden Fall schlafe ich da jetzt
erst nochmal drüber, bis ich mich endgültig entscheide (und Eure zahl-
reichen Antworten und Ratschläge lese) :-)

Danke schon mal (auch fürs Durchlesen bis hier) :-)

Gruß Matthias.
Achim Kalwa
2011-03-21 18:06:31 UTC
Permalink
Hallo,

Matthias Hanft wrote:

[...sehr viel...]
Post by Matthias Hanft
mißtrauisch wurde ich jetzt allerdings, als
with SQLQueryInsert do begin
ParamByName('MANDANT').AsInteger:=1;
ParamByName('KUNDE').AsInteger:=100;
ParamByName('RENR').AsString:='102011039001';
ParamByName('BIC').AsString:='TESTDEFFXXX';
ParamByName('IBAN').AsString:='DE99123456781234567890';
ParamByName('SEQTP').AsSmallInt:=0;
try
ExecSQL(True) // Direct=True, keine Ergebnismenge (Result=RowsAffected)
Da hast Du was falsch verstanden ;-)

ExecSQL(False);

wäre die korrekte Lösung.

ExecDirect zeigt an, dass die Query vor der Ausführung nicht vorbereitet
(prepared) werden muss. Dieser Wert kann auf True
gesetzt werden, wenn die Query keine anderen Parameter enthält.

HTH
Achim
Matthias Hanft
2011-03-22 08:30:19 UTC
Permalink
Post by Achim Kalwa
Post by Matthias Hanft
ExecSQL(True) // Direct=True, keine Ergebnismenge (Result=RowsAffected)
Da hast Du was falsch verstanden ;-)
ExecSQL(False);
wäre die korrekte Lösung.
ExecDirect zeigt an, dass die Query vor der Ausführung nicht vorbereitet
(prepared) werden muss. Dieser Wert kann auf True
gesetzt werden, wenn die Query keine anderen Parameter enthält.
Das habe ich nicht falsch verstanden, sondern falsch gelesen :-) In der Tat steht
in der Hilfe "Wenn die Abfrage keine Parameter enthält, kann ExecDirect auf true
gesetzt werden"; gelesen habe ich stattdessen "Wenn die Abfrage keine Ergebnis-
menge zurück liefert, kann ExecDirect auf true gesetzt werden." Vermutlich war
es schon spät am Abend... :-)

Habe es aber inzwischen doch schon mit Records und FileStreams gelöst (nachdem
ich die Art und Menge der zu speichernden Daten eh noch stark vereinfachen und
reduzieren konnte). Ich werde sogar mal probieren, die "Datenbank" komplett in
den Speicher zu lesen - ein Record hat mittlerweile 256 Bytes; die Anzahl der
Records kann ich noch nicht so genau abschätzen, wird aber wohl so zwischen
"ein paar hundert" bis "ein paar tausend" liegen. Mit z.B. 5.000 Records (was
schon eher viel ist) wäre die gesamte "Datenbank" "nur" 1,2 MB groß; das würde
ich bei Programmstart einfach mit (sinngemäß)

var
myDb: array of TMyRecord;
begin
SetLength(myDb, 5000);
myFileStream.ReadBuffer(mbDb[0], 5000*SizeOf(TMyRecord));

einlesen, dann brauchen die (häufigen) Lesezugriffe nachher nur noch im Speicher
stattfinden statt einzelne 256-Byte-Records von der Platte lesen zu müssen.

Ob ich das dann noch sortiere und/oder einen Index anlege und/oder eine binäre
Suche durchführe, wird sich anhand des Laufzeitverhaltens noch zeigen...

Gruß Matthias.

Loading...