# Normalisierung



## AlphaErpel (29. Jul 2017)

Hallo Leute,
habe die Normalisierung mit einer normalen Tabelle verstanden. Aber wie erkenne ich bei einer Tabelle wie sie unten steht, ohne sinnvolle Namen, in welcher Normalform sie ist und wie bringe ich sie in die 3. Normalform?

Spalte A | Spalte B | Spalte C | Spalte D
      1     |     ABC   |    XYZ     |    123
      2     |     DEF   |     UYZ    |    123
      1     |     UVW  |     XYZ    |    321  
      3     |      IJK    |     UYZ    |    456


----------



## Thallius (29. Jul 2017)

Da die Einträge in Spalte C und D mehrfach vorkommen kann man diese in eine eigene Tabelle auslagern.


----------



## AlphaErpel (29. Jul 2017)

Alles klar danke. Sieht die Lösung zur 3.NF dann so aus?:

Spalte A | *Spalte B* |* C* | *D* 
1 | ABC | 1 | 1
2 | DEF | 2 | 1
1 | UVW | 1 | 2
3 | IJK | 2 | 3

*C* | Spalte C
1 | XYZ
2 | UYZ

*D* | Spalte D
1 | 123
2 | 321
3 | 456


----------



## Thallius (29. Jul 2017)

sieht für mich schlüssig aus


----------



## AlphaErpel (29. Jul 2017)

Dann hab ich es wohl verstanden. Vielen Dank


----------



## JuKu (5. Aug 2017)

Bitte bedenke, dass die Normalform eine Datenbank sehr oft ineffizient machen kann (Performance geht verloren), diese Strategie also meist eher theoretischer Natur ist!


----------



## Meniskusschaden (5. Aug 2017)

JuKu hat gesagt.:


> diese Strategie also meist eher theoretischer Natur ist!


Laß dich davon nicht irritieren. Normalisierung ist gängige Praxis.


----------



## mrBrown (5. Aug 2017)

Spoiler



Performanceprobleme sind auch gängige Praxis


----------



## JuKu (5. Aug 2017)

mrBrown hat gesagt.:


> Spoiler
> 
> 
> 
> Performanceprobleme sind auch gängige Praxis



Leider hast du damit sooo recht!
Aber wir müssen das ja nicht so nachmachen.


----------



## Meniskusschaden (5. Aug 2017)

JuKu hat gesagt.:


> Leider hast du damit sooo recht!


Das widerspricht aber deiner ersten Aussage. Wenn Normalisierung meist nur Therorie ist, kann sie wohl kaum in größerem Umfang die Ursache für praktische Performanceprobleme sein.

Bleibt mal bitte bei einer angemessenen Bewertung. Ich habe ja nichts gegen die Aussage, dass Normalisierung Performance kosten kann. Meistens bezahlt man das aber gerne, weil man auf die Vorteile nicht verzichten will.


----------



## stg (5. Aug 2017)

Und spätere De-Normalisierungsschritte sind auch deutlich einfacher durchzuführen, als entsprechende Normalisierungsschritte. Besser also erstmal "zu viel", als "zu wenig" normalisieren.


----------



## Thallius (5. Aug 2017)

Ich hätte zu gerne mal ein Beispiel aus der Praxis wo Normalisierung Performance kostet. Ich kenne nämlich keines. Klar kann man sich ein theoretisch Beispiel konstruieren aber ich glaube kaum das das irgendwo so vorkommen würde.

Claus


----------



## Meniskusschaden (5. Aug 2017)

Ich glaube ein typisches Beispiel wären Data-Warehouse-Anwendungen. Da hat man für einen OLAP-Würfel ja typischerweise eine Faktentabelle und eine Dimensionstabelle pro Dimension. In der Dimensionstabelle werden dann sämtliche Attribute der Dimension gespeichert (Sternschema, denormalisiert) oder sie werden in eigene Tabellen ausgelagert (Schneeflockenschema, normalisiert). Beim Schneeflockenschema hat man durch die vielen Joins eine schlechtere Performance, aber weniger Redundanz.

