Wie Stammdatenänderungen speichern

Stephan_2021

Benutzer
Beiträge
14
Hallo,

in einer Lieferantendatenbank (MySQL), soll es eine Tabelle für die Stammdaten der Lieferanten geben mit typischen Feldern von Firmenname über Anschrift bis Bankdaten. Diese Tabelle besitzt einen Primärschlüsssel mit Auto-increment. Über diesen Schlüssel würde ich nun die Tabelle mit den Lieferungen verknüpfen.

Falls sich in den Stammdaten nun aber z.B. die Adresse ändert, weil ein Lieferant umgezogen ist muss für die schon erfassten Lieferungen die Adresse unverändert bleiben.

Gibt es hierfür eine best practice?

Meine Idee ist bei jeder Änderung eines Satzes der Stammdaten, diesen (geänderten) Datensatz zu duplizieren das auch ein neuer Primärschlüssel-Wert erzeugt wird. Neue Lieferungen würden dann (für den inhaltlich gleichen Lieferanten) über diesen Schlüsselwert verknüpft, gleichzeitig blieben alle bisherigen Verknüpfungen auf den bisherigen Primärschlüsselwert (dieses Lieferanten) erhalten.
Dieses Verfahren würde (imho) funktionieren, nur entstünden eine ganze Menge redundante Daten. MIr kommt nur keine verständliche Idee wie ich in einem solchen Falle diese Stammdatentabelle normalisieren könnte um reduntante Werte zu vermeiden.


Gruß
Stephan
 
Werbung:
Wenn es Unternehmensprozesse gibt, die solche "Altdaten" benötigen, fachlich oder rechtlich, dann musst Du wohl bei einer solchen Änderung irgendwo diese Daten "aufbewahren" oder vielleicht besser, den Zugriff für solche Änderungen voll integrieren.
Best practice gibt es da nicht unbedingt, weil es auf die fachlichen Zusammenhänge ankommt.
Du könntest die jeweils gültige Lieferadresse an jeder Lieferung heften.
Oder einen Verweis auf die gültige Adresse zum Zeitpunkt der Lieferung. Was dann etwas sparsamer wäre. So etwa wie Du geschrieben hast.
Ich würde dabei überlegen, ob es ein einziger Satz ist also Firma, Adresse ... oder ob schon hier Firmenname, Steuernummer, .. eigenständig gespeichert werden und Adressen 1:n. Dann ändern sich erstmal keine Primärschlüssel. Aber das gleiche Problem wie mit der Adresse gibt es ja auch weiter oben.
Ggf. kannst Du über einen kombinierten Primärschlüssel nachdenken. Ein Teil bleibt fix, ein Teil zeigt auf die Änderungen.
 
Danke für Deine Antwort.

Wenn es Unternehmensprozesse gibt, die solche "Altdaten" benötigen, fachlich oder rechtlich, dann musst Du wohl bei einer solchen Änderung irgendwo diese Daten "aufbewahren"

ja. Das war/ist Anlass meiner Frage, genauer gesagt WIE ich das am Besten implementiere.

Vielleicht ist das Problem schwieriger als ich denke, nur gerade bei Webshops muss das doch häufiger vorkommen? Oder bewahren Webshops gestellte Rechnungen in quasi fertiger Form als PDF auf, damit man notfalls inzwischen in der DB geänderte z.B. Anschriftendaten, dort auch Jahre später nachsehen kann... (in einer realen Firma kann ich mir das ja in Form von ausgedruckten und abgeheften Rechnungen noch vorstellen, wenn die Fa. etwas konservativ ist)

oder vielleicht besser, den Zugriff für solche Änderungen voll integrieren.

ich weiß leider nicht was Du damit meinst

Best practice gibt es da nicht unbedingt, weil es auf die fachlichen Zusammenhänge ankommt.

best practice meinte eigentlich beste Art der Implementierung - technisch gesehen nicht mit Blick auf rechtliche Normen.

Du könntest die jeweils gültige Lieferadresse an jeder Lieferung heften.

ja, aber wenn ich das tue gebe ich doch die Normalisierung der DB im Grundsätzlichen fast auf, die die Lieferungsdaten selbst sind nur noch zwei weitere Tabellen, die zusammen weniger Felder haben als die Tabelle mit den Stammdaten um die es hier geht
Ggf. kannst Du über einen kombinierten Primärschlüssel nachdenken. Ein Teil bleibt fix, ein Teil zeigt auf die Änderungen.

Dieser Gedanke kam mir auch, aber der setzte sich dann bei mir im Kopf sofort weiter fort und ich käme dann letztlich dorthin das ich die komplette Stammdatentabelle in Tabellen aufteile die jeweils nur eine Spalte + Primärschlüssel und Verknüpfungsspalte haben, was mir komisch vorkommt und was ich auch eigentlich nicht wirklich zu Ende gedacht bekomme.


Ich habe jetzt nur Deine Antwort etwas kommentiert, aber was sollte ich sonst tun...




Gruß
Stephan
 
