Page 1 of 2 1 2 LastLast
Results 1 to 15 of 20

Thread: Gebruik Generator van FireBird

  1. #1
    Senior Member Antoine's Avatar
    Join Date
    Apr 2011
    Location
    Molenwaard
    Posts
    2,399

    Gebruik Generator van FireBird

    Dag all,

    Zit te stoeien met AutoIncrement voor mijn ID veld in een FireBird database.

    Ik heb een SQLQuery op mijn form staan met de volgende query erin:

    SQL Code:
    1. SELECT GEN_ID (GENID_CONTACT, 1) FROM RDB$DATABASE;

    Vervolgens doe ik in de AfterPost van de CDS het volgende:

    Delphi Code:
    1. procedure TfrmMain.CDSOvzMainAfterPost(DataSet: TDataSet);
    2. begin
    3.    if DataSet.FieldByName('ID').IsNull then
    4.   begin
    5.     // haal een waarde op van de generator
    6.     SQLQueryID.ExecSQL;
    7.   end;
    8. end;

    Maar toch blijf ik de melding krijgen: "Field 'ID' must have a value."

    Hoe kan dit?

    Hoor graag van u!

    Gr Anton
    " De waarde van het leven is niet in geld uit te drukken "

  2. #2
    Je haalt wel een nieuwe waarde op, maar slaat deze niet op in je veld.
    Marcel

  3. #3
    Senior Member Antoine's Avatar
    Join Date
    Apr 2011
    Location
    Molenwaard
    Posts
    2,399
    Ja klopt, weet alleen niet even hoe ik dan de gegenereerde ID ook daadwerkelijk opsla in mijn veld ID....

    Gr Anton
    " De waarde van het leven is niet in geld uit te drukken "

  4. #4
    Als antwoord op jouw vraag...
    Je zult waarschijnlijk de volgende regels even ervoor moeten zetten:

    Delphi Code:
    1. DataSet.FieldByName('ID').Required := false;

    Ik denk dat jij het ID veld als volgt hebt aangemaakt: "ID INTEGER not NULL".
    Die "not NULL" zorgt ervoor dat Delphi een melding geeft als je het veld wilt gebruiken en het gevuld is met NULL.
    Door die Required handmatig op false te zetten los je dat op.

    (Voor het opslaan van de opgehaalde ID kun je hieronder zien hoe ik dat normaal doe.)

    Quote Originally Posted by Antoine View Post
    Zit te stoeien met AutoIncrement voor mijn ID veld in een FireBird database.
    Maar nog even uit nieuwsgierigheid... waarom werk je niet met een trigger om dit te doen?
    Voordeel is dat als je buiten je programma records toe voegt het ook goed gaat en dan ben je van het handmatig uitvoeren af en weet je zeker dat de ID altijd goed gevuld wordt.

    Code:
    SET TERM ^ ;
    
    CREATE GENERATOR GEN_CONTACT^
    SET GENERATOR GEN_CONTACT TO 0^
    
    CREATE TRIGGER TR_CONTACT FOR CONTACT ACTIVE BEFORE INSERT POSITION 0 AS
    BEGIN
      IF ((NEW.ID is NULL) or (NEW.ID = 0)) THEN
          NEW.ID = GEN_ID(GEN_CONTACT, 1);
    END^
    Als je in je code eventueel voor de post toch de ID handmatig wilt vullen (wat niet altijd nodig is) gebruik ik:

    Delphi Code:
    1. function CreateDefaultTransaction(Sender: TComponent; Db: tibdatabase): TIBTransaction;
    2. var
    3.   Trans: TIBTransaction;
    4. begin
    5.   Trans := TIBTransaction.Create(Sender);
    6.   Trans.DefaultDatabase := Db;
    7.   Trans.DefaultAction := taRollback;
    8.   Trans.Params.Add('read_committed');
    9.   Trans.Params.Add('rec_version');
    10.   Trans.Params.Add('nowait');
    11.   Trans.Active := false;
    12.   Result := Trans;
    13. end;
    14.  
    15. function GetnewID(table: string; Db: tibdatabase): integer;
    16. var
    17.   ib: tIbSQL;
    18.   sd: string;
    19. begin
    20.   Result := -1;
    21.   ib := tIbSQL.Create(nil);
    22.   try
    23.     try
    24.       ib.Database := Db;
    25.       ib.Transaction := CreateDefaultTransaction(ib, Db);
    26.       ib.Transaction.Active := true;
    27.       sd := 'SELECT GEN_ID(GEN_' + table + ',1) AS link FROM RDB$DATABASE';
    28.       ib.SQL.Clear;
    29.       ib.SQL.Add(sd);
    30.       ib.ExecQuery;
    31.       Result := ib.FieldByName('link').asInteger;
    32.       ib.Close;
    33.     except
    34.       on E: Exception do
    35.           ShowMessage('Getnewlinknummer : Error - ' + E.Message);
    36.     end;
    37.   finally
    38.       ib.free;
    39.   end;
    40. end;

    en:

    Delphi Code:
    1. Q := GetnewID('CONTACT', DataSet.database);
    2. DataSet.FieldByName('ID').AsInteger := Q;

    Initieel lijkt het wat meer code maar je kunt de functie dus wel hergebruiken voor meerdere tabellen.

  5. #5
    John Kuiper
    Join Date
    Apr 2007
    Location
    Almere
    Posts
    8,747
    Natuurlijk kan je dat met een trigger doen. Maar dan weet je niet de lastid() tenzij je een refresh van je tabel doet, wat soms wel intensief kan zijn. Antoine zal wel gebruik maken van deze FAQ.
    De manier van Antoine kan prima, maar dan moet je dat regelen in de BeforePost() en niet in AfterPost()

    In bepaalde gevallen maak ik gebruik van een lastid() op te halen via de generator en die mee te geven in de Post, zodat ik een aantal opties direct kan aanzetten, doordat deze gebruik maken van master/detail relaties.

  6. #6
    Senior Member Antoine's Avatar
    Join Date
    Apr 2011
    Location
    Molenwaard
    Posts
    2,399
    Nou ook in beforepost krijg ik het niet voor elkaar hoor John...

    Ik heb nu deze query:

    SQL Code:
    1. SET TERM ^ ;
    2. CREATE GENERATOR GEN_CONTACT^
    3. SET GENERATOR GEN_CONTACT TO 0^
    4.  
    5. CREATE TRIGGER TR_CONTACT FOR CONTACTEN ACTIVE BEFORE INSERT POSITION 0 AS
    6. BEGIN
    7.   IF ((NEW.ID IS NULL) OR (NEW.ID = 0)) THEN
    8.        NEW.ID = GEN_ID(GEN_CONTACT, 1);
    9. END^

    Maar dan krijg ik weer deze foutmelding:
    Dynamic SQL Error SQL error code = -104 Token unknown - line 1, column 5 TERM
    Ik werk met WorkBench4

    Iemand een idee wat er fout gaat?

    gr Anton
    " De waarde van het leven is niet in geld uit te drukken "

  7. #7
    Quote Originally Posted by jkuiper View Post
    Maar dan weet je niet de lastid() tenzij je een refresh van je tabel doet, wat soms wel intensief kan zijn.
    Daarom gaf ik ook in het tweede gedeelte van mijn post aan (inclusief code) dat ik soms handmatig de ID vast stel voor de Post.

    Quote Originally Posted by jkuiper View Post
    De manier van Antoine kan prima, maar dan moet je dat regelen in de BeforePost() en niet in AfterPost()
    Oops. Good catch Dat was me nog niet eens opgevallen.
    De GEN_ID() in AfterPost heeft inderdaad natuurlijk geen zin.
    (anders je zou gelijk weer een .edit en .post moeten doen)

    Wel zou ik sowieso altijd de trigger als extra functionaliteit adviseren en niet alleen vertrouwen op de GEN_ID() in de BeforePost.
    Je weet nooit of je (e.v. later) met een andere DataSet (of ander programma, databasemanager ofzo) een INSERT gaat doen zonder dat je zelf de ID nodig hebt (voor master/detail relaties).
    Een trigger in de database geeft je dan de rust dat een simpele INSERT ook gewoon goed gaat (zonder extra code in de BeforePost te zetten).

  8. #8
    Quote Originally Posted by Antoine View Post
    Ik heb nu deze query:
    Dit was geen query die ik noemde maar een script (met meerdere commando's) om de database-trigger aan te maken.
    Het maakt de GENERATOR aan, zet deze op 0 en maakt de bijbehorende TRIGGER.
    (Als je al een GENERATOR gemaakt hebt dan moet je regel 2 en 3 even verwijderen.)

    Verder moet je dit dus als script in je databasemanager uit moeten voeren (éénmalig) en niet als query.
    (In WorkBench4 dus Editors / Script Editor)

    Als de trigger eenmaal in de database zit hoef je niets meer te doen in je beforepost.
    (tenzij je je ID nodig hebt om dus master/details op te slaan).
    Last edited by rvk; 24-Apr-14 at 11:13.

  9. #9
    Senior Member Antoine's Avatar
    Join Date
    Apr 2011
    Location
    Molenwaard
    Posts
    2,399
    Dag,

    Ik heb het even anders opgelost omdat ik later wat andere dingen moet doen met die ID.

    Ik heb nu deze Query:

    SQL Code:
    1. SELECT GEN_ID (GEN_CONTACT, 1) FROM RDB$DATABASE;

    Ik doe het volgende om de ID op te hogen...

    Delphi Code:
    1. procedure TfrmMain.CDSOvzMainBeforePost(DataSet: TDataSet);
    2. var
    3.   lGenID : integer;
    4. begin
    5.   if DataSet.FieldByName('ID').IsNull then
    6.   begin
    7.     // haal een waarde op van de generator
    8.     SQLQueryID.Close;
    9.     SQLQueryID.Open;
    10.     lGenID := SQLQueryID.Fields[0].AsInteger;
    11.     SQLQueryID.Close;
    12.     DataSet.FieldByName('ID').AsInteger := lGenID;
    13.   end;
    14. end;

    En dat is voor nu even voldoende, heb weer wat geleerd, dank u allen!!!
    " De waarde van het leven is niet in geld uit te drukken "

  10. #10
    TDigitalTrain user Hans Brenkman's Avatar
    Join Date
    Mar 2002
    Location
    Weert
    Posts
    1,861
    Quote Originally Posted by jkuiper View Post
    Maar dan weet je niet de lastid() tenzij je een refresh van je tabel doet, wat soms wel intensief kan zijn.
    John

    Een lastID kun je opvragen zonder een tabel refresh en zonder de ID op te hogen met

    Code:
    SELECT GEN_ID(AGenerator, 0) from RDB$DATABASE;
    Testen kan niet de afwezigheid van fouten aantonen, slechts de aanwezigheid van gevonden fouten.

    Het is verdacht als een nieuw ontwikkeld programma direct lijkt te werken: waarschijnlijk neutraliseren twee ontwerpfouten elkaar.

  11. #11
    Quote Originally Posted by Hans Brenkman View Post
    Een lastID kun je opvragen zonder een tabel refresh en zonder de ID op te hogen met ...
    Ieks... Dat moet je natuurlijk niet in een multi-user omgeving doen
    (een andere gebruiker kan inmiddels alweer een insert gedaan hebben)

    In dat geval is het beter voor de post een nieuwe ID op te vragen en die handmatig toe te kennen.

  12. #12
    TDigitalTrain user Hans Brenkman's Avatar
    Join Date
    Mar 2002
    Location
    Weert
    Posts
    1,861
    Rik,

    Waarom niet, als je het laatste uitgegeven ID wilt weten, is het opvragen zonder ophogen een geijkte methode en niet het refreshen van je tabel. Ik zeg niet dat je er vervolgens ZELF iets mee moet doen door er bijv. 1 bij op te te tellen om een nieuw ID te bepalen, dat zeker niet ! Maar alleen het laatste uitgegeven ID willen weten is dit een prima methode.
    Testen kan niet de afwezigheid van fouten aantonen, slechts de aanwezigheid van gevonden fouten.

    Het is verdacht als een nieuw ontwikkeld programma direct lijkt te werken: waarschijnlijk neutraliseren twee ontwerpfouten elkaar.

  13. #13
    Quote Originally Posted by Hans Brenkman View Post
    Waarom niet, als je het laatste uitgegeven ID wilt weten, is het opvragen zonder ophogen een geijkte methode en niet het refreshen van je tabel.
    Ik zeg niet dat je er vervolgens ZELF iets mee moet doen...
    Als je inderdaad niets met die ID gaat doen kun je dat wel gebruiken.
    Het is op dat moment, en dan bedoel ik ook precies dat split-second-moment, de laatst uitgegeven ID.
    Het hoeft dus echter niet het ID te zijn dat toegekend is door je eigen nét gedane POST-actie.

    Ik wilde alleen maar even waarschuwen dat je die ID niet moet gaan gebruiken om de Details van een Master/Details te gaan vullen want dan kun je nog wel eens voor verassingen komen te staan.

    Ik kan dus even niet precies een reden bedenken waarom je de lastId zou willen hebben zonder dat je er ook echt iets mee gaat doen.

  14. #14
    http://www.firebirdsql.org/refdocs/l...d20-genid.html
    Warning

    Unless you know very well what you are doing, using GEN_ID() with step values lower than 1 may compromise your data's integrity.
    Verder klopt het wat rvk zegt: Als een andere gebruiker ook een rij invoegt, net tussen het moment van jouw insert en het ophalen van het id in, dan is het id wat je krijgt niet jouw id, maar dat van de ander. Gelukkig zijn er andere manieren om aan die data te komen

    http://www.firebirdfaq.org/faq243/

    Met de oude manier zou je dus inderdaad een nieuw ID opvragen en dat zelf invoegen als je het nodig hebt. Dat moet dan volgens mij wel zonder trigger, omdat je ander niet de waarde kunt ophalen.

    Maar er is nu ook de `returning` clause:
    SQL Code:
    1. INSERT INTO t1 (...) VALUES (...) returning PK_VELD;

    Aangezien ik Firebird nog steeds nooit van dichtbij heb gezien heb ik even geen concreet voorbeeld van hoe je dat toepast in Delphi, maar niettemin zou ik het in die richting gaan zoeken. Het lijkt erop alsof een insert statement dat je op deze manier schrijft gewoon een record teruggeeft. Dus je voert het uit als query, en je krijgt een resultaat met 1 kolom (met de naam van het opgegeven PK veld), en als waarde de id die door de trigger is gegenereerd.
    Last edited by GolezTrol; 29-Apr-14 at 11:52.
    1+1=b

  15. #15
    TDigitalTrain user Hans Brenkman's Avatar
    Join Date
    Mar 2002
    Location
    Weert
    Posts
    1,861
    @Rik, dan begrijpen we elkaar goed.

    Ik wou alleen maar aangeven dat het achterhalen van het laatste uitgegeven ID door een refresh van een tabel (dus costs op een server en onnodig netwerk-traffic) eenvoudiger kan. Met het ophalen van een lastID via een refresh van een tabel kun je net zo min iets doen voor een volgende ID want ook dat is in een multi-user omgeving volstrekt onbetrouwbaar (door mogelijk nieuwe inserts door iemand anders).

    Overigens doe ik het soms nog anders áls ik het nieuwe ID verder niet direct nodig heb.

    Het veld ID moet dan weliswaar een waarde bevatten, en ik geef dat in een OnNewRecord-event de waarde -1 (of in een reeks van nieuwe records een negatief dalende waarde -1, -2 etc.)

    Code:
    procedure OnNewRecord(DataSet: TDataSet);
    const
      {$J+}
      ID: Integer = -1;
      {$J-}
    begin
      ... (ID-value toekennen aan het veld ID)
      Dec(ID);
    end;
    Vervolgens laat ik de database het ID vullen door een generator/sequence gekoppeld aan een trigger. Het negatieve ID wordt dan overschreven door het genereren van een nieuw ID getriggerd door een insert. In de applicatie hoef ik dan helemaal niet te weten wat het laatste uitgegeven ID is.
    Last edited by Hans Brenkman; 29-Apr-14 at 13:01. Reason: Correctie juiste event
    Testen kan niet de afwezigheid van fouten aantonen, slechts de aanwezigheid van gevonden fouten.

    Het is verdacht als een nieuw ontwikkeld programma direct lijkt te werken: waarschijnlijk neutraliseren twee ontwerpfouten elkaar.

Page 1 of 2 1 2 LastLast

Thread Information

Users Browsing this Thread

There are currently 1 users browsing this thread. (0 members and 1 guests)

Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •