Designteile DB

8

Ich entwickle ein Werkzeug, das (elektrische) Teile handhabt. Die Teile können erstellt, angezeigt, geändert, gelöscht, gruppiert usw. werden.

Um diese Frage für zukünftige Besucher nützlich zu machen, möchte ich diese Frage universell halten, da das Verwalten von Teilen in einer Datenbank sehr häufig ist, unabhängig davon, welche Teile sich in der Datenbank befinden (CDs, Autos, Lebensmittel, Studenten, ...).

Ich denke an 3 verschiedene DB-Designs:

  1. Verwenden einer Teiletabelle und abgeleiteter Tabellen für spezielle Teileattribute.

    Parts      (id, part_type_id, name)
    PartTypes  (id, name)
    Wires      (id, part_id, lenght, diameter, material)
    Contacts   (id, part_id, description, picture)
    
  2. Verwenden Sie nur spezielle Teiletabellen.

    Wires      (id, name, lenght, diameter, material)
    Contacts   (id, name, description, picture)
    
  3. Verwenden einer Parts-, PartTypes-, ValueTypes- und PartValues-Tabelle, die alle Werte enthält.

    PartTypes  (id, name)
    ValueTypes (id, part_type_id, name)
    Parts      (id, part_type_id, name)
    PartValues (part_id, value_type_id, value)
    

Welches bevorzugen und warum? Oder gibt es eine bessere?
Ich bin besorgt über die DB-Abfragen. Ich möchte nicht, dass die Abfragen zu langsam oder zu kompliziert werden.

Aktualisieren

Die Anzahl der Typen in der Datenbank ist ziemlich gegeben und statisch, da sie auf einem internationalen Standard beruhen und selten erweitert werden.

Jürgen d
quelle
Geht es um rein SQL-DBs (rein relational) oder ist auch NOSQL-DB eine Option?
C-Smile
@ c-smile: Da ich noch nicht mit NOSQL gearbeitet habe, weiß ich nicht wirklich, ob es eine Option ist. Ich bin offen für alles.
Jürgen d

Antworten:

16

Option 3 : (manchmal)
Option 3 ist das "EAV" -Design. Theoretisch ist es schön, weil die Felder aus der Tabellenstruktur herausgenommen werden und zu Daten werden. Aber es gibt schreckliche Leistung. Es verbietet auch die Verwendung einer ordnungsgemäßen Indizierung. Und es macht Abfragen viel komplizierter.

Ich würde EAV nur unter besonderen Umständen verwenden. Ich habe EAV verwendet, um die für Bestellungen benötigten Hilfsteile zu berechnen, und es hat gut funktioniert. Aber seien Sie sehr müde, es als Design für Ihre Kerntabellen zu verwenden.

Option 2 : (nie?)
Option 2 ist ein Nein Nein. Was ist mit den freigegebenen Feldern? Werden Sie die Tabellenstruktur für jedes gemeinsam genutzte Feld duplizieren? Dazu müssten Sie Gewerkschaften in Berichte des gesamten Systems aufnehmen.

Option 1 : (Gewinner!)
Option 1 mag etwas zu einfach erscheinen, ist aber wahrscheinlich die beste Wahl für Ihre Kerntabellen. Alle Teile verwenden dieselbe Haupttabelle für freigegebene Felder, um Vereinigungen in Ihren Berichten zu vermeiden. Es hat eine großartige Leistung, die die ordnungsgemäße Verwendung der Indizierung ermöglicht. Abfragen sind im traditionellen Stil und einfach.

Der Nachteil von Option 1 ist, dass Sie Felder nicht dynamisch hinzufügen können. Aber willst du das wirklich? Durch dynamisches Hinzufügen von Feldern führen Sie zur Laufzeit ein Datenbankdesign durch.

