web-dev-qa-db-de.com

Wie entwerfe ich eine Datenbank für benutzerdefinierte Felder?

Meine Anforderungen sind:

  • Müssen in der Lage sein, benutzerdefinierte Felder eines beliebigen Datentyps dynamisch hinzuzufügen
  • Müssen in der Lage sein, UDFs schnell abzufragen
  • Sie müssen in der Lage sein, Berechnungen für UDFs basierend auf dem Datentyp durchzuführen
  • Müssen in der Lage sein, UDFs basierend auf dem Datentyp zu sortieren

Andere Informationen:

  • Ich suche in erster Linie Leistung
  • Es gibt einige Millionen Stammdatensätze, an die UDF-Daten angehängt werden können
  • Als ich das letzte Mal nachgesehen habe, waren über 50 Mio. UDF-Datensätze in unserer aktuellen Datenbank
  • In den meisten Fällen ist eine UDF nur einigen Tausend Master-Datensätzen zugeordnet, nicht allen
  • UDFs werden nicht verbunden oder als Schlüssel verwendet. Es handelt sich lediglich um Daten, die für Abfragen oder Berichte verwendet werden

Optionen:

  1. Erstellen Sie eine große Tabelle mit StringValue1, StringValue2 ... IntValue1, IntValue2, ... usw. Ich hasse diese Idee, werde sie aber in Betracht ziehen, wenn mir jemand sagen kann, dass sie besser ist als andere Ideen und warum.

  2. Erstellen Sie eine dynamische Tabelle, die bei Bedarf eine neue Spalte hinzufügt. Ich mag diese Idee auch nicht, da ich der Meinung bin, dass die Leistung langsam wäre, wenn Sie nicht jede Spalte indizieren würden.

  3. Erstellen Sie eine einzelne Tabelle mit UDFName, UDFDataType und Value. Wenn eine neue UDF hinzugefügt wird, generieren Sie eine Ansicht, in der nur diese Daten abgerufen und in den angegebenen Typ analysiert werden. Elemente, die die Analysekriterien nicht erfüllen, geben NULL zurück.

  4. Erstellen Sie mehrere UDF-Tabellen, eine pro Datentyp. Wir hätten also Tabellen für UDFStrings, UDFDates usw. Würden wahrscheinlich das Gleiche tun wie Nummer 2 und eine Ansicht automatisch generieren, sobald ein neues Feld hinzugefügt wird

  5. XML-Datentypen? Ich habe noch nie damit gearbeitet, aber ich habe gesehen, dass sie erwähnt wurden. Ich bin mir nicht sicher, ob sie mir die gewünschten Ergebnisse liefern würden, insbesondere bei der Leistung.

  6. Etwas anderes?

136
Rachel

Wenn Leistung das Hauptanliegen ist, würde ich mit # 6 gehen ... eine Tabelle pro UDF (wirklich, das ist eine Variante von # 2). Diese Antwort ist speziell auf diese Situation zugeschnitten und die Beschreibung der Datenverteilung und Zugriffsmuster beschrieben.

Vorteile:

  1. Da Sie angeben, dass einige UDFs Werte für einen kleinen Teil des Gesamtdatensatzes enthalten, bietet eine separate Tabelle die beste Leistung, da diese Tabelle nur so groß ist, wie es für die Unterstützung der UDF erforderlich ist. Gleiches gilt für die verwandten Indizes.

  2. Sie erhalten auch einen Geschwindigkeitsschub, indem Sie die Datenmenge begrenzen, die für Aggregationen oder andere Transformationen verarbeitet werden muss. Wenn Sie die Daten in mehrere Tabellen aufteilen, können Sie einen Teil der Aggregations- und anderen statistischen Analysen für die UDF-Daten durchführen und dieses Ergebnis über einen Fremdschlüssel mit der Mastertabelle verbinden, um die nicht aggregierten Attribute abzurufen.

  3. Sie können Tabellen-/Spaltennamen verwenden, die die tatsächlichen Daten widerspiegeln.

  4. Sie haben die vollständige Kontrolle über die Verwendung von Datentypen, das Überprüfen von Einschränkungen, Standardwerten usw. zum Definieren der Datendomänen. Unterschätzen Sie nicht die Leistungseinbußen, die sich aus der fliegenden Konvertierung von Datentypen ergeben. Mithilfe solcher Einschränkungen können RDBMS-Abfrageoptimierer auch effektivere Pläne entwickeln.

  5. Sollten Sie jemals Fremdschlüssel verwenden müssen, wird die integrierte deklarative referenzielle Integrität selten durch die Durchsetzung von Trigger- oder Anwendungsebenenbeschränkungen übertroffen.

