Autor Beitrag
JohnDyr
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 56
Erhaltene Danke: 1

Win 10
C# (VS 2017)
BeitragVerfasst: So 23.12.18 22:02 
Hallo zusammen,

ich habe mal wieder eine Frage :oops:

Ziel: Erstellung einer 8-stelligen eindeutigen ID mit dem Aufbau:

ausblenden Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
<YY><MM><immer 4 stellige Zahl + 1 von der letzten ID>

Beispiel (im Dezember wurden 4 IDs angelegt):

18120001
18120002
18120003
18120004

Im Januar 19 wurden 5 angelegt:

19010001
19010002
19010003
19010004
19010005


Wie kann ich sowas am elegantesten umsetzen? Aktuell habe ich es so umgesetzt:
ausblenden Quelltext
1:
2:
3:
4:
1. Nehme aktuelles Jahr und Monat
2. Prüfe in der Datenbank alle IDs die mit diesem aktuellen Jahr + Monat beginnen
3. Nehme den höchsten Eintrag (order by desc) und merke davon die letzten 4 Stellen
4. Inkrementiere die letzten 4 Stellen des gefundenen und füge sie meiner zu erstellenden ID hinten zu


Geht das auch schöner?
Symbroson
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 382
Erhaltene Danke: 67

Raspbian, Ubuntu, Win10
C, C++, Python, JavaScript, Lazarus, Delphi7, Casio Basic
BeitragVerfasst: So 23.12.18 22:29 
Das einzige was man sich sparen könnte wäre die 4 Stellen herauszufiltern. Entweder die 4 Stellen sind voll ausgenutzt (9999) - dann müsstest du sowiso irgendetwas ändern, oder eben nicht und du kannst die ID normal inkrementieren.

Vielleicht kann man, um sich die SQL Abfragen jedes Mal zu sparen, die zuletzt verwendete ID bereits zu Beginn abfragen (sofern man nur die des heutigen Tages benötigt) und mit der im Programm global weiterarbeiten. Das musst aber du entscheiden ob das für dein Programm sinnvoll und anwendbar ist.

Edit: Vorausgesetzt das Datums-Format in der ID ist zwingend erforderlich. Sonst wäre Narses(ens) Methode zu bevorzugen

_________________
most good programmers do programming not because they expect to get paid or get adulation by the public, but because it's fun to program. (Linus Torvalds)


Zuletzt bearbeitet von Symbroson am So 23.12.18 22:31, insgesamt 1-mal bearbeitet
Narses
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Administrator
Beiträge: 10122
Erhaltene Danke: 1227

W2k .. W7pro .. W10ent
TP3 .. D7pro .. D10.2CE
BeitragVerfasst: So 23.12.18 22:29 
Moin!

Wenn Speicherplatz keine Rolle spielt: erstelle einfach alle IDs und gib den Datensätzen ein "used"-Kennzeichen mit. Dann lässt du dir den kleinsten, noch nicht genutzten Datensatz geben. Am Monatsende kannst du optional die nicht verbrauchten löschen. :nixweiss:

Aber, ehrlich gesagt, was soll das bringen?! :gruebel: Nimm ein Autoincrement-Feld als ID und ein Feld für das Anlagedatum brauchst du doch meistens eh, also warum daraus umständlich eine eigene ID basteln, die Informationen sind doch auch so da, aber leichter zu verwalten. :idea:

cu
Narses

_________________
There are 10 types of people - those who understand binary and those who don´t.
JohnDyr Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 56
Erhaltene Danke: 1

Win 10
C# (VS 2017)
BeitragVerfasst: Mo 24.12.18 11:21 
Zitat:

Wenn Speicherplatz keine Rolle spielt

Performance ist wichtiger als Speicherplatz für meine Anwendung. Aber die Lösung mit dem "vorreservieren" finde ich auch nicht so schön ehrlich gesagt.

Zitat:

Nimm ein Autoincrement-Feld als ID und ein Feld für das Anlagedatum brauchst du doch meistens eh, also warum daraus umständlich eine eigene ID basteln, die Informationen sind doch auch so da, aber leichter zu verwalten

Weil man anhand der (Auftrags)ID erkennen soll, wann dieser Auftrag entstanden ist. Also Jahr / Monat. Das ist aus Sicht der Usabilty für den Benutzer später ziemlich convenient (wie man auf Neudeutsch sagen würde).

Ich könnte natürlich einfach die letzten 4 Ziffern mit einem Auto Inkrement ID versehen und vorne immer das Jahr und den Monat ranhängen.... aber was passiert dann am Tag, wo der 9999 Auftrag erstellt wurde? Nichts gutes.

