| Autor |
Beitrag |
CBRABO
Hält's aus hier
Beiträge: 10
WinXP
Delphi 2007 pro
|
Verfasst: So 23.09.07 13:04
Hallo Zusammen!
Nachdem ich ohne Ende Ärger mit der BDE hatte, habe ich mich nun durchgerungen, mein aktuelles Projekt vollständig auf den Firebase embedded umzustellen. Das Anlegen mit der IBOConsole hat auch alles super geklappt und ich muss gestehen, ich bin auch sehr begeistert davon.
Zunächst hatte ich überlegt, sämtliche TTable-Komponenten einfach gegen TIBTable zu tauschen. Da aber ohnehin noch einiges an Umstellungsaufwand notwendig ist und ich nun ohnehin die ganze Anwendung nochmal auseinandernehmen muss, wollte es dann auch ordentlich machen und gleich mit TIBDataSet arbeiten.
Leider gelingt mir jedoch die Master-Detail-Verknüpfung noch nicht richtig. Auch die Recherche in verschiedenen Foren und auch Google hat mich nicht wirklich zum Ziel führen können.
Ich habe zunächst nur mal mit zwei Tabellen begonnen. In der Tabelle T_Objekt gibt es die Felder ID_OBJEKT, STRASSE, PLZ, ORT, INDABRTEXT und in der Tabelle T_Jahr gibt es die Felder ID_JAHR, ID_OBJEKT, JAHR. Ziel ist es, zu jedem Objekt eine beliebige Zahl von Jahren anzulegen.
Die META-Daten:
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: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82:
| SET SQL DIALECT 3;
CREATE DOMAIN "BOOL" AS SMALLINT CHECK ((VALUE = 0) OR (VALUE = 1)); CREATE DOMAIN "DECIMALANTEILE" AS DECIMAL(9, 4); CREATE DOMAIN "EURO" AS DECIMAL(9, 2); CREATE DOMAIN "FLOATQM" AS FLOAT CHECK (VALUE >= 0); CREATE DOMAIN "INTID" AS INTEGER; CREATE DOMAIN "INTJAHR" AS INTEGER CHECK ((VALUE >= 1900) AND (VALUE <= 2099)); CREATE DOMAIN "INTSORT" AS INTEGER; CREATE DOMAIN "SMALLINT" AS SMALLINT; CREATE DOMAIN "STR10" AS VARCHAR(10); CREATE DOMAIN "STR100" AS VARCHAR(100); CREATE DOMAIN "STR20" AS VARCHAR(20); CREATE DOMAIN "STR250" AS VARCHAR(250); CREATE DOMAIN "STR30" AS VARCHAR(30); CREATE DOMAIN "STR50" AS VARCHAR(50);
CREATE TABLE "T_JAHR" ( "ID_JAHR" INTID NOT NULL, "ID_OBJEKT" INTID NOT NULL, "JAHR" INTJAHR , CONSTRAINT "PK_T_JAHRE" PRIMARY KEY ("ID_JAHR") );
CREATE TABLE "T_OBJEKT" ( "ID_OBJEKT" INTID NOT NULL, "STRASSE" STR50 , "PLZ" STR10 , "ORT" STR50 , "INDABRTEXT" STR250 , "BEMERKUNG" STR250 , CONSTRAINT "PK_T_OBJEKT" PRIMARY KEY ("ID_OBJEKT") );
CREATE INDEX "IDX_T_OBJEKT_STRASSE" ON "T_OBJEKT"("STRASSE"); ALTER TABLE "T_JAHR" ADD CONSTRAINT "FK_T_JAHR_OBJEKT" FOREIGN KEY ("ID_OBJEKT") REFERENCES "T_OBJEKT" ("ID_OBJEKT") ON UPDATE CASCADE ON DELETE CASCADE;
CREATE GENERATOR "GEN_JAHR_ID"; CREATE GENERATOR "GEN_OBJEKT_ID"; SET TERM ^ ;
CREATE TRIGGER "TRIG_JAHR_ID" FOR "T_JAHR" ACTIVE BEFORE INSERT POSITION 0 as BEGIN NEW.ID_JAHR = GEN_ID (GEN_JAHR_ID, 1); END ^
CREATE TRIGGER "TRIG_OBJEKT_ID" FOR "T_OBJEKT" ACTIVE BEFORE INSERT POSITION 0 as BEGIN NEW.ID_OBJEKT = GEN_ID (GEN_OBJEKT_ID, 1); END ^
COMMIT WORK ^ SET TERM ;^
|
In Delphi habe ich dann zwei IBDataSets und zwei Grids zum Testen benutzt. Im IBDataSet für T_Jahr habe ich folgende SQLs hinterlegt (bzw. mit dem Datenmengen-Editor angelegt).
SelectSQL:
SQL-Anweisung 1: 2:
| select * from T_JAHR WHERE ID_OBJEKT = :ID_OBJEKT |
RefreshSQL:
SQL-Anweisung 1: 2: 3: 4: 5: 6: 7:
| Select ID_JAHR, ID_OBJEKT, JAHR from T_JAHR where ID_JAHR = :ID_JAHR |
InsertSQL:
SQL-Anweisung 1: 2: 3: 4:
| insert into T_JAHR (ID_OBJEKT, JAHR) values (:ID_OBJEKT, :JAHR) |
ModifySQL:
SQL-Anweisung 1: 2: 3: 4: 5: 6:
| update T_JAHR set ID_OBJEKT = :ID_OBJEKT, JAHR = :JAHR where ID_JAHR = :OLD_ID_JAHR |
DeleteSQL:
SQL-Anweisung 1: 2: 3:
| delete from T_JAHR where ID_JAHR = :OLD_ID_JAHR |
Das Ganze funktioniert auch eigentlich ganz prima, was den SELECT-Bereich angeht. Sobald ich im Objekt-Grid einen Datensatz auswähle, werden auch im Jahr-Grid nur die entsprechenden Datensätze angezeigt.
Was jedoch noch nicht läuft, ist die Anlage eines Datensatzes. Hier sollte das Feld ID_OBJEKT eigentlich mit der ID_OBJEKT von T_OBJEKT automatisch belegt werden. Zumindest klappte das mit der BDE so immer.
DataSource von der TIBDataSet der T_JAHR steht auch auf die DataSource, die mit TIBDataSet von T_OBJEKT verbunden ist.
Vielleicht hat jemand eine Idee, wie man dieses Problem in den Griff bekommt.
Vielen Dank schon mal im Voraus.
Viele Grüße
Christoph
|
|
hansa
      