Nachteile:

  1. Dadurch könnten viele Tabellen erstellt werden. Das Erzwingen einer Schematrennung und/oder einer Namenskonvention würde dies abmildern.

  2. Es ist mehr Anwendungscode erforderlich, um die UDF-Definition und -Verwaltung auszuführen. Ich gehe davon aus, dass immer noch weniger Code benötigt wird als für die ursprünglichen Optionen 1, 3 und 4.

Andere Überlegungen:

  1. Wenn es irgendetwas an der Art der Daten gibt, das für die Gruppierung der UDFs Sinn macht, sollte dies gefördert werden. Auf diese Weise können diese Datenelemente in einer einzigen Tabelle zusammengefasst werden. Angenommen, Sie haben UDFs für Farbe, Größe und Kosten. Die Tendenz in den Daten ist, dass die meisten Instanzen dieser Daten so aussehen

     'red', 'large', 45.03 
    

    eher, als

     NULL, 'medium', NULL
    

    In diesem Fall wird durch das Kombinieren der drei Spalten in einer Tabelle keine spürbare Geschwindigkeitsbeeinträchtigung verursacht, da nur wenige Werte NULL sind und Sie vermeiden, zwei weitere Tabellen zu erstellen. Dies sind zwei Verknüpfungen weniger, wenn Sie auf alle drei Spalten zugreifen müssen .

  2. Wenn Sie mit einer UDF, die stark gefüllt ist und häufig verwendet wird, auf eine Performance-Pinnwand stoßen, sollte dies für die Aufnahme in die Mastertabelle in Betracht gezogen werden.

  3. Das logische Tabellendesign kann Sie zu einem bestimmten Punkt führen. Wenn die Anzahl der Datensätze jedoch sehr hoch ist, sollten Sie sich auch überlegen, welche Optionen für die Tabellenpartitionierung von dem RDBMS Ihrer Wahl bereitgestellt werden.

47
Phil Helmer

Ich habe geschrieben über dieses Problem viel . Die gebräuchlichste Lösung ist das Entity-Attribute-Value-Antipattern, das dem in Option 3 beschriebenen ähnelt. Vermeiden Sie dieses Design wie die Pest .

Wenn ich wirklich dynamische benutzerdefinierte Felder benötige, verwende ich diese Lösung, um sie in einem XML-Blob zu speichern, sodass ich jederzeit neue Felder hinzufügen kann. Um die Suche zu beschleunigen, erstellen Sie auch zusätzliche Tabellen für jedes zu durchsuchende oder zu sortierende Feld (Sie haben keine Tabelle pro Feld - nur eine Tabelle pro durchsuchbar Feld). Dies wird manchmal als invertiertes Indexdesign bezeichnet.

Einen interessanten Artikel aus dem Jahr 2009 zu dieser Lösung finden Sie hier: http://backchannel.org/blog/friendfeed-schemaless-mysql

Sie können auch eine dokumentenorientierte Datenbank verwenden, in der benutzerdefinierte Felder pro Dokument erwartet werden. Ich würde wählen Solr .

22
Bill Karwin

Ich würde höchstwahrscheinlich eine Tabelle mit der folgenden Struktur erstellen:

  • varchar Name
  • varchar Typ
  • decimal NumberValue
  • varchar StringValue
  • date DateValue

Die genauen Kurstypen hängen von Ihren Anforderungen ab (und natürlich von den von Ihnen verwendeten Datenbanken). Sie können auch das NumberValue-Feld (Dezimal) für Int-Werte und Boolesche Werte verwenden. Möglicherweise benötigen Sie auch andere Typen.