Deshalb dachte ich daran, die ID immer für jeden Monat auf 0000 zu setzen.
Th69
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Moderator
Beiträge: 4083
Erhaltene Danke: 845

Win7
C++, C# (VS 2015/17)
BeitragVerfasst: Mo 24.12.18 11:29 
Welche Datenbank benutzt du? Zumindestens Oracle und Postgres unterstützen Sequences, s. z.B. PostgreSQL: CREATE SEQUENCE.
JohnDyr Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 56
Erhaltene Danke: 1

Win 10
C# (VS 2017)
BeitragVerfasst: Mo 24.12.18 11:32 
Zitat:
Welche Datenbank benutzt du?

MSSQL.
JohnDyr Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 56
Erhaltene Danke: 1

Win 10
C# (VS 2017)
BeitragVerfasst: Mo 24.12.18 11:41 
Ich glaube eine perfomante Lösung gefunden zu haben.

Die ID ist bei mir ein int.

ausblenden Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
Annahme: Ein Auftrag existiert bereits in der Tabelle mit der ID: 18120001

Angenommen User erstellt jetzt einen neuen Auftrag im Dez 18.

1. int currentYearMonth = 18120000 // wird bei jeder neuen Auftragsanlage neu generiert über DateTime.ToString() Methoden.
2. int GetLatestId() = 18120001
2. if currentYearMonth < GetLatestId () 
   {
// hier geht er rein, wenn schon ein Auftrag für den aktuellen Monat existierte. 
     int new id = GetLatestId() + 1
   }
   else 
   {
// Hier geht er nur rein für den ersten Auftrag im neuen Monat
   newId = currentYearMonth + 1;
   }


Eben schnell runtergeschrieben im pseudocode. Macht das Sinn?
Symbroson
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 382
Erhaltene Danke: 67

Raspbian, Ubuntu, Win10
C, C++, Python, JavaScript, Lazarus, Delphi7, Casio Basic
BeitragVerfasst: Mo 24.12.18 12:37 
Abgesehen von Punkt 2 in Zeile 6 macht das Sinn. Aber jetzt lädst du ja anscheinend doch die letzte ID vor...

_________________
most good programmers do programming not because they expect to get paid or get adulation by the public, but because it's fun to program. (Linus Torvalds)
Ralf Jansen
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 4438
Erhaltene Danke: 911


VS2010 Pro, VS2012 Pro, VS2013 Pro, VS2015 Pro, Delphi 7 Pro
BeitragVerfasst: Mo 24.12.18 12:47 
Wenn du das System zwischen holen /testen der ID (GetLatestId) und benutzen der ID (zurückschreiben in die Tabellen) serialisierst so das immer nur einer das kann bzw. das eh eine Single User Anwendung ist könnte das funktionieren. Ansonsten sieht das nach einem Multi User Problem aus wenn mehrere User eine ID anfordern.
Th69
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Moderator
Beiträge: 4083
Erhaltene Danke: 845

Win7
C++, C# (VS 2015/17)
BeitragVerfasst: Mo 24.12.18 13:09 
Habe gerade nachgeschaut: auch MS SQL Server (ab 2012) unterstützt Sequences: CREATE SEQUENCE (Transact-SQL).
Du könntest entweder für jeden Monat eine Sequence anlegen (wenn benötigt) oder aber jeweils bei einem neuen Monat die Sequence zurücksetzen (bzw. löschen und neu anlegen).
JohnDyr Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 56
Erhaltene Danke: 1

Win 10
C# (VS 2017)
BeitragVerfasst: Mo 24.12.18 13:31 
user profile iconSymbroson hat folgendes geschrieben Zum zitierten Posting springen:
Abgesehen von Punkt 2 in Zeile 6 macht das Sinn. Aber jetzt lädst du ja anscheinend doch die letzte ID vor...

Mein Konstrukt kann nur funktionieren, wenn vorher mindestens eine ID schon drin ist. Aber ist ja kein Problem, eine Dummy ID einzurichten.

user profile iconRalf Jansen hat folgendes geschrieben Zum zitierten Posting springen:
Wenn du das System zwischen holen /testen der ID (GetLatestId) und benutzen der ID (zurückschreiben in die Tabellen) serialisierst so das immer nur einer das kann bzw. das eh eine Single User Anwendung ist könnte das funktionieren. Ansonsten sieht das nach einem Multi User Problem aus wenn mehrere User eine ID anfordern.