Beiträge: 3079
Erhaltene Danke: 9
|
Verfasst: Mo 24.09.07 01:35
Ich glaube, ich sehs. Trotz IBOconsole.
CBRABO hat folgendes geschrieben: | CREATE TABLE "T_JAHR"
(
"ID_JAHR" INTID NOT NULL,
"ID_OBJEKT" INTID NOT NULL,
"JAHR" INTJAHR ,
CONSTRAINT "PK_T_JAHRE" PRIMARY KEY ("ID_JAHR")
);
CREATE TABLE "T_OBJEKT"
(
"ID_OBJEKT" INTID NOT NULL,
"STRASSE" STR50 ,
"PLZ" STR10 ,
"ORT" STR50 ,
"INDABRTEXT" STR250 ,
"BEMERKUNG" STR250 ,
CONSTRAINT "PK_T_OBJEKT" PRIMARY KEY ("ID_OBJEKT")
);
|
Die Objekte sollen beliebig viele Jahre referenzieren können, oder wie ? Und wo bitte sehr ist die Referenz, die das Objekt für die Table Jahr braucht ?  Das Jahr hat zwar ein ID_OBJEKT, aber das ist verkehrt rum gedacht. So wie es da steht, hat jedes Jahr einige Objekte. Die Objekte haben aber nicht mehrere Jahre. Von Foreign Keys sehe ich gar nichts.
Allgemein fällt mir aber noch folgendes auf : der SQL-Quelltext lehnt sich zu sehr an Delphi an. Das fängt mit dem Prefix T_ schon an. Das Ganze hat mit Typen in Delphi nichts zu tun. Dafür werden aber quasi-Typen (Domains) deklariert. Gefährliche Sache ! Sofern Parameter gebraucht werden und das wird nicht ausbleiben, dann hauen die dir die DB um die Ohren. Stored Procedures usw., die können nichts damit anfangen. Zuerst müssen die Tabellenstrukturen stimmen, aber zu 100 % !
Für BDE-Umsteiger würde ich außerdem dringend empfehlen, ein anständiges Admin-Tool wie IBExpert zu verwenden und keines, das noch "" überall braucht. Das ist ja ätzend. 
_________________ Gruß
Hansa
|
|
CBRABO 
Hält's aus hier
Beiträge: 10
WinXP
Delphi 2007 pro
|
Verfasst: Mo 24.09.07 09:52
Hallo Hansa!
Vielen Dank für Deine Antwort.
Sorry, den Foreign hatte ich nicht mitgepostet, aber schon angelegt.
Hier nochmal das gesamte SQL-Skript :
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: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82:
| SET SQL DIALECT 3;
CREATE DOMAIN "BOOL" AS SMALLINT CHECK ((VALUE = 0) OR (VALUE = 1)); CREATE DOMAIN "DECIMALANTEILE" AS DECIMAL(9, 4); CREATE DOMAIN "EURO" AS DECIMAL(9, 2); CREATE DOMAIN "FLOATQM" AS FLOAT CHECK (VALUE >= 0); CREATE DOMAIN "INTID" AS INTEGER; CREATE DOMAIN "INTJAHR" AS INTEGER CHECK ((VALUE >= 1900) AND (VALUE <= 2099)); CREATE DOMAIN "INTSORT" AS INTEGER; CREATE DOMAIN "SMALLINT" AS SMALLINT; CREATE DOMAIN "STR10" AS VARCHAR(10); CREATE DOMAIN "STR100" AS VARCHAR(100); CREATE DOMAIN "STR20" AS VARCHAR(20); CREATE DOMAIN "STR250" AS VARCHAR(250); CREATE DOMAIN "STR30" AS VARCHAR(30); CREATE DOMAIN "STR50" AS VARCHAR(50);
CREATE TABLE "T_JAHR" ( "ID_JAHR" INTID NOT NULL, "ID_OBJEKT" INTID NOT NULL, "JAHR" INTJAHR , CONSTRAINT "PK_T_JAHRE" PRIMARY KEY ("ID_JAHR") );
CREATE TABLE "T_OBJEKT" ( "ID_OBJEKT" INTID NOT NULL, "STRASSE" STR50 , "PLZ" STR10 , "ORT" STR50 , "INDABRTEXT" STR250 , "BEMERKUNG" STR250 , CONSTRAINT "PK_T_OBJEKT" PRIMARY KEY ("ID_OBJEKT") );
CREATE INDEX "IDX_T_OBJEKT_STRASSE" ON "T_OBJEKT"("STRASSE"); ALTER TABLE "T_JAHR" ADD CONSTRAINT "FK_T_JAHR_OBJEKT" FOREIGN KEY ("ID_OBJEKT") REFERENCES "T_OBJEKT" ("ID_OBJEKT") ON UPDATE CASCADE ON DELETE CASCADE;
CREATE GENERATOR "GEN_JAHR_ID"; CREATE GENERATOR "GEN_OBJEKT_ID"; SET TERM ^ ;
CREATE TRIGGER "TRIG_JAHR_ID" FOR "T_JAHR" ACTIVE BEFORE INSERT POSITION 0 as BEGIN NEW.ID_JAHR = GEN_ID (GEN_JAHR_ID, 1); END ^
CREATE TRIGGER "TRIG_OBJEKT_ID" FOR "T_OBJEKT" ACTIVE BEFORE INSERT POSITION 0 as BEGIN NEW.ID_OBJEKT = GEN_ID (GEN_OBJEKT_ID, 1); END ^
COMMIT WORK ^ SET TERM ;^
|
Mit den "" gebe ich Dir völlig Recht. Das war auch gleich die erste Hürde. Ich kannte das von SQL nicht und wenn man dann beim Anlegen des Triggers ständig zu hören bekommt, es gäbe kein Feld ID, obwohl man es angelegt hat (nur leider unter "id"), dann könnte man schon mal in die Tastatur beißen.
Mit den Domains wollte ich eigentlich erreichen, dass ich nachher keine Probleme habe. Denn, so dachte ich, wäre gewährleistet, dass die Typen auf jeden Fall gleich sind, wenn ich Parameter und Stored Procedures einsetze. Ich hatte einiges in der Anwendung implementiert, was auf jeden Fall mit Stored Procedures deutlich einfach zu gehen scheint. Aber wer aus der BDE-Welt kommt, macht es sich wohl ohnehin gern mal etwas komplizierter. Aber man muss dann einfach mal seinen Mut zusammen nehmen und auf eine richtige Lösung setzen oder so wie ich einfach mal richtig auf die Nase fliegen mit der BDE...
Eigentlich stört mich etwas, dass ich die ID in der Tabelle mit den Objekten als ID_OBJEKT und in der Tabelle mit den Jahren auch als ID_OBJEKT bezeichnen muss. Habe das im Forum gelesen, dass es sonst mit der TDataSet in Verbindung mit einer Master-Detail nicht klappen würde, wenn das referenzierte Feld nicht die gleiche Bezeichnung hat. Das wird bei den JOINS doch irgendwie ein ziemliches Durcheinander, würde ich sagen.
Ich hatte gestern auch noch mal ein Wenig probiert und einfach mal in der AfterInsert der Detail-TDataSet den Wert einfach "manuell" für ID_OBJEKT gesetzt. Das funktioniert zwar, scheint mir aber eher gebastelt als eine wirkliche Lösung zu sein.
Viele Grüße
Christoph
|
|
hansa
      