An der Stelle finde ich die Denormalisierung unproblematisch und habe deshalb für so etwas immer das Sternschema genutzt (weiß aber nicht, welcher Weg in der Praxis gebräuchlicher ist). Das ändert aber nichts an meiner Meinung, dass bei OLTP-Anwendungen normalisiert werden sollte. Aber auch das kann man natürlich unterschiedlich intensiv betreiben.


----------



## togal (8. Aug 2017)

@normalisierung
Die Einträge xyz und 123 .. können ohne Weiteres für ein wiederkehrendes Merkmal stehen, bspw Herr/Frau oder männlich/weiblich sowie meinetwegen ein Userlevel oder was weiß ich. Das muss nicht zwingend in neuen Tabellen stehen. Ein Foreignkey würde aus Sicht der Subtabelle ebenfalls so erscheinen. Wäre die dargestellte Tabelle eine Adresstabelle, könnte es sich um Verweise auf die Person, Firma etc. handeln.

@Performance
Bleiben wir bei Adressen und Personen und Firmen.
Angenommen ein Modell definiert, dass Personen zu einer Firma gehören und Adressen zu einer Person.
Möchte ich nun eine Liste aller Personen mit ihren Adressen, machen ich eine entsprechende Join Abfrage.
Möchte ich das gleiche, aber eingeschränkt auf eine Firma, oder sogar nur alle Adressen einer Firma, bleibt mir nichts anderes übrig, als diesen Join auf die Firma mit in das Select zu nehmen, obwohl mich die Firma in der Ausgabe nicht interessiert. 

Für sich genommen sind alle Varianten im Beispiel wahrscheinlich nicht performancekritisch. Aber das Beispiel lässt sich auf andere "Welten" übertragen und in größere Tiefen (mehrere Join Ebenen ) aufblasen. Damit komme ich dann zu dem Extremfall, dass ich für ein paar blöde Adressen 5 oder 10 Tabellen rauf/runter joinen muss und das ggF. mit n Millionen Datensätzen je Ebene.

So und zur Denormalisierung: Um mir / der DB diese Mühe zu ersparen, könnte ich auf die Idee kommen, nicht nur in der Person die Firma zu referenzieren, sondern auch in der Adresse. Damit liefe meine obige Abfrage "gib mir die Adressen der Firma xy" gegen eine einzige Tabelle ohne Join. 

Dagegen ist nichts einzuwenden, solange der Performancegewinn den Aufwand für die Umsetzung und Konstistenzwahrung rechtfertigt (und dieser auch  getrieben wird). Alle insert/update/delete müssen nun auf der Adresstabelle per constraint/trigger diese Denormalisierung überwachen und verhindern, dass Adressen einer Person auf unterschiedliche Firmen zeigen.

Man kann an diesem Beispiel auch ungefähr ahnen, woher die Performance kommt oder wo sie bleibt. Und man kann sich ausrechnen, in welcher Art Datenhaltung sich welche Szenarios lohnen würden.
Im denormalisierten System verschiebt sich die Last von der (adhoc oder Reporting) Abfrage, auf den DML Part, also wenn Daten manipuliert werden. 
Man könnte also sagen, in einem (Teil-)System mit mehrheitlich statischen Daten, "gönne" ich mir eher eine Denormalisierung, vor allem wenn viel und umfangreich abgefragt wird. Womit man bei einem DWH wäre.
Umgekehrt wäre ein System mit hochfrequenter Aktualisierung nun ständig damit beschäftigt, die Konstistenz denormalisierter Daten zu kontrollieren, was man natürlich nicht ohne Not macht.


----------



## Thallius (8. Aug 2017)

Also wenn das Model definiert, dass Personen zu den Adressen gehört, und Firmen zu einer Person, nicht aber Firmen zu den Adressen, dann gibt es keine valide Abfrage der Firmen zu den Adressen, denn dann könnte ich auch einen SELECT * auf die Adressen machen.
Von daher ist dein Beispiel nicht realistisch.

Gruß

Claus


----------



## mrBrown (8. Aug 2017)

Thallius hat gesagt.:


> Also wenn das Model definiert, dass Personen zu den Adressen gehört, und Firmen zu einer Person, nicht aber Firmen zu den Adressen, dann gibt es keine valide Abfrage der Firmen zu den Adressen, denn dann könnte ich auch einen SELECT * auf die Adressen machen.
> Von daher ist dein Beispiel nicht realistisch.


