Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Datenbanken (https://www.delphipraxis.net/15-datenbanken/)
-   -   Letzte vergebene Nummer speichern (https://www.delphipraxis.net/211150-letzte-vergebene-nummer-speichern.html)

BlueStarHH 3. Aug 2022 20:38

Datenbank: Firebird • Version: 3.x • Zugriff über: IBDAC

Letzte vergebene Nummer speichern
 
Hallo,

ich erzeuge mit einer StoredProcedure eine Art eindeutige ID. Die StoredProcedure kann natürlich von mehreren Benutzern aufgerufen werden. Ich möchte nun immer nur die zuletzt erzeugte ID in einer Tabelle speichern. Es ist keine simple Zahl. Wenn man eine Liste der IDs hat, kann man keine Reihenfolge und damit auch nicht die letzt ID erkennen.

Wenn es Delphi-Code wäre, würde ich die ID-Erzeugung mit einer CriticalSection schützen und dort einfach vor dem Verlassen der CriticalSection die ID in die Tabelle schreiben.

Doch wie mache ich das in einer Firebird StoredProcedure? Dort gibt es meines Wissens nach keine CriticalSections oder doch?

Papaschlumpf73 3. Aug 2022 20:59

AW: Letzte vergebene Nummer speichern
 
Die StoredProcedure könnte "deine" ID in einer Tabelle anfügen. Diese Tabelle hat neben dieser ID-Spalte noch eine normale ID-Spalte, in der ein Integer hochgezählt wird. Die Zeile mit dem höchsten "normalen" ID-Wert enthält dann jeweils deine zuletzt erzeugte "eigene" ID. Alternativ könnte die Tabelle auch ein TimeStamp-Feld enthalten. Alle Datensätze mit einer kleineren ID oder einem kleineren TimeStamp könnten dann immer gelöscht werden, so dass nur der zuletzt erzeugte Datensatz übrig bleibt.

BlueStarHH 3. Aug 2022 21:06

AW: Letzte vergebene Nummer speichern
 
Zitat:

Zitat von Papaschlumpf73 (Beitrag 1509722)
Die StoredProcedure könnte "deine" ID in einer Tabelle anfügen. Diese Tabelle hat neben dieser ID-Spalte noch eine normale ID-Spalte, in der ein Integer hochgezählt wird. Die Zeile mit dem höchsten "normalen" ID-Wert enthält dann jeweils deine zuletzt erzeugte "eigene" ID. Alternativ könnte die Tabelle auch ein TimeStamp-Feld enthalten. Alle Datensätze mit einer kleineren ID oder einem kleineren TimeStamp könnten dann immer gelöscht werden, so dass nur der zuletzt erzeugte Datensatz übrig bleibt.

Beides funktioniert im Mehrbenutzerzugriff nicht. Dazu müsste es in der StoredProcedure eine CriticalSection geben. Denn sonst:
Benutzer A hat als erster die ID erzeugt, aber die StoredProcedure noch nicht verlassen. Bei Benutzer B, der nach Benutzer A die StoredProcedure startet ist sie schon durchlaufen und würde dann die ID von B als letzte ID eintragen. Danach würde die ID von A eingetragen werden.

himitsu 3. Aug 2022 21:09

AW: Letzte vergebene Nummer speichern
 
Zitat:

Dort gibt es meines Wissens nach keine CriticalSections oder doch?
Eigentlich doch.

Wenn du in eine Tabelle/Feld rein schreibst, dann wird dieser Record/Tabelle/Speicherseite gesperrt und kein Anderer kann reinschreiben.
Er kommt erst ebdgültig dazu, wenn die aktuelle Schreibaktion/Query beendet ist.

Du kannst also den Wert einfach reinschreiben und, wenn gewünscht, den zuletzt aktuellen Wert auch auslesen, ohne das zwischendrin ein Anderer schreiben kann.




Aber wie Vorgänger bereits sagte,
das Erstellen/Generieren kann glechzeitig passieren und dann kommt es drauf an, wer zuerst zum Speichern kommt ... dort könnte sich die Erstellungsreihenfolge umdrehen und dann der Erste zuletzt speichern.

Besser ist es, wenn du auf einen Generator des DBMS zugreifst.

Oder eben wie von Papaschlumpf73 genannt : nicht "die ID speichern", sondern ALLE und dann in der Tabelle ein Feld mit Generator (SERIAL) als Reihenfolge.



In manchen DBMS kann man Funktionen einer externen DLL oder Scriptes registrieren.
Da drin kannst du dann auch mit einer CriticalSection o.Ä. arbeiten.

Papaschlumpf73 3. Aug 2022 21:12

AW: Letzte vergebene Nummer speichern
 
Kannst du noch mal präzisieren, was du mit "zuletzt erzeugte ID" meinst? Die ID, die tatsächlich zuletzt erzeugt wurde, oder diejenige, deren Erzeugung zuletzt gestartet wurde?

MyRealName 4. Aug 2022 09:28

AW: Letzte vergebene Nummer speichern
 
Warum nicht einfach einen Generator nutzen ? Der läuft ausserhalb von Transaktionen

BlueStarHH 5. Aug 2022 12:18

AW: Letzte vergebene Nummer speichern
 
Zitat:

Zitat von MyRealName (Beitrag 1509750)
Warum nicht einfach einen Generator nutzen ? Der läuft ausserhalb von Transaktionen

Weil der Generator einfach eine Zahl erzeugt, kann ich ihn nicht nutzen. Ich brauche aber eine spezielle ID. Eine Art Seriennummer für einen Gegenstand.

BlueStarHH 5. Aug 2022 12:19

AW: Letzte vergebene Nummer speichern
 
Zitat:

Zitat von Papaschlumpf73 (Beitrag 1509725)
Kannst du noch mal präzisieren, was du mit "zuletzt erzeugte ID" meinst? Die ID, die tatsächlich zuletzt erzeugt wurde, oder diejenige, deren Erzeugung zuletzt gestartet wurde?

Danke für die Frage. Ich musste das selbst nochmal drüber nachdenken. Also ich versuche es nochmal deutlicher zu machen:

Die StoredProcedure, die die ID erzeugt sieht ungefähr so aus:

Code:
procedure GetNextID
  a := ErzeugePrefix;
 
  if Bedingung then
    b := gen_id(MeinGenerator, 1);
  else
    b := '';

  c := ErzeugeSuffix;

  result := a+b+c;
end;

Eine Reihenfolge von IDs wäre z.B.

XM07-001-Z
XM07-002-A
XM07-003-X
XM07-U
XM07-A
XM08-001-X
XM08-002-B
XM09-001-B
A444-5

Wenn dort keine fortlaufende Nummer enthalten ist, lässt sich nachträglich nur anhand der ID keine Reihenfolge mehr erkennen.

Der Generator wird nur unter einer bestimmten Bedingung genutzt, um in der Mitte der ID eine fortlaufende Nummer einzufügen. Im Mehrbenutzerzugriff brauche ich als letzte ID dann die ID, die den größten Generatorwert hat. Dafür darf auch keine Rolle spielen, welcher StoredProcedure-Aufruf zuerst aufgerufen worden ist oder durchgelaufen ist.

BlueStarHH 5. Aug 2022 12:24

AW: Letzte vergebene Nummer speichern
 
Zitat:

Zitat von himitsu (Beitrag 1509724)
Besser ist es, wenn du auf einen Generator des DBMS zugreifst.

Wie genau hilft das in meinem Fall?

Zitat:

Zitat von himitsu (Beitrag 1509724)
Oder eben wie von Papaschlumpf73 genannt : nicht "die ID speichern", sondern ALLE und dann in der Tabelle ein Feld mit Generator (SERIAL) als Reihenfolge.

Hier wird dann die Reigefolge dadurch falsch festgelegt, wer zuerst in die Tabelle schreibt. Das muss nicht zwingend die Reihenfolge sein, in der die IDs erzeugt wurde.

Zitat:

Zitat von himitsu (Beitrag 1509724)
In manchen DBMS kann man Funktionen einer externen DLL oder Scriptes registrieren.
Da drin kannst du dann auch mit einer CriticalSection o.Ä. arbeiten.

Ich komme wohl um eine CriticalSection nicht herum. Weiß jemand wie ich das mit einer DLL in Firebird lösen kann?

Delphi.Narium 5. Aug 2022 13:16

AW: Letzte vergebene Nummer speichern
 
Du kannst einen Generator nutzen:

Dazu benötigst Du eine weitere Tabelle in der Form:
SQL-Code:
create Table LastID
(
  GeneratorID Integer not null,
  DeineID Integer not null -- oder welcher Type das auch immer sein mag.
);
Dazu einen Generator:
SQL-Code:
CREATE GENERATOR GEN_LastID;


Und noch einen Trigger:
SQL-Code:
CREATE TRIGGER TR_LastID_BIU FOR LastID ACTIVE
BEFORE INSERT OR UPDATE POSITION 0
AS
BEGIN
  if ((NEW.GeneratorID is NULL)) then NEW.GeneratorID = Gen_ID(GEN_LastID, 1);
END^
Wenn Du eine neue ID erstellt hast, schreibst Du diese per Insert
SQL-Code:
insert into LastID (DeineID) values (DerWertDerID);
in diese Tabelle.

Willst Du nun die letzte Deiner IDs ermitteln, bekommst Du die per
SQL-Code:
select first 1 DeineID from LastID order by GeneratorID desc;


Eventuell noch 'nen absteigenden Index auf die GeneratorID und regelmäßig nicht mehr benötigte Sätze aus der Tabelle LastID entfernen. Dazu könntest Du der Tabelle LastID ggfls. noch 'ne TimeStamp-Spalte gönnen und im Trigger diese mit CURRENT_TIMESTAMP befüllen, dann wird das Entfernen alter Sätze einfacher und Du kannst sogar nachvollziehen, in welcher Reihenfolge Deine IDs erstellt wurden.

Wäre das eventuell 'nen Versuch wert?

Uwe Raabe 5. Aug 2022 13:25

AW: Letzte vergebene Nummer speichern
 
Zitat:

Zitat von BlueStarHH (Beitrag 1509720)
Die StoredProcedure kann natürlich von mehreren Benutzern aufgerufen werden. Ich möchte nun immer nur die zuletzt erzeugte ID in einer Tabelle speichern.

Es ist immer noch nicht ganz klar, was mit zuletzt erzeugt gemeint ist.

BlueStarHH 5. Aug 2022 13:33

AW: Letzte vergebene Nummer speichern
 
Zitat:

Zitat von Uwe Raabe (Beitrag 1509803)
Zitat:

Zitat von BlueStarHH (Beitrag 1509720)
Die StoredProcedure kann natürlich von mehreren Benutzern aufgerufen werden. Ich möchte nun immer nur die zuletzt erzeugte ID in einer Tabelle speichern.

Es ist immer noch nicht ganz klar, was mit zuletzt erzeugt gemeint ist.

Ist wohl in meinem Post #8 etwas untergegangen dahier hier nochmal deutlicher:

Die ID mit dem größten Generatorwert. Dieser wird aber von Zeit zu Zeit auch wieder auf 1 gesetzt. Daher kann ich den nicht einfach so abfragen.
Wenn der Generator nicht benutzt wird, ist es prinipiell egal ob die letzte ID durch die zuletzt aufgerufene SP oder die zuletzt beendete SP festgelegt wird.

BlueStarHH 5. Aug 2022 13:42

AW: Letzte vergebene Nummer speichern
 
Zitat:

Zitat von Delphi.Narium (Beitrag 1509802)
Du kannst einen Generator nutzen:
...
Wenn Du eine neue ID erstellt hast, schreibst Du diese per Insert
SQL-Code:
insert into LastID (DeineID) values (DerWertDerID);
in diese Tabelle.

Wann rufe ich das insert into auf? Wie ist sichergestellt, dass das insert into mit der zuletzt erzeugten ID aufgerufen wird? So klappt das z.B. *nicht*:


Code:
procedure GetNextID
  a := ErzeugePrefix;
 
  if Bedingung then
    b := gen_id(MeinGenerator, 1);
  else
    b := '';

  c := ErzeugeSuffix;

  result := a+b+c;

  insert into LastID (DeineID) values (DerWertDerID)
end;
Beispiel: Benutzer A erzeugt mit gen_id die Nr 1. Jetzt kommt schon Benutzer B und erzeugt die Nr 2 mit gen_id und schreibt diese mit insert into in die LastID-Tabelle. Nun wird erst die letzte Zeile der SP vom Benutzer A aufgerufen und schon steht die 1 als falsche letzte ID in der LastID-Tabelle.

BerndS 5. Aug 2022 14:26

AW: Letzte vergebene Nummer speichern
 
Ich habe ein ähnliches Problem auch gehabt.
Meine Lösung im groben:
Vor dem Aufruf der SP (die die Nummer erzeugt) wird ein Datensatz in einer Sperrtabelle eingefügt.
Diese Tabelle hat einen eindeutigen Index auf einen Feld.
Dadurch ist ein doppeltes Einfügen nicht möglich. z.B. 'GETBUCHNR'
Nach dem Insert in die Sperrtabelle wird die Nummer per SP erzeugt, wobei diese prüft ob auch die Sperre gesetzt ist.
Danach wird der Datensatz mit der Nummer entweder eingefügt oder die Nummer gesetzet. Nach dem Commit wird die Sperre entfernt.

Die Funktion, die die Sperre anlegt, ist so aufgebaut, dass sie es bis zu eimen definieten Timeout wiederholt mit kurzem Sleep versucht, den Datensatz einzufügen.

Delphi.Narium 5. Aug 2022 14:35

AW: Letzte vergebene Nummer speichern
 
Was heißt denn das?

Zitat:

Zitat von BlueStarHH (Beitrag 1509804)
Dieser wird aber von Zeit zu Zeit auch wieder auf 1 gesetzt.

Mit System, eher zufällig?

Wenn der Generator zurückgesetzt wird, müssen zeitgleich auch alle Datensätze aus der Tabelle entfernt werden. Zur Erkennung des neuesten Satzes benötigst Du einen eindeutigen Wert, der die korrekte Erkennung sicherstellt. Wenn dieser Wert allerdings "wahlfrei" zurückgesetzt oder verändert wird, ist eine eindeutige Erkennung nicht mehr gegeben. Das spricht ganz klar für ein Designproblem.

Einen Generator, den man für die eindeutige Kennzeichnung von Werten nutzt, setzt man nicht zurück. Allenfalls dann, wenn die Gefahr eines Überlaufes besteht.

Der Wertebereich von Generatoren bei FireBird liegt zwischen
Code:
-2^63 .. 2^63
-9,223,372,036,854,775,808 .. 9,223,372,036,854,775,807
Wenn man nach dem Erstellen eines Generators den Wert auf -2^63 setzt, kann man damit schon 'ne ganze Weile auskommen. Selbst bei 1.000.000 IDs pro Sekunde könnte das dann mit fast 584.942,41735507203247082699137494 Jahren, auch bei optimistischer Betrachtung, die Laufzeit heutiger Software deutlich übersteigen ;-)

Wenn Du also heute einen entsprechenden Generator (wie im obigen Post bechrieben) erstellst, darfst Du ihn in ca. 584.000 Jahren auf -2^63 setzen.

Und: Derartige generatorgenerierte IDs werden nie, nie und nochmals nie für was anderes verwendet, als für die Feststellung, welches ist der neueste Wert. Es gibt also (für meine Begriffe) keinen vernunftbegabten Grund, weshalb man einen für diese Aufgabe genutzten Generator jemals wieder auf 1 setzen sollte. (Ok, ist etwas arg überspitzt formuliert ;-))

---

Zitat:

Zitat von BlueStarHH (Beitrag 1509805)
Zitat:

Zitat von Delphi.Narium (Beitrag 1509802)
Du kannst einen Generator nutzen:
...
Wenn Du eine neue ID erstellt hast, schreibst Du diese per Insert
SQL-Code:
insert into LastID (DeineID) values (DerWertDerID);
in diese Tabelle.

Wann rufe ich das insert into auf? Wie ist sichergestellt, dass das insert into mit der zuletzt erzeugten ID aufgerufen wird? So klappt das z.B. *nicht*:


Code:
procedure GetNextID
  a := ErzeugePrefix;
 
  if Bedingung then
    b := gen_id(MeinGenerator, 1);
  else
    b := '';

  c := ErzeugeSuffix;

  result := a+b+c;

  insert into LastID (DeineID) values (DerWertDerID)
end;
Beispiel: Benutzer A erzeugt mit gen_id die Nr 1. Jetzt kommt schon Benutzer B und erzeugt die Nr 2 mit gen_id und schreibt diese mit insert into in die LastID-Tabelle. Nun wird erst die letzte Zeile der SP vom Benutzer A aufgerufen und schon steht die 1 als falsche letzte ID in der LastID-Tabelle.

Hier haben wir genau das Problem, nach dem Uwe Raabe soeben gefragt hat.

Was ist jetzt die letzte ID. Die, die beim letzten Aufruf der SP (durch wen auch immer) generiert wird, oder die, die beim letzten Beenden der SP (durch wen auch immer) erstellt wird.

Ruft Benutzer A die SP auf und die dauert 1 Stunde und dazwischen rufen Benutzer B bis Z die SP auf, mit 'ner Laufzeit von 1 Sekunde, wobei der letzte Aufruf von B bis Z garantiert vor dem Aufruf von A beendet wird. Was ist jetzt die letzte ID? Die von A, weil zuletzt fertig oder die von dem Benutzter, die zuletzt gestartet wurde, unabhängig vom Zeitpunkt der Beendigung?

Oder
SQL-Code:
insert into LastID (GeneratorID, DeineID) values (Gen_ID(GEN_LastID, 1),Result)


Ist das immernoch nicht so ganz das, was gewünscht wurde, dann kannst Du Dir auch am Beginn der SP per SQL den nächsten Wert des Generators in einer Variabel speichern und dann diesen Wert am Ende beim Insert in die Tabelle schreiben.

Sinngemäß, ohne irgendwie auf korrekte Syntax zu achten:
SQL-Code:
procedure GetNextID
  select (Gen_ID(GEN_LastID, 1) into v_GenLastID; -- Zeile ggfls. dahin verschieben, wo sie im Ablauf korrekt positioniert ist.

  a := ErzeugePrefix;
 
  if Bedingung then
    b := gen_id(MeinGenerator, 1);
  else
    b := '';

  c := ErzeugeSuffix;

  result := a+b+c;

  insert into LastID (GeneratorID, DeineID) values (v_GenLastID, Result);
end;
Da die Fachlichkeit nicht wirklich bekannt ist, eventuell auch in der Form:
SQL-Code:
procedure GetNextID
 
  a := ErzeugePrefix;
  c := ErzeugeSuffix;
 
  if Bedingung then
    select (Gen_ID(GEN_LastID, 1) into v_GenLastID; -- je nach Fachlichkeit
    b := gen_id(MeinGenerator, 1); -- ggfls. Zeilen vertauschen
    insert into LastID (GeneratorID, DeineID) values (v_GenLastID, a+b+c);
  else
    b := '';

  result := a+b+c;
end;

BlueStarHH 5. Aug 2022 14:48

AW: Letzte vergebene Nummer speichern
 
Zitat:

Zitat von Delphi.Narium (Beitrag 1509807)
Was heißt denn das?

Zitat:

Zitat von BlueStarHH (Beitrag 1509804)
Dieser wird aber von Zeit zu Zeit auch wieder auf 1 gesetzt.

Mit System, eher zufällig?

Entweder zum Monatswechsel oder nur zum Jahreswechsel. Ist nachträglich nur anhand der ID nicht erkennbar.

Zur Klarstellung: Die ID ist hier *keine* Datensatz-ID. Sondern eine Art Seriennumer für einen Gegenstand. Wenn es sich um eine Datensatz ID handeln würde, würde ich einfach einen Generator nehmen, die nie zurücksetzen und fertig. Das ist hier aber nicht das Thema.

Zitat:

Zitat von Delphi.Narium (Beitrag 1509807)
Was ist jetzt die letzte ID. Die, die beim letzten Aufruf der SP (durch wen auch immer) generiert wird, oder die, die beim letzten Beenden der SP (durch wen auch immer) erstellt wird.

Nichts von beiden. Ich verweise nochmal auf meinen Post #8 und #12 und verdeutliche es nochmals.

Die ID mit dem größten Generatorwert *direkt* nach dem Abfrage des Generators ist die letzte ID für mich. Dieser wird aber von Zeit zu Zeit auch wieder auf 1 gesetzt. Daher kann ich den nicht einfach so abfragen. Wann die SP aufgerufen oder beendet wird ist dafür nicht maßgeblich.
Wenn der Generator nicht benutzt wird, ist es prinipiell egal ob die letzte ID durch die zuletzt aufgerufene SP oder die zuletzt beendete SP festgelegt wird.

BlueStarHH 5. Aug 2022 14:55

AW: Letzte vergebene Nummer speichern
 
Zitat:

Zitat von BerndS (Beitrag 1509806)
Ich habe ein ähnliches Problem auch gehabt.
Meine Lösung im groben:
Vor dem Aufruf der SP (die die Nummer erzeugt) wird ein Datensatz in einer Sperrtabelle eingefügt.
Diese Tabelle hat einen eindeutigen Index auf einen Feld.
Dadurch ist ein doppeltes Einfügen nicht möglich. z.B. 'GETBUCHNR'
Nach dem Insert in die Sperrtabelle wird die Nummer per SP erzeugt, wobei diese prüft ob auch die Sperre gesetzt ist.
Danach wird der Datensatz mit der Nummer entweder eingefügt oder die Nummer gesetzet. Nach dem Commit wird die Sperre entfernt.

Die Funktion, die die Sperre anlegt, ist so aufgebaut, dass sie es bis zu eimen definieten Timeout wiederholt mit kurzem Sleep versucht, den Datensatz einzufügen.

Super, das hört sich gut an. Ist eine Art Critical Section. Damit ist dann sichergestellt, dass die SP als ganzes oder gar nicht abgearbeitet wird. Ich überlege gerade noch, ob es bei dieser Implementation Nachteile gibt.

Delphi.Narium 5. Aug 2022 15:51

AW: Letzte vergebene Nummer speichern
 
Wir haben hier ein grundlegendes Missverständnis.

Die von mir eingeführte ID hat nichts, aber auch garnichts mit der von Dir erstellten ID zu tun.

Die GeneratorID dient ausschließlich zur Kennzeichnung der letzten DeineID in der Tabelle LastID. Sie ist absolut losgelöst vom Rest des Universums zu sehen. Sie sagt nur, wirklich nur: Das ist der zuletzt eingefügte Datensatz.
Selbst wenn die von Dir für die Fachlichkeit erstellte ID minütlich bei 1 anfangen würde, zeigte die GeneratorID trotzdem immer auf die zuletzt erstellte DeineID.

Wir müssen hier strikt zwischen GeneratorID und DeineID unterscheiden. Fachlich gibt es hier keinerlei Zusammenhang. Es ist also problemlos möglich, dass sowohl die GeneratorID 1, die GeneratorID 12345 und die GeneratorID 5126811 alle auf DeineID 1 verweisen.

Flapsig formuliert: Mit DeineID kannst Du machen was Du willst, (beliebiges Zeitinterval) zurücksetzen, neuen Algorithmus entwickeln, ...

Die Reihenfolge ergibt sich immer durch die GeneratorID, das ist nix weiter als ein AutoInc, bei dem der nächste Satz halt den nächsten Wert in 'ner definierten Reihenfolge bekommt. Darüber kannst Du dann (sofern die Daten in der Tabelle immer erhalten bleiben) feststellen, in welcher Reihenfolge für DeineID die 1 vergeben wurde. Wann wurde die 1 zum ersten Mal vergeben, wann zum 2. Mal, wann zum x-mal.

Tabelle LastID:
GeneratorIDDeineID
0001XM07-001-Z
0002XM07-002-A
0003XM07-003-X
0004XM07-U
0005XM07-A
0006XM08-001-X
0007XM08-002-B
0008XM09-001-B
0009A444-5
... 
1001XM07-001-Z
... 
2002XM07-002-A
... 
3003XM07-003-X
... 
4004XM07-U
... 
5005XM07-A
... 
6006XM08-001-X
... 
8007XM09-001-B
8008XM08-002-B
8009A444-5
Wenn's verständlicher wird, nenn' die GeneratorID einfach Reihenfolge
SQL-Code:
create Table LastID
(
  Reihenfolge BIGINT not null,
  DeineID Integer not null -- oder welcher Type das auch immer sein mag.
);
CREATE GENERATOR GEN_Reihenfolge;
SET GENERATOR GEN_Reihenfolge TO 0;

-- in der SP an geeigneter Stelle:
insert into LastID (Reihenfolge, DeineID) values (Gen_ID(GEN_Reihenfolge, 1), DeineID);

-- zur Abfrage der neuesten DeineID
select first 1 DeineID from LastID order by Reihenfolge desc;

-- zur Ansicht der Reihenfolge der DeineID-Vergabe:
select Reihenfolge, DeineID from LastId order by Reihenfolge.

-- soll nachgeschaut werden, ob es eine DeineID mehrfach gibt:
select Count(*) as Anzahl, DeineID from LastID group by DeineID having Count(*) > 1 order by DeineID

-- soll für eine bestimmte DeineID, die mehrfach vorhanden ist, die Reihenfolge der Vergabe angezeigt werden?
select Reihenfolge, DeineID from LastID where DeineID = 'WertDerGesuchtenDeineID';
Ist die pure Anzeige der Reihenfolge noch nicht ausreichend, dann spendiere der Tabelle noch eine TimeStamp-Spalte und befülle diese jeweils mit.
SQL-Code:
create Table LastID
(
  Reihenfolge BIGINT not null,
  DeineID Integer not null, -- oder welcher Type das auch immer sein mag.
  ZeitStempel TimeStamp
);
CREATE GENERATOR GEN_Reihenfolge;
SET GENERATOR GEN_Reihenfolge TO 0;

-- in der SP an geeigneter Stelle:
insert into LastID (Reihenfolge, DeineID, ZeitStempel) values (Gen_ID(GEN_Reihenfolge, 1), DeineID, Current_TimeStamp);

-- Abfrage von DeineID in zeitlicher Abfolge:
select Reihenfolge, DeineID, Zeitstempel from LastID order by ZeitStempel, Reihenfolge
-- sollten innerhalb eines ZeitTempels mehrere DeineIDs eingefügt werden, so kannst Du damit auch bei denen noch die Reihenfolge der Erstellung nachvollziehen.

himitsu 5. Aug 2022 17:51

AW: Letzte vergebene Nummer speichern
 
Ein asynchrones Statement, in welchem nur die Nummer generiert wird.
https://www.firebirdfaq.org/faq16/
Wie dort, nur eben auf die eigene Datenbank. (die Werte für die Connection kann man von der aktuellen Connecion auslesen/nehmen)

Das ist dann unabhängig von Transaktionen und der Dauer des Executes.


Wir nutzen sowas für ein Log in der DB (Tabelle) ... bei einer Exception würde ja die Transaction zurückgesetzt und das Log würde verschwinden ... so aber nicht, da in einer unabhängigen Transaktion gespeichert wurde.
Bzw. was Ähnliches auch für Tabellen, welche über alle Entwicklungsdatenbanken geshared werden. (wie eine globale Tabelle, obwohl eigentlich Jeder seine Eigene hätte)

BlueStarHH 5. Aug 2022 18:06

AW: Letzte vergebene Nummer speichern
 
Zitat:

Zitat von Delphi.Narium (Beitrag 1509810)
Wir haben hier ein grundlegendes Missverständnis.

Ich habe schon verstanden, wie Dein Vorschlag gemeint ist. Dein Generator habe ich kein einziges mal erwähnt. Ich meinte nur meinen Generator, der bei der ID-Erstellung benutzt wird.

Das Problem liegt hier:

Zitat:

Zitat von Delphi.Narium (Beitrag 1509810)
-- in der SP an geeigneter Stelle:
insert into LastID (Reihenfolge, DeineID) values (Gen_ID(GEN_Reihenfolge, 1), DeineID);

Kurzfassung: Es gibt keine geeignete Stelle, wo ich diesen Code einfügen kann. Da direkt vor dem Aufruf von diesem "insert into" das "insert into" von einem anderen Benutzer aufgerufen werden kann. Ich verdeutliche es mal:

Die SP könnte so aussehen:

Code:
SP
begin
  DeineID := ErzeugeMeineID;
  insert into LastID (Reihenfolge, DeineID) values (Gen_ID(GEN_Reihenfolge, 1), DeineID);
end;
Das könnte so ablaufen, wenn 2 Benuzer die SP starten:

Benutzer A startet die SP:
Benutzer A: DeineID := ErzeugeMeineID; // DeineID = XY-1

Die SP von A läuft noch. Bevor die SP von A zum "insert into" kommt, startet B die SP und diese wird vollständig beendet:
Benutzer B: DeineID := ErzeugeMeineID; // DeineID = XY-2
Benutzer B: insert into LastID (Reihenfolge, DeineID) values (Gen_ID(GEN_Reihenfolge, 1), XY-2); //XY-2 steht in LastID als letzte ID (Reihenfolge=1)

Nun kommt die SP von A erst zum insert into:
Benutzer A: insert into LastID (Reihenfolge, DeineID) values (Gen_ID(GEN_Reihenfolge, 1), XY-1); //XY-1 steht in LastID als letzte ID (Reihenfolge=2). Stimmt nicht!

Am Ende sieht die Tabelle LastID so aus:
Code:
Reihenfolge DeineID
1           XY-2
2           XY-1
So müsste sie aber aussehen:
Code:
Reihenfolge DeineID
1           XY-1
2           XY-2

Delphi.Narium 5. Aug 2022 18:25

AW: Letzte vergebene Nummer speichern
 
Für mich immernochnicht wirkliche Klar ist: Was ist die letzte ID?

Zuerst die SP aufgerufen, zuletzt die SP aufgerufen?

Zuerst in der SP (unabhängig von der Gesamtlaufzeit) erstellte ID?

Zuletzt in der SP (unabhängig von der Gesamtlaufzeit) erstellte ID?

Der höchste Wert, der, ohne Beachtung von Prefix und Suffix, ermittelt wurde?

Der Moment, in dem in der SP die ID feststeht, unabhängig von der Laufzeit der davor und der danach auszuführenden Teile der SP?

Post #8 und #12 geben mir da noch nicht ausreichend Klarheit.

Eventuell könnte man die Tabelle LastId noch etwas aufbohren:

SQL-Code:
create Table LastID
(
  Reihenfolge BIGINT not null,
  DeineID Integer not null, -- oder welcher Type das auch immer sein mag.
  Prefix VarChar(32), -- oder wie lang Präfix auch immer maximal lang sein mag
  ID Integer,
  Suffix VarChar(32) -- oder wie lang Suffix auch immer maximal lang sein mag
);
CREATE GENERATOR GEN_Reihenfolge;
SET GENERATOR GEN_Reihenfolge TO 0;

-- in der SP an geeigneter Stelle:
insert into LastID (Reihenfolge, DeineID, Prefix, ID, Suffix) values (Gen_ID(GEN_Reihenfolge, 1), DeineID, a, b, c);
Dann könnte die letzte ID z. B. sinngemäß in der Art ermittelt werden:
SQL-Code:
select ID from LastID
where Reihenfolge =
(
  select Max(Reihenfolge) as MaxID
  from LastID
  where Prefix = 'gesuchtes Präfix'
  and Suffix = 'gesuchtes Suffix'
)
Aber nach Deinem letzten Einwand dürfte sich das Problem auch nur örtlich, aber nicht zeitlich, verlagern.

Oder:
SQL-Code:
SP
begin
  insert into LastID (Reihenfolge, DeineID) values (Gen_ID(GEN_Reihenfolge, 1), ErzeugeMeineID);
end;
Inzwischen ahne ich, wo das konkrete Problem zu suchen ist und nein, eine wirkliche Lösung fällt mir dazu nicht ein :-(

BlueStarHH 5. Aug 2022 18:36

AW: Letzte vergebene Nummer speichern
 
Zitat:

Zitat von Delphi.Narium (Beitrag 1509817)
Für mich immernochnicht wirkliche Klar ist: Was ist die letzte ID?

Der höchste Wert, der, ohne Beachtung von Prefix und Suffix, ermittelt wurde?

Fast richtig. Der letzte Wert, den *mein* Generator in meiner SP erzeugt hat. Das ist meistens der höchste Wert von meinem Generator aber nicht immer. Wenn der Generator wieder bei 1 anfägt.

Delphi.Narium 5. Aug 2022 18:43

AW: Letzte vergebene Nummer speichern
 
Ja, aber dann ist es immernoch der mit der höchsten Reihenfolge.
SQL-Code:
select ID from LastID
where Reihenfolge =
(
  select Max(Reihenfolge) as MaxID
  from LastID
  where Prefix = 'gesuchtes Präfix'
  and Suffix = 'gesuchtes Suffix'
)
müsste dann schon passen.

BlueStarHH 5. Aug 2022 18:46

AW: Letzte vergebene Nummer speichern
 
Zitat:

Zitat von Delphi.Narium (Beitrag 1509819)
Ja, aber dann ist es immernoch der mit der höchsten Reihenfolge.

Nein. Siehe mein Beispiel in Post #20. Und der Generator kann ja wieder von 1 anfangen und dann im zweiten Durchlauf den ersten Durchlauf überholen. Oder im ersten Durchlauf generell einen höheren Wert erreicht haben als im zwweiten oder n-ten Durchlauf.

Uwe Raabe 5. Aug 2022 18:58

AW: Letzte vergebene Nummer speichern
 
Ich sehe da momentan drei Möglichkeiten für eine Kollision durch nebenläufigen Zugriff:
  1. Beide verwenden den Generator
  2. Einer verwendet den Generator, der andere nicht
  3. Beide verwenden den Generator
Punkt 1 ist offenbar unkritisch: Es ist egal, welche der erzeugten IDs wir speichern.
Punkt 2 habe ich so verstanden, dass dann die ID mit Generator gewinnt.
Punkt 3 wäre der Fall, wo der höhere Generatorwert gewinnt, wenn wir ausschließen können, dass während der Kollision der Generator zurückgesetzt wird.

Ist das soweit richtig?

BlueStarHH 5. Aug 2022 19:03

AW: Letzte vergebene Nummer speichern
 
Zitat:

Zitat von Uwe Raabe (Beitrag 1509824)
Ich sehe da momentan drei Möglichkeiten für eine Kollision durch nebenläufigen Zugriff:
  1. Beide verwenden den Generator nicht
  2. Einer verwendet den Generator, der andere nicht
  3. Beide verwenden den Generator
Punkt 1 ist offenbar unkritisch: Es ist egal, welche der erzeugten IDs wir speichern.
Punkt 2 habe ich so verstanden, dass dann die ID mit Generator gewinnt.
Punkt 3 wäre der Fall, wo der höhere Generatorwert gewinnt, wenn wir ausschließen können, dass während der Kollision der Generator zurückgesetzt wird. Beim Zurücksetzen gewinnt der neue Startwert.

Ist das soweit richtig?

Ja, ich habs nochmal konkretisiert in fett.

Delphi.Narium 5. Aug 2022 19:08

AW: Letzte vergebene Nummer speichern
 
Wann wird denn der Generator auf 1 gesetzt?

Datumsabhängig? Dann muss das Datum, das Quartal, der wie auch immer geartete Wert, anhand dessen eine Zeitabfolge erkennbar werden könnte, mit in die Tabelle LastID.

Z. B. sowas in der Art?

SQL-Code:
insert into LastID (Reihenfolge, DeineID, Prefix, ID, Suffix, Monat, Jahr)
values (Gen_ID(GEN_Reihenfolge, 1), DeineID, a, b, c, Month(Current_Date), Year(Current_Date));
SQL-Code:
select Max(ID) as MaxID
from LastID
where Prefix = 'gesuchtes Präfix'
and Suffix = 'gesuchtes Suffix'
and Monat = 1
and Jahr = 2022
Letztlich muss die Information mitgespeichert werden, mit der eindeutig festgestellt werden kann, zu welchem Zeitraum die zu ermittelnde letzte ID gehört.

Uwe Raabe 5. Aug 2022 21:11

AW: Letzte vergebene Nummer speichern
 
Ich glaube, es fehlt ein Kriterium, mit dem man feststellen kann, ob zwischen zwei Generatorwerten ein Rücksetzen stattgefunden hat. Andernfalls gibt es keine Möglichkeit, die Werte im Nachgang zu priorisieren.

Man könnte z.B. beim Rücksetzen einen Zählerwert erhöhen. Damit wäre die Kombination Zählerwert/Generator-ID eindeutig.

Delphi.Narium 6. Aug 2022 18:34

AW: Letzte vergebene Nummer speichern
 
@BlueStarHH

Schau doch bitte mal, ob Du damit einen Ansatz bauen kannst: FireBird - Understanding the WITH LOCK clause
SQL-Code:
Syntax and behaviour

    SELECT ... FROM single_table
       [WHERE ...]
       [FOR UPDATE [OF ...]]
       [WITH LOCK]
Damit wird bis zum Commit eine Tabelle / ein Datensatz / eine Spalte gelockt. Andere können dann nicht schreiben, bekommen entweder eine Exception oder warten, bis die Freigabe erfolgt. Das kommt doch dann einer CriticalSection sehr nahe.

Dazu müsstest Du dann am Beginn der SP eine Tabelle / einen Datensatz / eine Spalte locken und zwar entsprechend für die Speicherung der jeweils letzten ID. Am Ende der SP schreibst Du dann die in der SP ermittelte ID entsprechend. Das anschließende Commit, egal aus ob aus der SP heraus oder aus dem aufrufenden Progamm, gibt den Datensatz bzw. Tabelle wieder frei, ebenso natürlich auch ein Rollback.

Für fachlich unterschiedliche IDs könnest Du dann eine Tabelle, mit nur einer Zeile, aber je ID einer Spalte, erstellen, per Select am Beginn der SP den Datensatz mit explizitem Lock auf die zu ändernde ID-Spalte lesen und am Ende genau die Spalte mit der ermittelten ID aktuallisieren.

Ohne auf korrekte Syntax zu achten als Idee:
SQL-Code:
procedure GetNextID
  SELECT LetzteIDSpalte FROM LetzteIDTabelle
  FOR UPDATE OF LetzteIDSpalte
  WITH LOCK;

  a := ErzeugePrefix;
 
  if Bedingung then
    b := gen_id(MeinGenerator, 1);
    UPDATE LetzteIDTabelle set LetzteIDSpalte = b;
  else
    b := '';

  c := ErzeugeSuffix;

  result := a+b+c;
end;

bnreimer42 6. Aug 2022 19:41

AW: Letzte vergebene Nummer speichern
 
Man kann in Firebird einen Generator als "Sperre" und zum Prüfen, ob Du loslegen darfst verwenden.

Z.B.
Generator mit 0 initialisieren (Irgendwann mal, wenn kein Programm läuft)

Im Programm (oder Stored Procedure)
Einfach wert des Generators erhöhen.
Wenn = 1 dann mache, was Du tun musst, kein anderer kommt Dir in die Quere
Wenn > 1 reduziere den Generator um 1 und brich ab.

Das läuft super, außer die Prozedur bricht ab, wenn der Generator auf 1 steht, denn dann bleibt die Sperre auf ewig. Muss dann vom Admin wieder auf 0 gesetzt werden, wenn kein Programm läuft.

Das Vorgehen ist einfacher, als in einer Tabelle mit LOCK einen Satz zu sperren.

MyRealName 7. Aug 2022 09:54

AW: Letzte vergebene Nummer speichern
 
Es gibt auch globale Variablen in Firebird, die sind auch entsprechend abfragbar, wenn sie nicht zu Deiner Session / Connection gehören


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