Beiträge: 3079
Erhaltene Danke: 9
|
Verfasst: Mo 24.09.07 14:37
CBRABO hat folgendes geschrieben: | | Mit den Domains wollte ich eigentlich erreichen, dass ich nachher keine Probleme habe...was auf jeden Fall mit Stored Procedures deutlich einfach zu gehen scheint... |
Du musst die Antworten auch lesen. Domains und SPs geht nicht !
_________________ Gruß
Hansa
|
|
mkinzler
      
Beiträge: 4106
Erhaltene Danke: 13
Delphi 2010 Pro; Delphi.Prism 2011 pro
|
Verfasst: Mo 24.09.07 15:08
| Zitat: | | Du musst die Antworten auch lesen. Domains und noch SPs geht nicht ! |
_________________ Markus Kinzler.
|
|
CBRABO 
Hält's aus hier
Beiträge: 10
WinXP
Delphi 2007 pro
|
Verfasst: Mo 24.09.07 20:25
Doch, gelesen habe ich Deine Antwort und auch vielen Dank für den wichtigen Hinweis.
Du hast mich aber offensichtlich falsch verstanden. Ich meinte damit, dass ich genau diese Probleme mit den Domains vermeiden wollte, als ich die DB angelegt habe. Ich habe aber doch erst durch Deinen Hinweis erfahren, dass das genau falsch herum gedacht war und mir mehr Probleme macht als es löst. Da hatten wir irgendwie aneinander vorbei geredet.
Habe mir eben mal der IBExpert als Personal Edition runtergeladen und werde es gleich mal ausprobieren.
Hast Du denn eine Idee, ob man das mit der IBDataSet auch anders lösen kann, als manuell per AfterInsert das referenzierte Feld zu setzen. Es läuft zwar so. Das scheint mir aber irgendwie unsauber zu sein. Mit dem Select und dem kaskadierten Löschen funktioniert übrigens einwandfrei, so wie es sein soll.
|
|
mkinzler
      