mike30
quelle
+1, aber schauen Sie sich meine Antwort an, um zu sehen, was die Gründe für Option 2 sein könnten.
Doc Brown
Nach einigem Überlegen und basierend auf den Anmerkungen des OP, dass die Teile ein absolut fester Standard gemäß Verordnung sind, stimme ich Option 1 und +1 für die gute Antwort zu, obwohl er auf jeden Fall bedenken sollte, dass Option 3 eine Migration sein kann Punkt in der Zukunft, auch wichtig, weil es sonst niemand erwähnt hat: Äußere Verknüpfungen weisen im Allgemeinen schlechte Leistungseigenschaften auf und sollten nach Möglichkeit vermieden werden. Fügen Sie dies hinzu, da Option 1 äußere Verknüpfungen umfasst, aber in diesem Fall sind die Kosten wahrscheinlich immer noch wert Option 3 hat ihre eigenen Leistungsprobleme.
Jimmy Hoffa
2
Option 1 scheint zu einfach? Auf keinen Fall, das ist definitiv der Weg, es zu tun. Jimmy ist falsch, äußere Verbindungen haben im Allgemeinen keine schlechten Leistungseigenschaften. Solange Sie richtig indizieren, ist es in Ordnung.
Rocklan
6

Ich würde dazu neigen, Option 3 nicht zu wählen.

Option 3 ist die Einrichtung von Name-Wert-Paaren, die gegen die Normalisierung verstößt.

Im Idealfall wird versucht, ein gewisses Maß an Normalisierung der Datenbank zu erreichen. Streben Sie eine vollständige Normalisierung an und denormalisieren Sie sie nach Bedarf, wenn sie für Anpassungs- oder Leistungsprobleme identifiziert wird.

Betrachten Sie die Abfrage "Wie lauten die Namen und Teile-IDs für alle Drähte aus Kupfer?"

Struktur # 1 ist

select
  name, parts.id
from
  wire, parts
where
  wire.material = 'copper'
  and wire.part_id = parts.id

Struktur # 2 ist

select id, name from wire where material = 'copper'

Struktur # 3 ist

select
  parts.name,
  parts.id,
from
  parts, part_types, part_values, value_types
where
  part_types.name = "wire"
  and parts.part_type_id = part_types.id
  and value_types.name = "material"
  and value_types.id = part_values.type_value_id
  and part_values.value = "copper"

Berücksichtigen Sie auch die Komplikation von Einfügungen und Löschungen aus dem System.

Lesen Sie weiter, warum nicht # 3 - Der Fluch des Name-Wert-Paares


quelle
2
Ja, das Name-Wert-Paar ist böse. Ich denke, alle sind sich einig, aber es geht weiter, weil es ein notwendiges Übel ist. Vielleicht ist # 3 hier nicht notwendig, aber es scheint sehr ähnlich zu sein, als ob die Tabellenstrukturen, die ich gesehen habe, unhaltbar geworden sind und schließlich eine Denormalisierung der Namenswertpaarform benötigen. Wenn es jedoch behoben ist, ist # 1 vielleicht der richtige Ansatz (vorausgesetzt, Abfragen würden auf Aggregate verschiedener Teile einwirken wollen, andernfalls ist # 2 in Ordnung)
Jimmy Hoffa
Auch sind Sie nicht mit schließt sich hier, die endet Putting unnötige Arbeit in die where - Klausel, die in der Verbindung wie das gehen würde , part_type_id = part_types.idund value_types.id = part_values.type_value_idsind beide Klauseln beitreten verlassen die wo nach wo ein Teil Typ Draht ist, Werttyp ist Material und Wert ist Kupfer Das ist relativ prägnant
Jimmy Hoffa
@ JimmyHoffa Ich habe gerade eine kurze Kurzversion gemacht, um zu zeigen, wie es aussehen würde, anstatt ideales SQL. Die dritte Option, die ich in Redmines Tabellenstruktur gesehen habe, bei der Name / Wert-Paare dem System im laufenden Betrieb hinzugefügt werden. Es ist unpraktisch, Datenbankaktualisierungen durchführen zu müssen, um ein neues benutzerdefiniertes Feld hinzuzufügen. Daher ist der Namenswert die geeignete Struktur. Dadurch werden Datenbankabfragen jedoch etwas langsamer (Indizes sind nicht so zufrieden, da der Typ für alles zu Zeichenfolgen wird) und Abfragen etwas hässlich.
1
Das letzte Mal, als ich Option 3 ausgeführt habe, war es in MSSQL und ich habe den Typ SQL_Variant verwendet. Ich glaube, solche Indizes sind etwas mehr als Zeichenfolgen, da sie nach Typ und Wert katalogisiert werden, wenn ich mich nicht irre, obwohl es immer noch komplexer ist Ansatz und wie Sie sagten, ist es am besten, wenn Sie wissen, dass es ein stetiges Wachstum neuer Typen geben wird. Als ich dies das letzte Mal tat, wurde eine Tabelle mit 60 Spalten konvertiert. 1 für jeden Schlüssel, der stetig gewachsen ist, so dass diese Szenarien offensichtlich auftreten, aber vielleicht ist dies nicht einer von ihnen, der vom OP identifiziert werden müsste.
Jimmy Hoffa
4