Die ID wird erst angefordert, wenn der Benutzer auf "Speichern" klickt und nicht vorher. Damit umgehe ich dieses Problem, wenn zwei User gleichzeitig einen Auftrag erstellen wollen. Also sie müssten ja wirklich in exakt derselben Milli Sekunde auf "Speichern" klicken, was dann zum Problem werden kann. Oder übersehe ich da etwas? :?:


user profile iconTh69 hat folgendes geschrieben Zum zitierten Posting springen:
Habe gerade nachgeschaut: auch MS SQL Server (ab 2012) unterstützt Sequences: CREATE SEQUENCE (Transact-SQL).
Du könntest entweder für jeden Monat eine Sequence anlegen (wenn benötigt) oder aber jeweils bei einem neuen Monat die Sequence zurücksetzen (bzw. löschen und neu anlegen).

Schaue ich mir gerade an und könnte funktionieren, wenn ich sowas wie:
ausblenden SQL-Anweisung
1:
2:
3:
4:
5:
6:
7:
CREATE SEQUENCE Test.ID
    AS int
    START WITH 1812000
    INCREMENT BY 1 
    MINVALUE 1812000  
    MAXVALUE 
;

Und das müsste ich dann "jeden neuen Tag" anpassen... finde ich persönlich umständlicher, als einfach eine 5 zeilige Methode einzufügen.
Ralf Jansen
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 4438
Erhaltene Danke: 911


VS2010 Pro, VS2012 Pro, VS2013 Pro, VS2015 Pro, Delphi 7 Pro
BeitragVerfasst: Mo 24.12.18 13:48 
Zitat:
Die ID wird erst angefordert, wenn der Benutzer auf "Speichern" klickt und nicht vorher. Damit umgehe ich dieses Problem, wenn zwei User gleichzeitig einen Auftrag erstellen wollen. Also sie müssten ja wirklich in exakt derselben Milli Sekunde auf "Speichern" klicken, was dann zum Problem werden kann. Oder übersehe ich da etwas? :?:


Wenn man weiß das Timings ein Problem sind sollte man die richtig lösen. Kannst du garantieren das sich der Code nicht ändert, das da plötzlich nicht mehr Zeit zwischen anfordern und nutzen ist so das aus einen unwahrscheinlichen Konflikt ein wahrscheinlicher wird, das da andere Entwickler dran arbeiten denen diese Probleme nicht bewusst sind etc.

Und es gibt bereits für diese Sorte von Problemen fertige Lösungen in Datenbanken die man einfach nutzen kann. Die sind getestet und haben sich bewährt.
Autoinkrement Felder, Generatoren, Sequences etc.

Zitat:
Und das müsste ich dann "jeden neuen Tag" anpassen... finde ich persönlich umständlicher, als einfach eine 5 zeilige Methode einzufügen.


Du meinst du müsstest eine Handvoll Codezeilen schreiben die funktionieren (Code der die Sequence anpasst) um eine handvoll Codezeilen zu ersetzen die bekannt manchmal nicht funktionieren werden. Und das findest du umständlich? Da solltest du nochmal drüber nachdenken :roll:
JohnDyr Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 56
Erhaltene Danke: 1

Win 10
C# (VS 2017)
BeitragVerfasst: Mo 24.12.18 14:01 
Zitat:
Wenn man weiß das Timings ein Problem sind sollte man die richtig lösen. Kannst du garantieren das sich der Code nicht ändert, das da plötzlich nicht mehr Zeit zwischen anfordern und nutzen ist so das aus einen unwahrscheinlichen Konflikt ein wahrscheinlicher wird, das da andere Entwickler dran arbeiten denen diese Probleme nicht bewusst sind etc.


Bin ich bei dir. Aber jetzt mal aus rein technischer Sicht betrachtet, sollte mein Code in jedem Szenario funktionieren. Ich spiele das einmal theoretisch durch (mir gehts nur ums Verständnis erstmal, ob das best-practice ist mal außen vor):

ausblenden Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
1. User1 legt einen neuen Auftrag an und füllt diesen mit Informationen. NOCH ist keine ID irgendwo entstanden.
2. User2 macht das gleiche und legt einen Auftrag an.
3. User1: Um folgende Uhrzeit 14:00.00.250 klickt User 1 auf "Speichern". 
   3a. Das "Speichern" führt dazu, dass geprüft wird ob ein Datensatz mit der ID bereits existiert, welcher jetzt [i]gleich [/i]anzulegen wäre. 
   3b. Ist dem nicht so, lege diesen Datensatz an. Wenn es den Datensatz bereits gibt, inkrementiere den ursprünglich vorhergesehenen und prüfe wieder etc.
