Forum: PC-Programmierung [SQL] Datenbankdesign - Grundlegende Entscheidung


von foo (Gast)


Lesenswert?

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?

von Peter II (Gast)


Lesenswert?

foo schrieb:
> Allerdings
> hätte meine Artikelkategorien-Tabelle nur eine Spalte, so in etwa:

warum nicht beide Tabellen zusammenfassen( Artikelkategorien, 
Artikelkategorien_BEZ )
1
+----------------------+
2
| Artikelkategorien    |
3
+======================+
4
| ID       integer  pk |
5
| KAT_ID   integer  fk |
6
| SPRACHE  varchar(2)  |
7
| BEZ      varchar(254)|
8
+----------------------+

von foo (Gast)


Lesenswert?

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.

von Clemens L. (c_l)


Lesenswert?

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.

von Peter II (Gast)


Lesenswert?

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.

von foo (Gast)


Lesenswert?

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)

von Jörg M. (derlang)


Lesenswert?

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).

: Bearbeitet durch User
von Cyblord -. (Gast)


Lesenswert?

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.

von foo (Gast)


Lesenswert?

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".

von Cyblord -. (Gast)


Lesenswert?

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.

von foo (Gast)


Lesenswert?

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.

von Michael B. (laberkopp)


Lesenswert?

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.

von waflija (Gast)


Lesenswert?

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.

von foo (Gast)


Lesenswert?

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.

von waflija (Gast)


Lesenswert?

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)

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.