Sie benötigen einen Link zu den Stammsätzen, die den Wert besitzen. Es ist wahrscheinlich am einfachsten und schnellsten, eine Benutzerfeldtabelle für jede Mastertabelle zu erstellen und einen einfachen Fremdschlüssel hinzuzufügen. Auf diese Weise können Sie Stammsätze einfach und schnell nach Benutzerfeldern filtern.

Möglicherweise möchten Sie eine Art Metadaten-Information haben. Am Ende haben Sie also Folgendes:

Tabelle UdfMetaData

  • int id
  • varchar Name
  • varchar Typ

Tabelle MasterUdfValues

  • int Master_FK
  • int MetaData_FK
  • decimal NumberValue
  • varchar StringValue
  • date DateValue

Was auch immer Sie tun, ich würde nicht die Tabellenstruktur dynamisch ändern. Es ist ein Alptraum für die Instandhaltung. Ich würde auch nicht XML-Strukturen verwenden, sie sind viel zu langsam.

9

Dies klingt nach einem Problem, das möglicherweise besser durch eine nicht relationale Lösung wie MongoDB oder CouchDB gelöst werden kann.

Beide ermöglichen eine dynamische Schemaerweiterung und gleichzeitig die Aufrechterhaltung der gewünschten Tupel-Integrität.

Ich stimme Bill Karwin zu, das EAV-Modell ist für Sie kein performanter Ansatz. Die Verwendung von Name-Wert-Paaren in einem relationalen System ist an sich nicht schlecht, funktioniert aber nur, wenn das Name-Wert-Paar einen vollständigen Tupel von Informationen erstellt. Wenn Sie eine Tabelle zur Laufzeit dynamisch rekonstruieren müssen, werden alle möglichen Dinge schwierig. Das Abfragen wird zu einer Übung bei der Pivot-Wartung oder zwingt Sie, die Tupel-Rekonstruktion in die Objektebene zu verschieben.

Sie können nicht bestimmen, ob ein Nullwert oder ein fehlender Wert ein gültiger Eintrag oder ein fehlender Eintrag ist, ohne Schemaregeln in Ihre Objektebene einzubetten.

Sie verlieren die Fähigkeit, Ihr Schema effizient zu verwalten. Ist ein 100-stelliger varchar der richtige Typ für das Feld "value"? 200 Zeichen? Sollte es stattdessen nvarchar sein? Es kann ein schwieriger Kompromiss sein, der damit endet, dass Sie der Dynamik Ihres Sets künstliche Grenzen setzen müssen. So etwas wie "Sie können nur x benutzerdefinierte Felder haben und jedes darf nur y Zeichen lang sein.

Mit einer dokumentenorientierten Lösung wie MongoDB oder CouchDB verwalten Sie alle einem Benutzer zugeordneten Attribute in einem einzigen Tupel. Da Joins kein Thema sind, ist das Leben glücklich, da keiner dieser beiden trotz des Hype gut mit Joins zurechtkommt. Ihre Benutzer können so viele Attribute definieren, wie sie möchten (oder zulassen), und das bei einer Länge, die nicht schwer zu verwalten ist, bis Sie ungefähr 4 MB erreichen.

Wenn Sie Daten haben, für die eine Integrität auf ACID-Ebene erforderlich ist, können Sie die Lösung aufteilen, wobei die Daten mit hoher Integrität in Ihrer relationalen Datenbank und die dynamischen Daten in einem nicht relationalen Speicher vorhanden sind.

8
Data Monk

Selbst wenn Sie einen Benutzer angeben, der benutzerdefinierte Spalten hinzufügt, ist die Abfrage dieser Spalten nicht unbedingt zufriedenstellend. Es gibt viele Aspekte, die bei der Abfrageerstellung eine gute Leistung ermöglichen. Das wichtigste davon ist die korrekte Angabe, was überhaupt gespeichert werden soll. Im Grunde genommen möchten Sie also Benutzern erlauben, ein Schema ohne Berücksichtigung von Spezifikationen zu erstellen und Informationen aus diesem Schema schnell abzuleiten? Wenn dies der Fall ist, ist es unwahrscheinlich, dass sich eine solche Lösung gut skalieren lässt, insbesondere wenn Sie dem Benutzer die Möglichkeit geben möchten, numerische Analysen der Daten durchzuführen.

Option 1