Ich gehe Option 3

Option 1 ist schlecht, da Sie nicht möchten, dass Ihre Joins auf einem abgelegten Wert basieren. (dh If type ="Wire" join to TblWire)

Option 2 ist schlecht, da Sie keine Möglichkeit haben, über Ihr gesamtes Inventar zu berichten

Idioten
quelle
Beachten Sie auch, dass Option 3 die besten Wartungseigenschaften für neue Teileattribute aufweist. Ich beziehe mich auf dieses Formular (obwohl ich sicher bin, dass es unter DBAs einen gemeinsamen Begriff für diese Struktur gibt, die mir fehlt) als schwenkbares Formular, da es sich um einen Drehpunkt handelt Von der allgemeineren Struktur, die Sie in Nr. 1 und Nr. 2 beschrieben haben, erstellen Benutzer häufig Nr. 1, um am Ende neue Tabellen / Spalten für neue Typen hinzuzufügen, sodass sie nach einem großen Durcheinander häufig auf Nr. 3 wechseln müssen sie können nicht mehr pflegen.
Jimmy Hoffa
Für Option 1 benötigen Sie vor einem Join niemals ein "Wenn" für den Typ. Wenn es erfolgreich verbunden ist, ist es der Typ. Joins selbst könnten Filter ersetzen. Sie könnten so weit gehen, den Typ nicht mehr zu speichern.
Mike30
@mike was ist, wenn er 2 Produkttypen will? Wenn Kabel mit "Kabeln" verbunden sind, wenn Steckverbinder mit "Steckverbindern" verbunden sind, wenn er mit beiden verbunden ist, bekommt er nichts! Wenn er mitmacht, bekommt er Duplikate!
Idioten
@ Morons. Links verbinden Sie den Master mit den Untertabellen. Filter, bei dem calbles.ID nicht null und connectors.ID nicht null ist. Viola! Verwenden Sie den Erfolg des Joins als Filter.
Mike30
2
@ Morons: Das Wiederholen des Wortes "Albtraum" macht es nicht wahrer. Wenn man beim Erstellen eines neuen Typs "den gesamten Code" ändern muss, hat dies nichts mit "Option 1" oder "Option 3" zu tun. Es muss tun, wie gut der Code strukturiert ist. Und dass man an einigen Stellen Code ändern muss, wenn eine neue Anforderung eintrifft, ist kein "Albtraum", das ist einfach normal (und auch für Option 3 notwendig). Bevor Sie weiter argumentieren, sollten Sie sich über die Fälle informieren, in denen das Entity-Attribute-Value-Muster angemessen ist, und wenn nicht . EAV ist manchmal ein Anti-Muster.
Doc Brown
4

Ich würde mit einem Daten- / Objektmodell beginnen, das die Vererbung zulässt, und dann eine standardmäßige objektrelationale Zuordnung verwenden . Auf diese Weise kann eine Basisklasse erhalten Partsund Unterklassen wie Wires, Contactsusw. Nun, wenn Sie eine „Karte-jede-Klasse-to-own-Tabelle“ Strategie anwenden, erhalten Sie die Option 1, die die meist „normalisierte“ Lösung ist und sollte die kanonische Strategie sein, wenn Sie keine weiteren Informationen zu den von Ihnen erwarteten Abfragen haben.