Beziehung Person-Firme ist eine "hat mal bestellt bei" und die Firma möchte die Wohnorte ihrer Kunden wissen.
Da ist deine valide Abfrage.


----------



## Thallius (8. Aug 2017)

mrBrown hat gesagt.:


> Beziehung Person-Firme ist eine "hat mal bestellt bei" und die Firma möchte die Wohnorte ihrer Kunden wissen.
> Da ist deine valide Abfrage.



Ja aber das ist eine SELECT * abfrage auf die Adressen Tabelle. Da brauch ich keine join....


----------



## mrBrown (8. Aug 2017)

Thallius hat gesagt.:


> Ja aber das ist eine SELECT * abfrage auf die Adressen Tabelle. Da brauch ich keine join....


Nö, dann hab ich die von Firma A und B, die Kundendaten von Firma B aus Tuvalu sind aber für Firma A aus Deutschland selten relevant.


----------



## togal (9. Aug 2017)

Thallius hat gesagt.:


> Also wenn das Model definiert, dass Personen zu den Adressen gehört, und Firmen zu einer Person, nicht aber Firmen zu den Adressen, dann gibt es keine valide Abfrage der Firmen zu den Adressen, denn dann könnte ich auch einen SELECT * auf die Adressen machen.
> Von daher ist dein Beispiel nicht realistisch.


Ja, Realität und Beispiel gehen oft nicht unter einen Hut. 
Deine Formulierung hätte ich auch nicht gewählt. Mein Beispiel war eine Person gehört zu einer Firma, viele Personen gehören zu einer Firma und zu jeder Person gehört eine oder mehrere Adressen.
Firma 1: Person N, Persom 1: Adresse N
Real könnte man sich ein großes Paketzustellerunternehmen vorstellen, 1000e Filialen/Firmen/Subunternehmer in D, >10T Mitarbeiter. weltweit noch mehr. Die Mitarbeiter bekommen Schulungsmaterial, die Filialen sind nach Umsatz, Fläche, Standort, Standortbewertung usw. kategoriesiert. Die MItarbeiter können sich aussuchen, wohin sie welche Unterlagen bekommen wollen. 
Dann gibt es sowas wie Postoptimierung (Pressepost), Anschreiben werden gebündelt nach PLZ verschickt. Oder es gibt besondere Standorte, deren Einzugs /Liefergebiet auf Bundeslandgrenzen liegt mit unterschiedlichen Feiertagen. 
Die Realität ist meist verrückter als das Drehbuch.
Wie auch immer, benötige ich die Adressen der MItarbeiter spezifischer Firmen/Filialen, dann reicht das Select auf Adressen nicht.
Es ist letztlich auch nicht relevant, wie realistisch das Beispiel ist. Dafür sind Beispiele ja da, sie stecken eine definierte Umgebung ab.
Wenn man sich ein Datenmodell für einen riesigen Online Händler vorstellt, mit dessen Händlern, deren Kunden und Lieferadressen, deren Versandmechnismen und Retourenverwaltung, dann findet man sicher ähnliche oder "schlimmere" Fälle.


----------



## Thallius (9. Aug 2017)

togal hat gesagt.:


> Wie auch immer, benötige ich die Adressen der MItarbeiter spezifischer Firmen/Filialen, dann reicht das Select auf Adressen nicht.