Beiträge: 4106
Erhaltene Danke: 13
Delphi 2010 Pro; Delphi.Prism 2011 pro
|
Verfasst: Mo 24.09.07 20:31
Ab FireBird 2.1 kann man Domains innerhalb SP verwenden.
_________________ Markus Kinzler.
|
|
hansa
      
Beiträge: 3079
Erhaltene Danke: 9
|
Verfasst: Mo 24.09.07 20:35
Schaffe zuerst die "" weg. Lege mit IBExpert die DB neu an. Dialect 3 einstellen ! Und dann damit testen. Sollte das gehen, dann erst mit Delphi das Programm realisieren.
@gelber Kasten : FB 2.1 sollte ziemlich egal sein. Es wurde gesagt : IBX. Also besser vergessen.  FB 2.1 ist noch nicht stabil.
_________________ Gruß
Hansa
|
|
mkinzler
      
Beiträge: 4106
Erhaltene Danke: 13
Delphi 2010 Pro; Delphi.Prism 2011 pro
|
Verfasst: Mo 24.09.07 20:44
| Zitat: | FB 2.1 ist noch nicht stabil. |
Meiner Erfahrung nach schon.
_________________ Markus Kinzler.
|
|
CBRABO 
Hält's aus hier
Beiträge: 10
WinXP
Delphi 2007 pro
|
Verfasst: Di 25.09.07 20:34
IBExpert ist echt super. Klasse ist auch, dass es allein Trigger und Generatoren anlegt. Das spart wirklich ne Menge Zeit.
Leider funktioniert es mit der Master-Detail aber immer noch nicht.
Die Meta-Daten sehen aber etwas "aufgeräumter" aus:
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: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117:
|
SET SQL DIALECT 3;
SET NAMES NONE;
CREATE DATABASE 'D:\RAD Studio\Projekte\NK_FireBird_DB\NK2007.FDB' USER 'SYSDBA' PASSWORD 'masterkey' PAGE_SIZE 16384 DEFAULT CHARACTER SET NONE;
CREATE GENERATOR GEN_JAHR_ID; SET GENERATOR GEN_JAHR_ID TO 6;
CREATE GENERATOR GEN_OBJEKT_ID; SET GENERATOR GEN_OBJEKT_ID TO 3;
CREATE TABLE JAHR ( ID INTEGER NOT NULL, OBJEKT_ID INTEGER NOT NULL, JAHR INTEGER NOT NULL );
CREATE TABLE OBJEKT ( OBJEKT_ID INTEGER NOT NULL, STRASSE VARCHAR(30), PLZ VARCHAR(10), ORT VARCHAR(20), INDABRTEXT VARCHAR(250), BEMERKUNG VARCHAR(250) );
ALTER TABLE JAHR ADD CHECK ((JAHR >= 1900) AND (JAHR <= 2099));
ALTER TABLE JAHR ADD CONSTRAINT PK_JAHR PRIMARY KEY (ID); ALTER TABLE OBJEKT ADD CONSTRAINT PK_OBJEKT PRIMARY KEY (OBJEKT_ID);
ALTER TABLE JAHR ADD CONSTRAINT FK_JAHR_OBJEKT FOREIGN KEY (OBJEKT_ID) REFERENCES OBJEKT (OBJEKT_ID) ON DELETE CASCADE ON UPDATE CASCADE;
CREATE INDEX JAHR_IDX_JAHR ON JAHR (OBJEKT_ID, JAHR); CREATE INDEX OBJEKT_IDX_STRASSE ON OBJEKT (STRASSE);
SET TERM ^ ;
CREATE TRIGGER JAHR_BI FOR JAHR ACTIVE BEFORE INSERT POSITION 0 as begin if (new.id is null) then new.id = gen_id(gen_jahr_id,1); end ^
CREATE TRIGGER OBJEKT_BI FOR OBJEKT ACTIVE BEFORE INSERT POSITION 0 as begin if (new.objekt_id is null) then new.objekt_id = gen_id(gen_objekt_id,1); end ^
SET TERM ; ^ |
Oder ist das so richtig, dass man den Wert im referenzierten Feld OBJEKT_ID (in der Tabelle JAHR) in AfterInsert "manuell" belegen muss?
|
|
hansa
      
Beiträge: 3079
Erhaltene Danke: 9
|
Verfasst: Di 25.09.07 22:10
Aha. Echt brav.  Trotzdem : der zweite Schritt soll anscheinend immer noch vor dem ersten gemacht werden. Die Domains sind weg, die "", dafür ist ein "Check" drin. NOT NULL würde ich auch vorsichtig verwenden. Reduziere das Ganze doch auf das absolute Minimum und baue dann langsam alles ein und nicht umgekehrt.
Am wichtigsten : wo ist die ID_JAHR in dem ID_Objekt ?? Meiner bescheidenen Meinung nach muss die Tabellenstruktur so ungefähr aussehen :
SQL-Anweisung 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18:
| CREATE TABLE JAHR ( ID INTEGER NOT NULL, ID_OBJEKT INTEGER NOT NULL, JAHR INTEGER NOT NULL );
CREATE TABLE OBJEKT ( ID INTEGER NOT NULL, ID_OBJEKT INTEGER, STRASSE VARCHAR(30), PLZ VARCHAR(10), ORT VARCHAR(20), INDABRTEXT VARCHAR(250), BEMERKUNG VARCHAR(250) ); |
_________________ Gruß
Hansa
|
|
|