IMO gibt Ihnen dieser Ansatz ein Schema ohne Wissen darüber, was das Schema bedeutet. Es ist ein Rezept für eine Katastrophe und ein Albtraum für Berichtsdesigner. Das heißt, Sie müssen über die Metadaten verfügen, um zu wissen, in welcher Spalte welche Daten gespeichert sind. Wenn diese Metadaten durcheinander geraten, besteht die Gefahr, dass Ihre Daten überlastet werden. Außerdem ist es so einfach, die falschen Daten in die falsche Spalte zu setzen. ("Was? String1 enthält den Namen von Klöstern? Ich dachte, es wäre Chalie Sheens Lieblingsdroge.")

Option 3,4,5

IMO, Anforderungen 2, 3 und 4 eliminieren alle Variationen eines EAV. Wenn Sie diese Daten abfragen, sortieren oder berechnen müssen, ist ein EAV der Traum von Cthulhu und der Albtraum Ihres Entwicklungsteams und DBAs. EAVs verursachen einen Leistungsengpass und geben Ihnen nicht die Datenintegrität, die Sie benötigen, um schnell zu den gewünschten Informationen zu gelangen. Abfragen werden sich schnell in gordische Kreuztabellenknoten verwandeln.

Option 2,6

Das lässt wirklich eine Wahl: Sammeln Sie Spezifikationen und bauen Sie dann das Schema auf.

Wenn der Kunde die bestmögliche Leistung für die Daten erzielen möchte, die er speichern möchte, muss er mit einem Entwickler zusammenarbeiten, um seine Anforderungen zu verstehen und diese so effizient wie möglich zu speichern. Es könnte immer noch in einer Tabelle gespeichert werden, die vom Rest der Tabellen getrennt ist und Code enthält, mit dem dynamisch ein Formular erstellt wird, das auf dem Schema der Tabelle basiert. Wenn Sie über eine Datenbank verfügen, die erweiterte Eigenschaften für Spalten zulässt, können Sie diese sogar verwenden, um dem Formular-Generator bei der Verwendung von Nice-Beschriftungen, Tooltips usw. zu helfen, sodass nur das Schema hinzugefügt werden muss. In beiden Fällen müssen die Daten ordnungsgemäß gespeichert werden, damit Berichte effizient erstellt und ausgeführt werden können. Wenn die fraglichen Daten viele Nullen enthalten, können einige Datenbanken diese Art von Informationen speichern. Beispielsweise verfügt SQL Server 2008 über eine Funktion namens Sparse Columns, die speziell für Daten mit vielen Nullen vorgesehen ist.

Wenn dies nur eine Datenmenge wäre, für die keine Analyse, Filterung oder Sortierung durchgeführt werden sollte, würde ich sagen, dass eine Variation eines EAV den Trick tun könnte. In Anbetracht Ihrer Anforderungen besteht die effizienteste Lösung darin, die richtigen Spezifikationen zu erhalten, selbst wenn Sie diese neuen Spalten in separaten Tabellen speichern und Formulare dynamisch aus diesen Tabellen erstellen.

spärliche Spalten

6
Thomas

Dies ist eine problematische Situation, und keine der Lösungen erscheint "richtig". Option 1 ist jedoch wahrscheinlich sowohl in Bezug auf die Einfachheit als auch in Bezug auf die Leistung die beste.

Dies ist auch die Lösung, die in einigen kommerziellen Unternehmensanwendungen verwendet wird.