Die Frage ist nun wie Du da die performance optimieren willst indem Du die Normalisierung aufhebst. Mir würden da nur ganz abstrusse Ideen kommen die absolut nicht diskussionsfähig wären. 
Würde so ein Query öfter benötigt, dann würde ich wahrscheinlich einen VIEW dafür anlegen der einmal am Tag aktualisiert wird (So oft ziehen die Mitarbeiter ja wahrscheinlich auch nicht um 

Gruß

Claus


----------



## togal (9. Aug 2017)

Das ist ja der Punkt, Denormalisierung = Abstrus. Man wirft die Sicherheits-/Konsistenzmechnismen über Bord, wenn man denormalisiert oder erst gar nicht normalisiert.
Dennoch ist es legitim zu denormalisieren, wenn man adäquate Ersatzmechnismen schafft.
Und Denormalisierung ist das letzte Mittel, ganz klar. Ein materialized view wäre vorzuziehen, wenn die DB das bietet und die Aktualisierungsfreuqenz ausreicht. Letztlich verbraucht ein materialized View aber auch verhältnismäßig viel Ressourcen, weil er Daten doppel hält und er bricht dabei die Konsistenz, wenn er nicht aktuell ist. Also ist es kein Allheilmittel.

Letztlich ist es vollkommen wurscht, was man als Beispiel nimmt. Und egal ob man Attribute in die Detailebene zieht oder aus ihr raus. Beispiel Produkte, Produkte gehören wahrscheinlich zu den vielfältigsten Entitäten, die man haben kann. Wahrscheinlich hat jeder Entwickler seine Produkttabellen schon mehrmals geändert und nicht nur, weil mehr Infos abzubilden waren, auch wegen der Performance. 
Die Metro hat gerade beschlossen sich aufzuspalten. Ganz genau weiß ich es nicht, aber eine der gröbsten Kategoriesierungen, die man dort bilden kann wäre vermutlich Food/Nonfood, gemäß der Aufspaltung. Dieses Attribut kann ich aus verschiedensten Artikelmerkmalen ableiten, die vielleicht in irgendwelchen Produktdetails verwoben sind, oder ich kann sie auf höchster Ebene, sagen wir der Hauptprodukttabelle, ansiedeln. Dann können die Leiter von Food und Nonfood bequem die Reports für Ihre Sparte auseinander halten. Also die können es eh bequem, weil sie einen One Button Manager Report bekommen, aber wie bequem hat es der Entwickler oder die Datenbank, wenn die Gruppenattribute für die Produktumsatzreports aus den Daten geholt werden. 
Ich wette, diese Merkmale landen früher oder später irgendwo "sehr weit oben".
Schon die Erfassung von PLZ und Ort in einer Adresstabelle sind ein Beispiel für Denormalisierung.

M.E. ist jede Denormalisierung ein Performancegewinn, natürlich mit den Randeffekten, die ich im vorigen Beitrag genannt habe.  
Irgendwann sagt einem vermutlich der gesunde Menschenverstand, dass es nicht sinnvoll ist, für jede Bestellposition, Name und Anschrift des Bestellers zu erfassen.


----------



## JuKu (15. Aug 2017)

@Thallius Der Punkt ist, dass JOINS sehr viel Performance kosten (welche du wiederum bei Normalisierung häufiger benötigst), das steht sogar in den Dokumentationen der Datenbanken drin (z.B. bei MySQL).
Übrigens kannst du das auch selbst prüfen / testen, wenn du ein "EXPLAIN " vor deinen Query schreibst, also z.B. "EXPLAIN SELECT * FROM ... LEFT JOIN ..." und das selbe ohne JOIN mit 2 Einzelabfragen machst. Du wirst dann sehen, dass der JOIN nicht nur extrem viel Rechenzeit intern (die zeigt / gibt dir EXPLAIN mit an) benötigt, sondern auch viel mehr Zeilen betroffen / benötigt werden. Im Worst Case nützen dir nicht mal deine Indexe was.

Die glaube ich einfachste Lösung ist es, die Ergebnisse lokal in einem Cache vorzuhalten und JOINS so gut es geht einzusparen. Also quasi einzeln abfragen (ohne großartige JOINS), aber Einzelergebnisse cachen.

Und das ist nicht so gemeint, dass man alles denormalisieren soll!
Es geht hier eher um Daten, die sehr häufig abgefragt werden. Bin selbst kein großer Freund von Redundanz.


----------



## Meniskusschaden (15. Aug 2017)

JuKu hat gesagt.:


> Im Worst Case nützen dir nicht mal deine Indexe was.


Im Alltag ist der worst case ziemlich uninteressant. Der DB-Administrator beobachtet seine Antwortzeiten und legt gelegentlich Indizes an, die zu den Anforderungen des eigenen Unternehmens passen, so dass man für den grössten Teil der wirklich vorkommenden Abfragen gute Antwortzeiten hat.


JuKu hat gesagt.:


> Die glaube ich einfachste Lösung ist es, die Ergebnisse lokal in einem Cache vorzuhalten und JOINS so gut es geht einzusparen. Also quasi einzeln abfragen (ohne großartige JOINS), aber Einzelergebnisse cachen.


Das ist aus verschiedenen Gründen überhaupt nicht einfach. Einmal muss hier der Entwickler alles im voraus berücksichtigen, was der DB-Admin des Anwenders sonst individuell passend für das eigene Unternehmen optimieren könnte. Das ist für ihn viel einfacher als für den Entwickler, weil er es auf das eigene Mengengerüst abstimmen kann, während der Entwickler für sämtliche Kunden optimieren müsste. Zudem kann er es tun, wenn echte Daten vorliegen, während der Entwickler im voraus meist nur theoretische Volumina kennt.
Die Entwicklung ist natürlich auch viel bequemer, wenn ich einfach eine DB-Anfrage stelle, die mir exakt die benötigten Daten zurück liefert. Da fallen nur zwei Latenz-Zeiten an: Die Anfrage zum DB-Server und seine Antwort mit den Daten. Wenn ich einen eigenen Cache führe, um Joins zu vermeiden, kann  ich ja nicht im ersten Schritt so selektieren, wie ich es benötige. Also hole ich mir entweder mehr Daten als ich brauche (langsamer, weil mehr Volumen) oder ich stelle mehr Anfragen (langsamer, weil mehr Latenzen). Dazu kommen dann noch Konsistenz- und Aktualitätsprobleme bei Updates. Mit anderen Worten: ich verschenke die wunderbare Funktionalität der hervorragend arbeitenden DB-Systeme, um einem völlig überbewerteten Performance-Problem entgegenzuwirken. Klar kosten Joins etwas Zeit, aber sie bieten eben auch enorme Flexibilität und gehören aus gutem Grund zum DB-Alltagsgeschäft. Ich glaube nicht, dass man besser ist, wenn man versucht drum herum zu programmieren. Ausnahme sind spezielle Anwendungsfälle, die zum Teil bereits genannt wurden.


----------



## togal (15. Aug 2017)

@joins: 
Klar, joins kosten Zeit. Denormalisierung spart joins, bringt also Performance. Gefährdet aber Konsistenz und was auch schlimm ist, macht das System unflexibel. Unflexibel im Sinne von Erweiterbarkeit/ DDL. Eine Eigenschaft eines Produktes, die ich als Datensatz anhängen kann, kostet NULL Entwicklungszeit und Downtime. Als zusätzliche Spalte hingegen, sieht das ganz anders aus. Man will nicht jedes mal das 24*7 System anhalten, um neue Spalten dranzubauen, abhängig Objekte auf Konsistenz zu prüfen usw. 

@index: 
Notfalls können Indizierungen sich überschneiden, die DB wählt den passenden je nach Abfrage (oder bekommt per Optimizer Hint einen Wink mit dem Zaunpfahl). Und es gibt keine goldene Regel. Wenn die Anforderungen zu konträr sind, muss das Datawarehouse ran. Alle freuen sich, eine weitere Anwendung, mehr Server, mehr Lizenzen, mehr Geschwindigkeit, mehr Arbeit.

@cache
Da haben wir leider auch noch das Problem, dass DB sich gerade dadurch auszeichnen, gigantische Datenmengen zu verwalten, dabei nutzen sie für gute Leistung selber schon einen erheblichen Teil der Ressourcen als Cache (serverseitig). Man kann das Prinzip auf dem Client natürlich wiederholen. Dann hat man aber zunächst ein Volumen/Transportproblem, weitere unschöne Effekte wurden schon von Menuskusschaden beschrieben. 
Ein schönes Beispiel wie ätzend lokale Replikation sein kann liefert(e) m.E. MS mit seinem unter .Net eingeführten Datatables, die lokale Replikate der Datensätze halten (können) und sogar für offline Arbeit gedacht sind. Tolle Idee, kann riesige Probleme in der Praxis bringen. Ich persönlich bin der Meinung, Hauptziel war die Schonung der DB Server, weniger gleichzeitige Verbindungen, Server Ressourcen sparen so weit es geht.
Klar, viele Wald und Wiesen DBSysteme passen ins RAM eines Entwickler PC. Trotzdem kommt niemand auf die Idee, dass jeder seine eigene DB bekommt.


----------