Normalisierung aufgeben/voll integrieren.
Wenn Du Abläufe hast, die auf historische Daten Zugriff haben müssen, so ist das eine fachliche Anforderung, hinter der eine perfekte Normalisierung zurückstehen muss.
Das kann man so sehen / schreiben, man kann aber auch die Gültigkeitsdauer bzw. eine Datensachlage zu einem Zeitpunkt X vollständig ins Datenmodell aufnehmen und wäre damit "formal" konform mit der Normalisierungsvorgabe.
Du musst bei diesem Problem beachten, dass Normalisierung kein Selbstzweck ist, sondern ein Weg, möglichst wenig Nebeneffekte bei der Datenverarbeitung zu haben und keine Funktionsverluste. Es bleibt Dir ja nichts anderes übrig, als Daten zu speichern, die Du irgendwann noch benötigst, auch wenn sie nicht aktuell sind.
Einfaches Beispiel "Kontostand". Datenmodelle halten idR ein (redundantes) Saldo, das aktuell gilt, obwohl es jederzeit aus den Buchungsbewegungen berechenbar sein muss. Das ist reiner Pragmatismus und hat mit Normalisierung nichts zu tun. Es geht da primär um einen resourcen schonenden, schnellen Zugriff.
Wenn man denormalisierte Daten hält oder speichert, muss man diesen "Fehler" technisch geeignet begleiten, also Querchecks haben, die die Richtigkeit der denormalisierten Daten sicherstellen. Das ist der Preis, den man für den schnellen Zugriff auf das aktuelle Saldo zahlt. Man würde ja umgekehrt nicht auf die Idee kommen, Einzelbuchungen nicht zu erfassen, sondern immer nur den aktuellen Kontostand. Das ist ähnlich zu Deinen veränderlichen Lieferantenadressen.
Man muss bei der Modellierung in solchen Fällen sicherstellen, das redundante Daten, hier historische, nur für ihren jeweiligen Verwendungszweck genutzt werden. Historisierte Daten sind naturgemäß nur noch readonly zugreifbar. Es darf keine Änderungsprozesse und keine Änderungsrechte für solche Daten geben.

Zum klaren Verständnis
Best Practice gibt es ohne detailierte Fallbeschreibungen nicht.
Generell kann man z.B. sagen, historisierte Datenhaltung ist okay, wenn sie notwendig ist und wenn das Modell sicherstellt, dass diese Daten nicht mit aktuellen Daten vermischt werden.
Im Detail ist immer die Frage, was ist der Nutzen, die Grenzen des Systems und der Aufwand. Bsp. Kontoverwaltung oben, muss man bei Vereinsmitgliedern jeden Beitrag verwalten oder reicht das Saldo? Oder reicht die Markierung der Zahlung des Monatsbeitrages? Oder GEZ: muss man überhaupt den Geldbetrag erfassen? Oder reicht gezahlt/nicht gezahlt? Der Betrag ist fest.

Datenbanken haben mit ihren Mechanismen von Constraints, Privileges, Views, Triggern usw. die Aufgabe, alle diese Dinge sicherzustellen. Sie können die Einhaltung solcher Modelle garantieren. Deswegen setzt man sie ein. Damit kann man im Sinne klassischer Client/Server Architektur Systeme bauen, die selbst im Fall fehlerhafter Clientprogramme alle definierten Spielregeln einhalten. Selbst wenn jemand per SQL Console mit den Daten arbeitet. Das muss kein 100% normalisiertes Modell sein, auch Redundanz kann geregelt ablaufen und garantiert richtig.

Leider ist die DB, die Du nutzt, nicht besonders stark an der Stelle Constraints und Robustheit. Damit fallen dann wahrscheinlich viele Probleme zurück auf Clientprogrammierung, die normalerweise nicht nötig wäre. Also eher nicht best practice. Die Frage ist da und an anderen Stellen, wieviel Spielraum du hast.
 
bei jeder Änderung eines Satzes der Stammdaten, diesen (geänderten) Datensatz zu duplizieren das auch ein neuer Primärschlüssel-Wert erzeugt wird.

Wie willst Du dann eine Statistik fahren zB Summe aller Lieferungen dieses Lieferanten?
Und rechtlich und steuerlich bleibt der Lieferant ja der gleiche, auch wenn er an eine andere Adresse umzieht.
 
Gibt es hierfür eine best practice?

Meine grobe Idee (basierend auf den Features von PostgreSQL):

  • 2 Tabellen, eine für die aktuellen Stammdaten und eine für historische
  • in beiden Tabellen ein Feld 'vald' oder so, Typ DATERANGE. für die aktuellen Daten vom Datum, ab dem diese Daten gelten, bis unendlich
  • bei Änderungen ein TRIGGER, der a) die bisherigen Daten in die Archivtabelle kopiert, dabei dort das valid-Feld bis auf das aktuelle Datum setzt, und in der Tabelle für aktuelle Daten wieder das Startdatum in valid auf das aktuelle setzt
  • ein VIEW, der im Prinzip ein UNION dieser beiden Tabellen enthält
  • bei einer Suche nach Alt-Daten das Rechnungsdatum nutzen, um im obigen View die zu diesem Zeitpunkt gültigen Werte zu suchen (via CONTAINS-Operator)

