Ich bin gerade dabei ein Datenbankmodell zu erstellen und hänge gerade
an einem kleinen Problem.
Es gibt Artikel und Artikelkategorien. Der Einfachheit halber nehmen wir
an, die Artikel haben nur eine ID und eine Artikelnummer.
Die Produktkategorien haben eine ID und eine sprachabhängige Bezeichnung
1
+--------------------+
2
| Artikel |
3
+====================+
4
| ID integer pk |
5
| ARTNR varchar(20) |
6
+--------------------+
7
8
+----------------------+
9
| Artikelkategorien |
10
+======================+
11
| ID integer pk |
12
| SPRACHE varchar(2) |
13
| BEZ varchar(254)|
14
+----------------------+
Ein Artikel kann nun aber in mehreren Kategorien auftauchen, also
brauchts noch eine Verknüpfungstabelle.
1
+-----------------------+
2
| Art_Zu_Kat |
3
+=======================+
4
| ART_ID integer pk fk |
5
| KAT_ID integer pk fk |
6
+-----------------------+
So, jetzt gefällt mir das aber nicht so ganz, weil die ID der Kategorien
ja nicht "die Kategorie" an sich repräsentiert,
sondern die sprachabhängige Variante.
IMHO sauberer wäre es, das ganze noch weiter zu abstrahieren. Allerdings
hätte meine Artikelkategorien-Tabelle nur eine Spalte, so in etwa:
1
+----------------------+
2
| Artikelkategorien |
3
+======================+
4
| ID integer pk |
5
+----------------------+
6
7
+----------------------+
8
| Artikelkategorien_BEZ|
9
+======================+
10
| ID integer pk |
11
| KAT_ID integer fk |
12
| SPRACHE varchar(2) |
13
| BEZ varchar(254)|
14
+----------------------+
Meine Frage ist: Was ist "Best Practice" oder auch, wie würdet ihr das
machen?
foo schrieb:> Allerdings> hätte meine Artikelkategorien-Tabelle nur eine Spalte, so in etwa:
warum nicht beide Tabellen zusammenfassen( Artikelkategorien,
Artikelkategorien_BEZ )
Peter II schrieb:> warum nicht beide Tabellen zusammenfassen( Artikelkategorien,> Artikelkategorien_BEZ )
Öhm, weil dann KAT_ID nicht mehr eindeutig ist und die Zuordnung zu den
Artikeln über die Verknüpfungstabelle nichtmehr funktioniert?
Zusatzinfo (entschuldigt vielmals, ich mag auch keine Salamitaktik):
Das ganze bekommt ein Persistenzframework (JPA o.ä.) übergestülpt.
Warum hat Artikelkategorien_BEZ zwei IDs? Der eigentliche
Primärschlüssel besteht doch aus KAT_ID und SPRACHE.
Aber sonst ist dies richtig. In der Tabelle Artikelkategorien werden
Attribute gespeichert, die nicht sprachabhängig sind; in diesem Beispiel
gibt es keine.
foo schrieb:> Öhm, weil dann KAT_ID nicht mehr eindeutig ist und die Zuordnung zu den> Artikeln über die Verknüpfungstabelle nichtmehr funktioniert?
versteht ich (noch) nicht. Es gibt Artikelkategorien die sind eindeutig,
die haben eine ID und eine Bezeichnung - also ein Tabelle.
Dann gibt es Artikel, sind auch eindeutig - also noch eine Tabelle.
Dann gibt es eine Tabelle mit Artikel zu Artikelkategorien.
Clemens L. schrieb:> Warum hat Artikelkategorien_BEZ zwei IDs? Der eigentliche> Primärschlüssel besteht doch aus KAT_ID und SPRACHE.
Richtig, deshalb auch mein Nachschub mit JPA.
Irgendwo muss ich halt Komplexität lassen.
Wenn ich composite primary keys verwende, dann ist das Datenbankmodell
einfacher, dafür die Persistenzschicht komplexer.
Oder ich hab wie hier eine id-spalte, die den pk darstellt, dann wirds
wieder in der Persistenzschicht übersichtlicher.
> Aber sonst ist dies richtig. In der Tabelle Artikelkategorien werden> Attribute gespeichert, die nicht sprachabhängig sind; in diesem Beispiel> gibt es keine.
Also würdest du zur zweiten Variante tendieren? (Und ja, klar kann noch
überall was dazukommen, wie das halt immer so ist)
Ich verwende meist eine "Internationalisierung"-Tabelle:
Group_ID (PK)
Country_Code (PK)
Caption_Id (PK)
Caption
In den Tabellen, in denen Inhalte Sprachabhängig sind:
Caption_Id als FK ggfs. auch Group_id.
Group_id kann dann beispielsweise den Inhalt haben:
"Handscanner"
"GUI_abc"
Innerhalb einer Gruppe muss die Kombination {Country_Code,Caption_id}
eindeutig sein.
Somit ist man generisch für alle Möglichen Kombinationen.
Bei dir könnte beispielsweise die Gruppe mit der Gruppen_Id "Kategorien"
genutzt werden.
Aber der Nachteil ist, dass es abstrakter ist. Im Quelltext verknüpfe
ich dann immer Group_Id (statisch und bekannt), country_code aus
irgendeiner Quelle und den Caption_id (entweder hinterlegt als Tag eines
Steuerelements oder statisch).
Wenn du ein ORM verwenden möchtest, hast du weitestgehend Recht mit der
Aussage keine Kompositprimärschlüssel zu verwenden, dazu rate ich dir
auch. Integer für Primär- und Fremdschlüssel sind am einfachsten zu
handhaben. Möchte man uniqueness oder effiziente Abfragen mit den
betreffenden Attributen erreichen, kann man immer noch einen seperaten
Index spendieren. Für kleine bis mittelgroße Datenmengen wird das nicht
ins Gewicht fallen.
Handelt es sich hier um eine Übungsaufgabe oder warum möchtest du das
Rad neu erfinden? Gerade bei E-Commerceanwendungen gibt es einiges zu
beachten, je nach Anforderung.
Abradolf L. schrieb:> Handelt es sich hier um eine Übungsaufgabe oder warum möchtest du das> Rad neu erfinden? Gerade bei E-Commerceanwendungen gibt es einiges zu> beachten, je nach Anforderung.
Es ist quasi eine selbstgestellte Übungsaufgabe um mal warm mit JPA und
JAX-RS zu werden.
Schlussendlich wird das aber doch eine produktive Anwendung werden.
Aber kein Webshop oder ähnliches, eher eine firmeninterne REST-API, die
aus diversen Gründen aber ein eigenes Datenbankbackend braucht - und
jenes will ich halt gleich von Grund auf vernünftig aufgebaut haben,
daher die Frage.
Ich frage ja auch nach "Best Practice"-_Erfahrungen_ - Das ganze wird
sich ja nicht auf sprachabhängige Kategorien beschränken.
Sagen wir mal, es kommen sprachabhängige "Anwendungsgebiete" hinzu.
Basteln wir mal ein konkretes Beispiel:
Ich hab einen Artikel mit ID 1 und Artikelnummer 'L42' - z.B. ein
Lötkolben
Den will ich jetzt in den Produktkategorien "Lötkolben" und
"Brennwerkzeuge" haben.
Ausserdem in den Anwendungsgebieten "löten" und "brennkunst"
Das ganze jetzt in den Sprachen de, en, fr und it.
Bei meiner ersten Variante hätte ich dann 8 Kategorie-IDs und 8 Einträge
in der Zuordnungstabelle.
Bei der zweiten Variante hätte ich 2 Kategorie-IDs und 2 Einträge in
der Zuordnungstabelle. Dazu noch 8 Einträge in "Artikelkategorien_BEZ"
und eine extra Tabelle, die (momentan) nur aus der Spalte mit der
Kategorie-ID besteht.
Das selbe dann nochmal für die Anwendungsgebiete.
Die zweite Variante erscheint mir sauberer aber vielleicht auch etwas
over-engineered, wogegen die erste sich irgendwie falsch "anfühlt".
foo schrieb:> Ich frage ja auch nach "Best Practice"-_Erfahrungen_ - Das ganze wird> sich ja nicht auf sprachabhängige Kategorien beschränken.> Sagen wir mal, es kommen sprachabhängige "Anwendungsgebiete" hinzu.
Ja es gibt Best Prictices, aber die sehen je nach Anwendungsfall
unterschiedlich aus. Zuerst einmal würde ich dich bitten, dich über
Datenbanknormalisierung zu informieren:
https://de.wikipedia.org/wiki/Normalisierung_(Datenbank)
Man sollte verstehen warum das gemacht wird. Nun kann es zu Fällen
kommen, die dazu führen, dass man sich bewusst gegen Normalisierung
entscheidet (meist, aber nicht ausschließlich, performanzbedingt).
Als erstes stehen allerdings die Anforderungen um ableiten zu können was
gespeichert werden soll und in welcher Beziehung die Entitäten
miteinander stehen.
Wenn du dich in Foren nach Lösungen zu Datenbankdesignproblemen
erkundigst wirst du auf folgendes Problem stoßen: Antworten reichen von
der reinen Theoriefront "wie es sein sollte" bishin zu den
Praktikerlösungen "hauptsache es tut". Beide Extrema sind meist nicht
zielführend.
Wenn du ein konkretes Problem und dessen Anforderungen schilderst,
also nichts in der Form "wenn man mal annimmt" oder "stellen wir uns
vor", versuche ich dir Input zu geben.
Vielen Dank schonmal für deine Mühe.
> Zuerst einmal würde ich dich bitten, dich über> Datenbanknormalisierung zu informieren:
Hm, ich dachte eigentlich, es wäre offensichtlich, dass ich mich damit
bereits beschäftigt habe.
Alles was ich hier aufgeführt habe ist doch bereits durchnormalisiert.
> Wenn du ein konkretes Problem und dessen Anforderungen schilderst,> also nichts in der Form "wenn man mal annimmt" oder "stellen wir uns> vor", versuche ich dir Input zu geben.
Mein wirkliches, konkretes Problem ist im Eingangspost beschrieben.
Die weiteren Ausführungen meinerseits waren u.a. gedacht um die
Nachfragen von Clemens und Peter zu beantworten bzw. meine Problematik
nochmals zu verdeutlichen.
> Antworten reichen von> der reinen Theoriefront "wie es sein sollte" bishin zu den> Praktikerlösungen "hauptsache es tut". Beide Extrema sind meist nicht> zielführend.
So wie ich das momentan sehe, ist meine erste "Lösung" aus der Kategorie
"hauptsache es tut" und die Zweite aus "wie es sein sollte".
Eine ganz andere Alternative mit der "Internationalisierung"-Tabelle hat
Jörg ja vorgestellt, aber das bekommt man so garnicht vernünftig in ein
ORM verpackt.
Ich habe ja vermutet, dass ich einfach nur auf dem Schlauch stehe und
die offensichtlich beste (andere) Lösung, die jedem anderen sofort ins
Gesicht springt, sich mir verschliesst.
foo schrieb:> Was ist "Best Practice"
Nicht zu weit normalisieren, sonst wird die Datenbank zu langsam.
Zähle die Anzahl der Datensatz-Zugriffe bei den üblichen Anfragen (z.B.
"Artikelliste") und versuche sie zu reduzieren, auch wenn die Redundanz
dadurch zunimmt.
Man KANN also in einem Artikel ein Feld Kategorie-ID aufnehmen und jeden
Artikel der in mehreren Kategorien ist auch mehrfach speichern. Man kann
auch die Umsetzung Kategorie-ID in Text nur ein mal aus der Datenbank
laden denn die Sprache wird sich ja im Programmlauf nicht ändern, so daß
diese Tabelle nie mehr abgefragt wird.
Das Programm sollte es nicht interessieren, wie die Daten gespeichert
sind, egal ob stark normalisiert oder als bloss ein Datensatz, denn die
Anfrage erfolgt in SQL möglichst sowieso nur durch eine View die genau
das zusammenstellt und liefert was benötigt wird. Beim Hineinspeichern
sollte man Code schreiben, so daß es dem auch egal ist, welche
Tabellenstruktor vorliegt, weil er Schlüssel ggf. dazuerfindet
(AUTOINCREMENT). Alle Felder die KEINE Schlüsselwerte (KEYs) sind und
nie als Suchbegriff verwendet werden, können sogar komprimiert als BLOB
am Datensatz hängen weil sie aus Sicht der Datenbank völlig egal sind.
> Das ganze bekommt ein Persistenzframework (JPA o.ä.) übergestülpt.
Ok, wenn dir das Tempo der Anwendung egal ist, kann man das so machen.
foo schrieb:> Ich habe ja vermutet, dass ich einfach nur auf dem Schlauch stehe und> die offensichtlich beste (andere) Lösung, die jedem anderen sofort ins> Gesicht springt, sich mir verschliesst.
Dann versuch ich es mal :)
Also es gibt:
Artikel
ArtikelID, Preis, Gewicht
ArtikelBezeichnung
ArtikelBezeichnungID, ArtikelID, SpracheID, Name, Beschreibung
ArtikelZuKategorie
ArtikelZuKategorieID, ArtikelID, KategorieID
Kategorie
KategorieID, ElternID
KategorieBezeichnung
KategorieBezeichnungID, KategorieID, SpracheID, Name, Beschreibung
Ziemlich genau das, was du oben selber schon vorgeschlagen hast.
Warum gibt es "ArtikelBezeichnungID" usw? Einfach damit du bei der
Bearbeitung (gerade wenn du ein ORM wie Doctrine verwendest) einfacher
die richtige Zeile findest. Theoretisch könnte man genausogut einen
zusammengesetzten Schlüssel aus ArtikelID und SprachID nehmen.
Performancemäßig nimmt sich beides wenig.
Beispiele für solche Schema sind z.B. OsCommerce, Joomla, Typo3.
Willst du jetzt eine Artikelliste anzeigen machst ud entsprechend
"SELECT * FROM Artikel INNER JOIN ArtikelZuKategorie USING ArtikelID)
INNER JOIN ArtikelBezeichnung USING (ArtikelID) WHERE SpracheID = 3 AND
KategorieID = 42"
Sieht komplex aus, aber die Datenbank verarbeitet solches Joins extrem
schnell.
Sinnvoll ist diese Trennung immer bei allen Sprachabhängigendaten,
jedenfalls wenn in der Praxis am Ende >3 Sprachen zu Verwalten sind.
Ansonsten müsste man sich das Verhältnis von write/read usw. anschauen
um da etwas zu optimieren, aber bei Anwenungen unter 1M Einträge völlig
irrelefant. Da zählt eher die saubere, logische Aufschlüsselung als
Performance.
Hallo waflija,
danke für deine Ausführungen.
Ja richtig, das ist alles in etwa genau das, was ich als die "sauberere"
Variante gedacht hatte.
waflija schrieb:> Kategorie> KategorieID, ElternID
Die ElternID hast du mit drin, um auch Unterkategorien abzubilden nehm
ich an?
Ich glaub, ich werd es wirklich so machen.
foo schrieb:> Die ElternID hast du mit drin, um auch Unterkategorien abzubilden nehm> ich an?
richtig. Dafür wäre sowas ideal, aber ich brauchte eigentlich nur ein
weiteres Beispielfeld um da was hin zu schreiben :)
Das nennt sich self-referencing key (selbstrefenzierender Schlüssel). -
Und kann so auch in MySQL mit InnoDB oder MsSQL per
Fremdschlüsselbeziehung abgebildet werden.
Auslesen würde man sowas dann mit:
getChildren($kategorieID) {
"SELECT * FROM kategorien WHERE elternID = $kategorieID"
foreach($result as $newID) {
getChildren($newID);
}
}
Wenn dass rekursiv laufen lässt, kann man einen beliebig tiefen Baum
aufbauen. Dann muss man nur aufpassen, dass keine Kreisreferenzen
existieren. (also sich irgendwo der Baum zu einem Ring schließt, dann
würde die Funktion unendlich laufen)