Option 2 erhalten Sie, wenn Sie einen "Map-Each-Concrete-Class-to-Own-Table" -Ansatz anwenden. Dies kann "Verknüpfungen" vermeiden und bei Abfragen (insbesondere bei Abfragen nur für einen "Teiletyp") eine bessere Leistung erzielen. Andererseits wird die generische Behandlung mit allen Teilen schwieriger und langsamer. Vermeiden Sie dies, wenn Sie keine besonderen Gründe dafür haben.

Option 3 ist nur dann erforderlich, wenn der Benutzer die Anzahl der Teiletypen zur Laufzeit ändern soll. Wenn Sie diese Anforderung nicht erwarten, ist Option 3 ein perfektes Beispiel für Überentwicklungen.

Doc Brown
quelle
2

Mit der NOSQL DB-Datenbank (wie zum Beispiel MongoDB) benötigen Sie nur einen Satz mit dem Namen "Parts". Jeder Teil in diesem Satz wird als Dokumentdatensatz mit variablem Feldsatz bezeichnet:

{
   "_id": ObjectId("4efa8d2b7d284dea1"),
   "partType": "wire",
   "length": 102.5,
   "diameter": 1.5,
   "material": "silver"
}, 
{
   "_id": ObjectId("4efa8d2b7d284sjsq23d"),
   "partType": "contact",
   "description": "something",
   "picture": Binary(...)
}, 

Ich denke, dass dies der natürlichste Datenspeicher für die von Ihnen beschriebene Aufgabe ist.

c-smile
quelle
2

Entscheiden Sie sich auf jeden Fall für Option 1, aber mit ein paar sehr einfachen Änderungen:

Parts      (id, part_type_id, name)
PartTypes  (id, name)
Wires      (id, part_id, part_type_id, lenght, diameter, material)
Contacts   (id, part_id, part_type_id, description, picture)

Sie können dann CHECK-Einschränkungen und DEFAULT-Werte verwenden, um sicherzustellen, dass die part_type_id korrekt ist. Anschließend können Sie sowohl part_type_id als auch part_id verbinden. Dadurch wird vermieden, dass eine bedingte Verknüpfung nur auf einer Tabelle basiert. Wenn Sie Drähten eine part_type_id hinzufügen müssen (sagen wir, wir unterteilen diesen Teil und fügen eine weitere Tabelle mit erweiterten Attributen hinzu), können die Standard- und Überprüfungsbeschränkungen geändert werden.

Chris Travers
quelle
Sie können auch (sicher - es sei denn, einige ORM erfordern einspaltige Primärschlüssel) das wires.idund entfernen, contacts.idda die (part_id, part_type_id)Kombination ausreicht, um ein Teil eindeutig zu identifizieren.
Ypercubeᵀᴹ
@ypercube, klar, aber da part_id in diesem Fall eindeutig ist, verwenden Sie es einfach als Primärschlüssel mit einem sekundären eindeutigen Index für part_id und part_type_id, wenn Sie möchten.
Chris Travers
1

Option 3 ist allgemeiner und kann mehr Anwendungsfälle berücksichtigen.

Wenn Sie Option 3 wählen, benötigen Sie möglicherweise mehr Verknüpfungen und komplexe Abfragen für einfache Funktionen. In Option 2 benötigen Sie komplexe Abfragen für "große" Funktionen wie Inventar und Berichte und müssen möglicherweise Gewerkschaften verwenden, um dies zu erreichen.

Sie können Ihre Abfragen in Option 3 mithilfe von Ansichten jederzeit vereinfachen. Wenn Sie häufig nur den Draht oder Kontakt benötigen, erstellen Sie für jede eine Ansicht. Sie können es optimieren, wenn es notwendig wird.

RMalke
quelle