Damit findest Du immer die richtigen Werte, hast eine Historie über alle Änderungen, es belegt wenig Platz und es ist durch die Features von PostgreSQL (Indexierung) sehr schnell.
 
Historisierte Daten sind naturgemäß nur noch readonly zugreifbar. Es darf keine Änderungsprozesse und keine Änderungsrechte für solche Daten geben.

Ja stimmt, das ist ein sinnvoller/wichtiger Hinweis. Da muss ich erst einmal nachdenken wie ich das lösen kann.

Best Practice gibt es ohne detailierte Fallbeschreibungen nicht.

In Ordnung dann ziehe ich das "best practice" zurück. Inzwischen habe ich das Gefühl als wenn meine eigene Überlegung für die Lösung vielleicht garnicht so schlecht ist wie ich ursprünglich annahm.

Leider ist die DB, die Du nutzt, nicht besonders stark an der Stelle Constraints und Robustheit.

Gibt es überhaupt irgendein Projekt, irgendein Implementierungsdetail wo man sich nicht sagen lassen muss man hätte dummerweise gerade die falsche Datenbank gewählt :-(

Im Konkreten war/ist MySQL das Ergebnis längerer Überlegungen zu grundsätzlichen Anforderungen und der konkreten Situation beim Kunden (geprüfte Alternativen waren: PostgreSQL, Firebird, HSQLDB, MS SQL Server).
 
Wie willst Du dann eine Statistik fahren zB Summe aller Lieferungen dieses Lieferanten?

Ich dachte über eine eindeutige Lieferanten-Nummer, welche ich vom ersten Wert für den Primärschlüssel ableite, der für den Lieferanten vergeben wird.
Mein Frontend ist LibreOffice und wenn ich einen neuen Lieferanten in die Datenbank eintrage kann ich das automatisch erzeugte Auto_increment/Primärschlüssel abfragen und in eine weitere Spalte übernehmen. Bei Änderungen wird dann letzterer Eintrag beibehalten.
 
Werbung:
Code:
--
-- Tabellendefinitionen
--

edb=*# \d stammdaten
              Table "public.stammdaten"
 Column  |   Type    | Collation | Nullable | Default
---------+-----------+-----------+----------+---------
 kunde   | integer   |           | not null |
 val     | text      |           |          |
 von_bis | daterange |           | not null |
Indexes:
    "stammdaten_pkey" PRIMARY KEY, btree (kunde, von_bis)

edb=*# \d stammdaten_archiv
           Table "public.stammdaten_archiv"
 Column  |   Type    | Collation | Nullable | Default
---------+-----------+-----------+----------+---------
 kunde   | integer   |           | not null |
 val     | text      |           |          |
 von_bis | daterange |           | not null |
Indexes:
    "stammdaten_archiv_pkey" PRIMARY KEY, btree (kunde, von_bis)

edb=*# \d+ alle_stammdaten
                         View "public.alle_stammdaten"
 Column  |   Type    | Collation | Nullable | Default | Storage  | Description
---------+-----------+-----------+----------+---------+----------+-------------
 kunde   | integer   |           |          |         | plain    |
 val     | text      |           |          |         | extended |
 von_bis | daterange |           |          |         | extended |
View definition:
 SELECT stammdaten_archiv.kunde,
    stammdaten_archiv.val,
    stammdaten_archiv.von_bis
   FROM stammdaten_archiv
UNION
 SELECT stammdaten.kunde,
    stammdaten.val,
    stammdaten.von_bis
   FROM stammdaten;

--
-- Werte
--

edb=*# select * from stammdaten;
 kunde |      val       |        von_bis       
-------+----------------+-----------------------
  4711 | aktueller wert | [01-01-2020,infinity)
(1 row)

edb=*# select * from stammdaten_archiv ;
 kunde |       val       |         von_bis         
-------+-----------------+-------------------------
  4711 | sehr alter wert | [01-01-2018,01-01-2019)
  4711 | alter wert      | [01-01-2019,01-01-2020)
(2 rows)

--
-- Abfragen
--
edb=*# select * from alle_stammdaten where von_bis @> current_date;
 kunde |      val       |        von_bis       
-------+----------------+-----------------------
  4711 | aktueller wert | [01-01-2020,infinity)
(1 row)

edb=*# select * from alle_stammdaten where von_bis @> '2019-08-01'::date;
 kunde |    val     |         von_bis         
-------+------------+-------------------------
  4711 | alter wert | [01-01-2019,01-01-2020)
(1 row)

edb=*# select * from alle_stammdaten where von_bis @> '2018-08-01'::date;
 kunde |       val       |         von_bis         
-------+-----------------+-------------------------
  4711 | sehr alter wert | [01-01-2018,01-01-2019)
(1 row)

edb=*#
 
Zurück
Oben