4. User 2: Um 14:00:00.300 klickt User 2 auf "Speichern.
   4a. Mache was in 3a passiert.
   4b. mache was in 3b passiert.


Wo ist da genau das Risiko...? Ist die Gefahr gemeint, dass zwischen 3a und 3b ein anderer User ein Datensatz anlegen kann? (Deswegen ist das "Gleich" kursiv geschrieben). Das würde ich nur einmal gerne verstehen wollen.

Zitat:
Und es gibt bereits für diese Sorte von Problemen fertige Lösungen in Datenbanken die man einfach nutzen kann. Die sind getestet und haben sich bewährt.
Autoinkrement Felder, Generatoren, Sequences etc.

Meine genannte Lösung habe ich gerade implementiert und sie funktioniert wohl. Aber dennoch würde ich diese rausnehmen und dann euren Vorschlägen folgen. Für die Umsetzung bräuchte ich aber dann ggf. wieder etwas Support von euch. Ich bin ja bereit was neues zu lernen, aber ich will halt das "Warum" dahinter verstehen... deshalb die obige Nachfrage.
Palladin007
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 1201
Erhaltene Danke: 158

Windows 10 x64 Home Premium
C# (VS 2015 Enterprise)
BeitragVerfasst: Mo 24.12.18 23:07 
Halten wir doch Mal fest, was Du erreichen willst:

Du willst eine eindeutige ID haben, anhand der der Nutzer den Erstellungszeitraum erkennen kann. Eventuell soll er auch danach suchen können?
Soweit richtig?

Mir stellt sich da als erstes die Frage:
Warum nicht einfach das Erstellungsdatum extra anzeigen? Dann kannst Du auch schon formatieren und brauchst keinen extra Code.

Wenn Du aber unbedingt eine zusammengesetzte ID willst, sehe ich drei Wege:

1. Reines UI-Feature
Du speicherst die eine von der DB generierte ID als PrimaryKey und den Zeitspempel vom Speichern nur in der UI zusammen.
Wenn der Nutzer danach suchen will, nimmst Du die Zahl wieder auseinander und suchst nach beiden Werten.

2. beim Speichern als dritter Wert generieren
Du lässt von der DB die ID als PrimaryKey generieren und speicherst den Zeitstempel. Gleichzeitig setzt Du beides zu deiner zusammengesetzten ID zusammen und speicherst es als dritten Wert daneben.
Da alle drei Werte sich nicht ändern, stört mMn. nicht, dass damit streng genommen die erste Normalform verletzt wird.
Außerdem kannst Du so einen Index darauf setzen und effektiver suchen, während DB-intern die "richtige", von der DB generierte ID verwendet wird.

3. Calculated Columns
MSSQL unterstützt Calculated Columns.
Das Zusammensetzen sollte mit SQL recht simpel machbar sein, also erstellst Du eine Spalte, die das automatisch tut und Du nutzt nur noch den Wert.
Allerdings kann ich hier nicht sagen, wie gut und wie performant das mit dem Suchen funktioniert.



Ich würde Option 2 nutzen, das ist wohl der performanteste Weg.
Alle drei Wege haben aber den Vorteil, dass Du nichts neues ausdenken musst, weil die DB sich schon um Alles kümmert. Du setzt nur noch die Ergebnisse zusammen und da die generierte ID per se eindeutig ist, ist es die zusammengesetzte ID auch, egal welches Datum davor hängt.

Alle drei Optionen funktionieren natürlich auch mit einer Sequence, das wäre mir noch lieber, dann musst Du den PrimaryKey nicht raus geben und hast trotzdem eine eindeutige ID.
jaenicke
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 18738
Erhaltene Danke: 1634

W10 x64 (Chrome, IE11)
Delphi 10.2 Ent, Oxygene, C# (VS 2015), JS/HTML, Java (NB), PHP, Lazarus
BeitragVerfasst: Mi 26.12.18 09:06 
user profile iconJohnDyr hat folgendes geschrieben Zum zitierten Posting springen:
Die ID wird erst angefordert, wenn der Benutzer auf "Speichern" klickt und nicht vorher. Damit umgehe ich dieses Problem, wenn zwei User gleichzeitig einen Auftrag erstellen wollen. Also sie müssten ja wirklich in exakt derselben Milli Sekunde auf "Speichern" klicken, was dann zum Problem werden kann. Oder übersehe ich da etwas? :?:
Du könntest auch einfach mit einem Trigger arbeiten, der im SQL Server auf das Einfügen eines neuen Datensatzes reagiert. Diese stored procedure könnte dann ganz bequem und innerhalb einer Transaktion die ID vergeben.

