Supertyp / Subtyp
Wie wäre es mit einem Blick auf das Supertyp / Subtyp-Muster? Allgemeine Spalten werden in einer übergeordneten Tabelle gespeichert. Jeder einzelne Typ hat eine eigene Tabelle mit der ID des übergeordneten Typs als eigene PK und enthält eindeutige Spalten, die nicht allen Untertypen gemeinsam sind. Sie können eine Typspalte sowohl in übergeordnete als auch in untergeordnete Tabellen aufnehmen, um sicherzustellen, dass jedes Gerät nicht mehr als einen Untertyp haben kann. Erstellen Sie eine FK zwischen den untergeordneten und den übergeordneten Elementen (ItemID, ItemTypeID). Sie können FKs entweder für die Supertyp- oder Subtyp-Tabellen verwenden, um die gewünschte Integrität an anderer Stelle aufrechtzuerhalten. Wenn beispielsweise die ItemID eines beliebigen Typs zulässig ist, erstellen Sie die FK für die übergeordnete Tabelle. Wenn nur auf SubItemType1 verwiesen werden kann, erstellen Sie die FK für diese Tabelle. Ich würde die TypeID aus referenzierenden Tabellen herauslassen.
Benennung
Wenn es um die Benennung geht, haben Sie meines Erachtens zwei Möglichkeiten (da die dritte Wahl von nur "ID" in meinen Augen ein starkes Anti-Muster ist). Rufen Sie entweder den Subtypschlüssel ItemID wie in der übergeordneten Tabelle auf, oder nennen Sie ihn den Subtypnamen wie DoohickeyID. Nach einigem Nachdenken und einigen Erfahrungen damit empfehle ich, es DoohickeyID zu nennen. Der Grund dafür ist, dass, obwohl es Verwirrung über die Subtyp-Tabelle geben könnte, die wirklich verkleidete Elemente enthält (und nicht Doohickeys), dies ein kleines Minus ist, verglichen mit dem Erstellen einer FK für die Doohickey-Tabelle und den Spaltennamen nicht Spiel!
An EAV oder nicht an EAV - Meine Erfahrung mit einer EAV-Datenbank
Wenn EAV das ist, was Sie wirklich tun müssen, dann ist es das, was Sie tun müssen. Aber was wäre, wenn Sie es nicht tun müssten?
Ich habe eine EAV-Datenbank erstellt, die in einem Unternehmen verwendet wird. Gott sei Dank ist der Datensatz klein (obwohl es Dutzende von Elementtypen gibt), sodass die Leistung nicht schlecht ist. Aber es wäre schlecht, wenn die Datenbank mehr als ein paar tausend Elemente enthalten würde! Darüber hinaus sind die Tabellen so schwer abzufragen. Diese Erfahrung hat mich zu dem Wunsch geführt, EAV-Datenbanken in Zukunft möglichst zu meiden.
Jetzt habe ich in meiner Datenbank eine gespeicherte Prozedur erstellt, die automatisch PIVOTed-Ansichten für jeden vorhandenen Subtyp erstellt. Ich kann nur von AutoDoohickey abfragen. Meine Metadaten zu den Untertypen enthalten eine Spalte "ShortName", die einen objektsicheren Namen enthält, der für die Verwendung in Ansichtsnamen geeignet ist. Ich habe sogar die Ansichten aktualisierbar gemacht! Leider können Sie sie bei einem Join nicht aktualisieren, aber Sie können eine bereits vorhandene Zeile in sie einfügen, die in ein UPDATE konvertiert wird. Leider können Sie nicht nur einige Spalten aktualisieren, da Sie in VIEW nicht angeben können, welche Spalten Sie mit dem Konvertierungsprozess INSERT-to-UPDATE aktualisieren möchten: Ein NULL-Wert sieht aus wie "Diese Spalte auf NULL aktualisieren", auch wenn Sie wollten angeben, dass diese Spalte überhaupt nicht aktualisiert werden soll.
Trotz all dieser Dekoration, um die Verwendung der EAV-Datenbank zu vereinfachen, verwende ich diese Ansichten bei den meisten normalen Abfragen immer noch nicht, da sie LANGSAM sind. Abfragebedingungen sind keine Prädikate, die vollständig in die Value
Tabelle zurückgeschoben werden. Daher muss vor dem Filtern eine Zwischenergebnismenge aller Elemente des Typs dieser Ansicht erstellt werden. Autsch. Ich habe also viele, viele Abfragen mit vielen, vielen Verknüpfungen, von denen jede einen anderen Wert erhält und so weiter. Sie arbeiten relativ gut, aber autsch! Hier ist ein Beispiel. Der SP, der dies (und seinen Update-Trigger) erstellt, ist ein riesiges Tier, und ich bin stolz darauf, aber es ist nichts, was Sie jemals versuchen möchten, aufrechtzuerhalten.
CREATE VIEW [dbo].[AutoModule]
AS
--This view is automatically generated by the stored procedure AutoViewCreate
SELECT
ElementID,
ElementTypeID,
Convert(nvarchar(160), [3]) [FullName],
Convert(nvarchar(1024), [435]) [Descr],
Convert(nvarchar(255), [439]) [Comment],
Convert(bit, [438]) [MissionCritical],
Convert(int, [464]) [SupportGroup],
Convert(int, [461]) [SupportHours],
Convert(nvarchar(40), [4]) [Ver],
Convert(bit, [28744]) [UsesJava],
Convert(nvarchar(256), [28745]) [JavaVersions],
Convert(bit, [28746]) [UsesIE],
Convert(nvarchar(256), [28747]) [IEVersions],
Convert(bit, [28748]) [UsesAcrobat],
Convert(nvarchar(256), [28749]) [AcrobatVersions],
Convert(bit, [28794]) [UsesDotNet],
Convert(nvarchar(256), [28795]) [DotNetVersions],
Convert(bit, [512]) [WebApplication],
Convert(nvarchar(10), [433]) [IFAbbrev],
Convert(int, [437]) [DataID],
Convert(nvarchar(1000), [463]) [Notes],
Convert(nvarchar(512), [523]) [DataDescription],
Convert(nvarchar(256), [27991]) [SpecialNote],
Convert(bit, [28932]) [Inactive],
Convert(int, [29992]) [PatchTestedBy]
FROM (
SELECT
E.ElementID + 0 ElementID,
E.ElementTypeID,
V.AttrID,
V.Value
FROM
dbo.Element E
LEFT JOIN dbo.Value V ON E.ElementID = V.ElementID
WHERE
EXISTS (
SELECT *
FROM dbo.LayoutUsage L
WHERE
E.ElementTypeID = L.ElementTypeID
AND L.AttrLayoutID = 7
)
) X
PIVOT (
Max(Value)
FOR AttrID IN ([3], [435], [439], [438], [464], [461], [4], [28744], [28745], [28746], [28747], [28748], [28749], [28794], [28795], [512], [433], [437], [463], [523], [27991], [28932], [29992])
) P;
Hier ist eine andere Art von automatisch generierter Ansicht, die von einer anderen gespeicherten Prozedur aus speziellen Metadaten erstellt wird, um Beziehungen zwischen Elementen zu finden, zwischen denen mehrere Pfade liegen können (insbesondere: Modul-> Server, Modul-> Cluster-> Server, Modul-> DBMS- > Server, Modul-> DBMS-> Cluster-> Server):
CREATE VIEW [dbo].[Link_Module_Server]
AS
-- This view is automatically generated by the stored procedure LinkViewCreate
SELECT
ModuleID = A.ElementID,
ServerID = B.ElementID
FROM
Element A
INNER JOIN Element B
ON EXISTS (
SELECT *
FROM
dbo.Element R1
WHERE
A.ElementID = R1.ElementID1
AND B.ElementID = R1.ElementID2
AND R1.ElementTypeID = 38
) OR EXISTS (
SELECT *
FROM
dbo.Element R1
INNER JOIN dbo.Element R2 ON R1.ElementID2 = R2.ElementID1
WHERE
A.ElementID = R1.ElementID1
AND R1.ElementTypeID = 40
AND B.ElementID = R2.ElementID2
AND R2.ElementTypeID = 38
) OR EXISTS (
SELECT *
FROM
dbo.Element R1
INNER JOIN dbo.Element R2 ON R1.ElementID2 = R2.ElementID1
WHERE
A.ElementID = R1.ElementID1
AND R1.ElementTypeID = 38
AND B.ElementID = R2.ElementID2
AND R2.ElementTypeID = 3122
) OR EXISTS (
SELECT *
FROM
dbo.Element R1
INNER JOIN dbo.Element R2 ON R1.ElementID2 = R2.ElementID1
INNER JOIN dbo.Element C2 ON R2.ElementID2 = C2.ElementID
INNER JOIN dbo.Element R3 ON R2.ElementID2 = R3.ElementID1
WHERE
A.ElementID = R1.ElementID1
AND R1.ElementTypeID = 40
AND C2.ElementTypeID = 3080
AND R2.ElementTypeID = 38
AND B.ElementID = R3.ElementID2
AND R3.ElementTypeID = 3122
)
WHERE
A.ElementTypeID = 9
AND B.ElementTypeID = 17
Der hybride Ansatz
Wenn Sie einige der dynamischen Aspekte einer EAV-Datenbank haben MÜSSEN, können Sie die Metadaten so erstellen, als hätten Sie eine solche Datenbank, aber stattdessen das Entwurfsmuster für Supertyp / Subtyp verwenden. Ja, Sie müssten neue Tabellen erstellen und Spalten hinzufügen, entfernen und ändern. Aber mit der richtigen Vorverarbeitung (wie ich es mit den Auto-Ansichten meiner EAV-Datenbank getan habe) könnten Sie echte tabellenähnliche Objekte haben, mit denen Sie arbeiten können. Nur wären sie nicht so knorrig wie meine und das Abfrageoptimierungsprogramm könnte Push-down auf Basistabellen prädizieren (lesen Sie: gute Leistung mit ihnen). Es würde nur eine Verknüpfung zwischen der Supertyp-Tabelle und der Subtyp-Tabelle geben. Ihre Anwendung kann so eingestellt sein, dass sie die Metadaten liest, um herauszufinden, was sie tun soll (oder in einigen Fällen kann sie die automatisch generierten Ansichten verwenden).
Oder wenn Sie einen mehrstufigen Satz von Untertypen hatten, nur ein paar Verknüpfungen. Mit mehrstufig meine ich, wenn einige Untertypen gemeinsame Spalten haben, aber nicht alle, könnten Sie eine Untertypentabelle für diejenigen haben, die selbst ein Supertyp einiger anderer Tabellen ist. Wenn Sie beispielsweise Informationen zu Servern, Routern und Druckern speichern, kann ein Zwischenuntertyp von "IP-Gerät" sinnvoll sein.
Ich werde den Vorbehalt geben, dass ich noch keine solche hybride Datenbank mit Supertyp / Subtyp-EAV-Metatable-Dekor erstellt habe, wie ich sie hier vorschlage, um sie noch in der realen Welt auszuprobieren. Aber die Probleme, die ich mit EAV hatte, sind nicht klein, und etwas zu tun ist wahrscheinlich ein absolutes Muss, wenn Ihre Datenbank groß sein soll und Sie eine gute Leistung ohne verrückte teure gigantische Hardware wünschen.
Meiner Meinung nach wäre die Zeit, die für die Automatisierung der Verwendung / Erstellung / Änderung realer Subtyp-Tabellen aufgewendet wird, letztendlich die beste. Wenn ich mich auf die Flexibilität der Daten konzentriere, klingt der EAV so attraktiv (und glauben Sie mir, ich liebe es, wenn jemand mich nach einem neuen Attribut für einen Elementtyp fragt, kann ich es in etwa 18 Sekunden hinzufügen und sofort mit der Eingabe von Daten auf der Website beginnen ). Flexibilität kann jedoch auf mehrere Arten erreicht werden! Die Vorverarbeitung ist ein weiterer Weg, dies zu tun. Es ist eine so leistungsstarke Methode, die so wenige Menschen verwenden. Sie bietet die Vorteile einer vollständigen Datensteuerung, aber die Leistung einer harten Codierung.
(Hinweis: Ja, diese Ansichten sind wirklich so formatiert und die PIVOT-Ansichten haben wirklich Update-Trigger. :) Wenn jemand wirklich so an den schrecklichen schmerzhaften Details des langen und komplizierten UPDATE-Triggers interessiert ist, lass es mich wissen und ich werde es posten ein Beispiel für Sie.)
Und noch eine Idee
Schreiben Sie alle Ihre Daten in eine Tabelle. Geben Sie Spalten generische Namen und verwenden / missbrauchen Sie sie dann für mehrere Zwecke. Erstellen Sie Ansichten über diese, um ihnen sinnvolle Namen zu geben. Fügen Sie Spalten hinzu, wenn eine nicht verwendete Spalte vom geeigneten Datentyp nicht verfügbar ist, und aktualisieren Sie Ihre Ansichten. Trotz meiner Länge über Subtyp / Supertyp kann dies der beste Weg sein.