Autor |
Beitrag |
JohnDyr
Beiträge: 56
Erhaltene Danke: 1
Win 10
C# (VS 2017)
|
Verfasst: So 23.12.18 22:02
Hallo zusammen,
ich habe mal wieder eine Frage
Ziel: Erstellung einer 8-stelligen eindeutigen ID mit dem Aufbau:
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:
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
Beiträge: 382
Erhaltene Danke: 67
Raspbian, Ubuntu, Win10
C, C++, Python, JavaScript, Lazarus, Delphi7, Casio Basic
|
Verfasst: 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
Beiträge: 10182
Erhaltene Danke: 1255
W10ent
TP3 .. D7pro .. D10.2CE
|
Verfasst: 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.
Aber, ehrlich gesagt, was soll das bringen?! 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.
cu
Narses
_________________ There are 10 types of people - those who understand binary and those who don´t.
|
|
JohnDyr
Beiträge: 56
Erhaltene Danke: 1
Win 10
C# (VS 2017)
|
Verfasst: 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
Beiträge: 4791
Erhaltene Danke: 1059
Win10
C#, C++ (VS 2017/19/22)
|
Verfasst: Mo 24.12.18 11:29
Welche Datenbank benutzt du? Zumindestens Oracle und Postgres unterstützen Sequences, s. z.B. PostgreSQL: CREATE SEQUENCE.
|
|
JohnDyr
Beiträge: 56
Erhaltene Danke: 1
Win 10
C# (VS 2017)
|
Verfasst: Mo 24.12.18 11:32
Zitat: | Welche Datenbank benutzt du? |
MSSQL.
|
|
JohnDyr
Beiträge: 56
Erhaltene Danke: 1
Win 10
C# (VS 2017)
|
Verfasst: Mo 24.12.18 11:41
Ich glaube eine perfomante Lösung gefunden zu haben.
Die ID ist bei mir ein int.
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
Beiträge: 382
Erhaltene Danke: 67
Raspbian, Ubuntu, Win10
C, C++, Python, JavaScript, Lazarus, Delphi7, Casio Basic
|
Verfasst: 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
Beiträge: 4706
Erhaltene Danke: 991
VS2010 Pro, VS2012 Pro, VS2013 Pro, VS2015 Pro, Delphi 7 Pro
|
Verfasst: 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
Beiträge: 4791
Erhaltene Danke: 1059
Win10
C#, C++ (VS 2017/19/22)
|
Verfasst: 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
Beiträge: 56
Erhaltene Danke: 1
Win 10
C# (VS 2017)
|
Verfasst: Mo 24.12.18 13:31
|
|
Ralf Jansen
Beiträge: 4706
Erhaltene Danke: 991
VS2010 Pro, VS2012 Pro, VS2013 Pro, VS2015 Pro, Delphi 7 Pro
|
Verfasst: 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
|
|
JohnDyr
Beiträge: 56
Erhaltene Danke: 1
Win 10
C# (VS 2017)
|
Verfasst: 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):
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
Beiträge: 1282
Erhaltene Danke: 182
Windows 11 x64 Pro
C# (Visual Studio Preview)
|
Verfasst: 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
Beiträge: 19288
Erhaltene Danke: 1743
W11 x64 (Chrome, Edge)
Delphi 11 Pro, Oxygene, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
|
Verfasst: Mi 26.12.18 09:06
JohnDyr hat folgendes geschrieben : | 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.
JohnDyr hat folgendes geschrieben : | 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:
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
Beiträge: 56
Erhaltene Danke: 1
Win 10
C# (VS 2017)
|
Verfasst: 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
Beiträge: 19288
Erhaltene Danke: 1743
W11 x64 (Chrome, Edge)
Delphi 11 Pro, Oxygene, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
|
Verfasst: 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
Beiträge: 56
Erhaltene Danke: 1
Win 10
C# (VS 2017)
|
Verfasst: 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.
|
|
|