[~ # ~] edit [~ # ~]

eine andere Option, die jetzt verfügbar ist, aber bei der ursprünglichen Frage nicht existierte (oder zumindest nicht ausgereift war), ist die Verwendung von JSON-Feldern in der Datenbank.

viele relationale DBs unterstützen jetzt json-basierte Felder (die eine dynamische Liste von Unterfeldern enthalten können) und ermöglichen die Abfrage dieser Felder

postgress

mysql

4
Ophir Yoktan
  1. Erstellen Sie mehrere UDF-Tabellen, eine pro Datentyp. Wir würden also Tabellen für UDFStrings, UDFDates usw. haben. Würde wahrscheinlich das Gleiche tun wie Nummer 2 und eine Ansicht automatisch generieren, sobald ein neues Feld hinzugefügt wird

Nach meiner Recherche werden mehrere Tabellen, die auf dem Datentyp basieren, Ihnen bei der Leistung nicht helfen. Vor allem, wenn Sie Massendaten haben, z. B. 20.000 oder 25.000 Datensätze mit mehr als 50 UDFs. Leistung war das schlechteste.

Sie sollten mit einer einzelnen Tabelle mit mehreren Spalten gehen wie:

varchar Name
varchar Type
decimal NumberValue
varchar StringValue
date DateValue
4
Amit Contractor

Ich habe Erfahrung mit 1, 3 und 4, und alle sind entweder unübersichtlich, da nicht klar ist, was die Daten sind, oder es ist wirklich kompliziert, die Daten in dynamische Datensatztypen zu unterteilen.

Ich wäre versucht, XML auszuprobieren. Sie sollten in der Lage sein, Schemas für den Inhalt der XML-Datei zu erzwingen, um die Dateneingabe usw. zu überprüfen, was das Speichern unterschiedlicher Sätze von UDF-Daten erleichtert. In neueren Versionen von SQL Server können Sie XML-Felder indizieren, was sich auf die Leistung auswirken sollte. (Siehe http://blogs.technet.com/b/josebda/archive/2009/03/23/sql-server-2008-xml-indexing.aspx ) zum Beispiel

2
Jon Egerton

Wenn Sie SQL Server verwenden, übersehen Sie nicht den Typ sqlvariant. Es ist ziemlich schnell und sollte deinen Job machen. Andere Datenbanken haben möglicherweise etwas Ähnliches.

XML-Datentypen sind aus Leistungsgründen nicht so gut. Wenn Sie Berechnungen auf dem Server durchführen, müssen Sie diese ständig deserialisieren.

Option 1 klingt schlecht und sieht grob aus, aber in Bezug auf die Leistung kann dies die beste Wahl sein. Ich habe zuvor Tabellen mit Spalten mit dem Namen Field00-Field99 erstellt, weil Sie die Leistung einfach nicht übertreffen können. Möglicherweise müssen Sie auch Ihre INSERT-Leistung berücksichtigen. In diesem Fall ist dies auch die richtige Wahl. Sie können jederzeit Ansichten für diese Tabelle erstellen, wenn Sie möchten, dass sie ordentlich aussieht!

2
Tim Rogers

SharePoint verwendet Option 1 und bietet eine angemessene Leistung.

1
Nathan DeWitt

Ich habe dies in der Vergangenheit sehr erfolgreich mit keiner dieser Optionen geschafft (Option 6? :)).

Ich erstelle ein Modell, mit dem die Benutzer spielen können (als XML speichern und über ein benutzerdefiniertes Modellierungswerkzeug verfügbar machen) und aus dem Modell generierte Tabellen und Ansichten, um die Basistabellen mit den benutzerdefinierten Datentabellen zu verbinden. Jeder Typ hätte also eine Basistabelle mit Kerndaten und eine Benutzertabelle mit benutzerdefinierten Feldern.

Nehmen Sie ein Dokument als Beispiel: Typische Felder wären Name, Typ, Datum, Autor usw. Dies würde in der Kerntabelle stehen. Dann würden Benutzer ihre eigenen speziellen Dokumenttypen mit ihren eigenen Feldern definieren, z. B. contract_end_date, renewal_clause, bla bla bla. Für dieses benutzerdefinierte Dokument gibt es die Kerndokumenttabelle, die xcontract-Tabelle, die auf einem gemeinsamen Primärschlüssel verknüpft ist (der xcontracts-Primärschlüssel ist also auch auf dem Primärschlüssel der Kerntabelle fremd). Dann würde ich eine Ansicht erzeugen, um diese beiden Tabellen zu umbrechen. Die Leistung beim Abfragen war schnell. Zusätzliche Geschäftsregeln können ebenfalls in die Ansichten eingebettet werden. Das hat bei mir sehr gut funktioniert.

1
Kell

Ich würde # 4 empfehlen, da diese Art von System in Magento verwendet wurde, einer hoch akkreditierten E-Commerce-CMS-Plattform. Verwenden Sie eine einzelne Tabelle, um Ihre benutzerdefinierten Felder mithilfe der Spalten fieldId & label zu definieren. Verfügen Sie dann über separate Tabellen für jeden Datentyp und haben Sie in jeder dieser Tabellen einen Index, der durch fieldId und den Datentyp value Spalten indiziert. Verwenden Sie dann in Ihren Abfragen Folgendes:

SELECT *
FROM FieldValues_Text
WHERE fieldId IN (
    SELECT fieldId FROM Fields WHERE [email protected]
)
AND value LIKE '%' + @search + '%'

Dies wird meiner Meinung nach die bestmögliche Leistung für benutzerdefinierte Typen gewährleisten.

Meiner Erfahrung nach habe ich an mehreren Magento-Websites gearbeitet, die Millionen von Benutzern pro Monat bedienen, Tausende von Produkten mit benutzerdefinierten Produktattributen hosten und die Datenbank die Arbeitslast auch für die Berichterstellung problemlos handhaben.

Für die Berichterstellung können Sie PIVOT verwenden, um Ihre Felder Tabelle Beschriftung Werte in Spaltennamen zu konvertieren. Anschließend können Sie Ihre Abfrageergebnisse aus jeder Datentyp-Tabelle in die Pivot-Werte umwandeln Säulen.

0
Mark Entingh

In den Kommentaren habe ich gesehen, dass Sie gesagt haben, dass die UDF-Felder importierte Daten sichern sollen, die vom Benutzer nicht richtig zugeordnet wurden.

Möglicherweise besteht eine andere Option darin, die Anzahl der von jedem Benutzer erstellten UDFs zu verfolgen und sie zur Wiederverwendung von Feldern zu zwingen, indem sie angeben, dass sie 6 benutzerdefinierte Felder (oder ein anderes gleichermaßen zufälliges Limit) verwenden können.

Wenn Sie mit einem solchen Problem der Datenbankstrukturierung konfrontiert sind, ist es häufig am besten, zum grundlegenden Design der Anwendung (in Ihrem Fall zum Importsystem) zurückzukehren und einige weitere Einschränkungen vorzunehmen.

Was ich jetzt tun würde, ist Option 4 (BEARBEITEN) mit der Hinzufügung eines Links zu Benutzern:

general_data_table
id
...


udfs_linked_table
id
general_data_id
udf_id


udfs_table
id
name
type
owner_id --> Use this to filter for the current user and limit their UDFs
string_link_id --> link table for string fields
int_link_id
type_link_id

Stellen Sie jetzt sicher, dass Sie Ansichten erstellen, um die Leistung zu optimieren und Ihre Indizes richtig zu machen. Durch diesen Normalisierungsgrad wird der DB-Footprint kleiner, Ihre Anwendung jedoch komplexer.

0
Wouter Simons

Unsere Datenbank unterstützt eine SaaS App (Helpdesk-Software), in der Benutzer über 7.000 "benutzerdefinierte Felder" verfügen. Wir verwenden einen kombinierten Ansatz:

  1. (EntityID, FieldID, Value) Tabelle für Suche die Daten
  2. ein JSON-Feld in der Tabelle entities, das alle Entitätswerte enthält und für Anzeigen die Daten verwendet wird. (Auf diese Weise benötigen Sie keine Million JOINs, um die Werte values ​​zu erhalten).

Sie könnten # 1 weiter aufteilen, um eine "Tabelle pro Datentyp" zu erhalten, wie diese Antwort nahelegt, auf diese Weise können Sie sogar Ihre UDFs indizieren.

P.S. Ein paar Worte, um den "Entity-Attribute-Value" -Ansatz zu verteidigen. Wir haben jahrzehntelang Nr. 1 ohne Nr. 2 verwendet und es hat einwandfrei funktioniert. Manchmal ist es eine Geschäftsentscheidung. Haben Sie Zeit, Ihre App neu zu schreiben und die Datenbank neu zu gestalten, oder können Sie ein paar Dollar auf einem Cloud-Server ausgeben, die heutzutage wirklich billig sind? Übrigens, als wir den ersten Ansatz verwendeten, verfügte unsere Datenbank über Millionen von Entitäten, auf die Hunderttausende von Benutzern zugegriffen haben, und ein 16-GB-Dual-Core-Datenbankserver lief einwandfrei (wirklich ein "r3" -VM unter AWS). .

0
Alex