Nichtsdestotrotz halte ich die schon genannten Alternativen ebenfalls für sinnvoller.

user profile iconJohnDyr hat folgendes geschrieben Zum zitierten Posting springen:
Ich könnte natürlich einfach die letzten 4 Ziffern mit einem Auto Inkrement ID versehen und vorne immer das Jahr und den Monat ranhängen.... aber was passiert dann am Tag, wo der 9999 Auftrag erstellt wurde? Nichts gutes.
In dem Fall muss die ID doch ohnehin eine Stelle länger sein, egal wie du das löst. Also wo ist das Problem?
Fülle immer auf eine einstellbare Anzahl von Stellen mit führenden Nullen auf, lösche aber auch keine überzähligen Stellen. Dann kommt eben so etwas heraus, wenn man zu wenige Stellen eingestellt hat:
ausblenden Quelltext
1:
2:
3:
4:
5:
6:
7:
18120001
18120002
...
18129999
181210000
181210001
...


Nebenbei gibt es auch viele Firmen, die eine solche Vergabe der ID gar nicht gut finden, denn man kann als Kunde sofort sehen wie viele Aufträge in einem Monat erstellt wurden, wenn man am Ende des Monats einen Auftrag auslöst. Viele benutzen deshalb nicht einmal eine fortlaufende ID, sondern eine eindeutige, aber pseudozufällige Zahl. Das macht es auch unmöglich die IDs benachbarter Aufträge zu erraten, falls das relevant sein könnte.
JohnDyr Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 56
Erhaltene Danke: 1

Win 10
C# (VS 2017)
BeitragVerfasst: Fr 28.12.18 17:12 
Zitat:
Das macht es auch unmöglich die IDs benachbarter Aufträge zu erraten, falls das relevant sein könnte


Und wie würde man das am effektivsten umsetzen? Ganz stumpf, indem ich eine generiere, prüfe ob sie da ist, wenn nicht eine neue generiere usw.?
jaenicke
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 18738
Erhaltene Danke: 1634

W10 x64 (Chrome, IE11)
Delphi 10.2 Ent, Oxygene, C# (VS 2015), JS/HTML, Java (NB), PHP, Lazarus
BeitragVerfasst: Sa 29.12.18 11:07 
Man könnte zum Beispiel einen Pool von solchen Nummern im Hintergrund generieren lassen, so dass in einer Tabelle immer eine bestimmte Anzahl von Nummern drin ist. Dann könnte man immer die erste abrufen und löschen. Das geht dann ohne weitere Prüfungen und ist damit beim Abrufen einer Nummer sehr schnell. Und da die Generierung nicht in Echtzeit passiert, ist es auch egal, wenn das einen Moment länger dauert.

Das ganze müsste sich mit einer Queue Implementierung wie dieser umsetzen lassen:
www.mssqltips.com/sq...eadpast-and-updlock/
Die Einträge in der Queue sind die generierten eindeutigen IDs und dann kann jeder sich immer einen Eintrag daraus herausnehmen.

Ob sich der Aufwand gegenüber der von dir genannten simplen Variante lohnt, hängt von den Datenmengen und der benötigten Performance ab. Normalerweise sollte eine Indexabfrage, ob ein Wert schon existiert, schnell genug sein, aber du darfst nicht vergessen, dass für deine Lösung auch irgendeine Art der Sperre der Tabelle nötig ist, damit nicht zufällig zwei gleiche Werte parallel hinzugefügt werden.

Mit der skizzierten Lösung über eine separate Queue als Nummernpool würde man dieses Problem auf diese kleinere Tabelle verlagern. Lediglich bei der Generierung sind dann Sperren auf der eigentlichen Tabelle notwendig, aber wenn man das gut umsetzt, ist das kein Problem. Denn dann muss man ja nur zuerst alle Nummern aus dem Pool und dann alle Nummern aus der eigentlichen Tabelle abrufen, dann hat man auf jeden Fall alle vorhandenen. Und dann kann man bequem neue Nummern generieren und muss nur prüfen, ob die Daten in den geholten Liste drin sind.

Für große Datenmengen wäre der Weg vielleicht nicht der richtige, aber danach hörte es sich ja nicht an.

Für diesen Beitrag haben gedankt: JohnDyr
JohnDyr Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 56
Erhaltene Danke: 1

Win 10
C# (VS 2017)
BeitragVerfasst: Sa 29.12.18 20:26 
Danke, habe soweit genug Input. Der Vorschlag mit den vorgefertigten IDs und einer benutzt/unbenutzt Spalte wird es bei mir werden.