Ist das Speichern einer begrenzten Liste in einer Datenbankspalte wirklich so schlecht?


363

Stellen Sie sich ein Webformular mit einer Reihe von Kontrollkästchen vor (einige oder alle können ausgewählt werden). Ich habe sie in einer durch Kommas getrennten Liste von Werten gespeichert, die in einer Spalte der Datenbanktabelle gespeichert sind.

Jetzt weiß ich, dass die richtige Lösung darin besteht, eine zweite Tabelle zu erstellen und die Datenbank ordnungsgemäß zu normalisieren. Die einfache Lösung war schneller zu implementieren, und ich wollte schnell und ohne zu viel Zeit damit einen Proof-of-Concept für diese Anwendung haben.

Ich dachte, die eingesparte Zeit und der einfachere Code haben sich in meiner Situation gelohnt. Ist dies eine vertretbare Designentscheidung, oder hätte ich sie von Anfang an normalisieren sollen?

Etwas mehr Kontext, dies ist eine kleine interne Anwendung, die im Wesentlichen eine Excel-Datei ersetzt, die in einem freigegebenen Ordner gespeichert wurde. Ich frage auch, weil ich darüber nachdenke, das Programm zu bereinigen und es wartbarer zu machen. Es gibt einige Dinge, mit denen ich nicht ganz zufrieden bin. Eines davon ist das Thema dieser Frage.


21
In diesem Fall, warum die Datenbank stören?, reicht das Speichern in einer Datei.
Thavan

6
Einverstanden mit @thavan. Warum die Daten überhaupt für einen Proof of Concept speichern? Wenn Sie den Proof abgeschlossen haben, fügen Sie eine Datenbank korrekt hinzu. Ihr feines Tun ist leicht für Proof of Concept. Machen Sie einfach keine Dinge, die Sie später aufheben müssen.
Jeff Davis

1
In Postgres sollte eine Array-Spalte einer durch Kommas getrennten Liste vorgezogen werden. Dies stellt zumindest den richtigen Datentyp sicher, hat keine Probleme bei der Unterscheidung des Trennzeichens von den tatsächlichen Daten und kann effizient indiziert werden.
a_horse_with_no_name

Antworten:


568

Kommagetrennte Listen verletzen nicht nur die erste Normalform aufgrund der sich wiederholenden Gruppe von Werten, die in einer einzelnen Spalte gespeichert sind, sondern haben auch viele andere praktischere Probleme:

  • Ich kann nicht sicherstellen, dass jeder Wert der richtige Datentyp ist: Keine Möglichkeit, 1,2,3, Banane, 5 zu verhindern
  • Fremdschlüsseleinschränkungen können nicht zum Verknüpfen von Werten mit einer Nachschlagetabelle verwendet werden. Keine Möglichkeit, die referenzielle Integrität durchzusetzen.
  • Einzigartigkeit kann nicht erzwungen werden : Keine Möglichkeit, 1,2,3,3,3,5 zu verhindern
  • Ein Wert kann nicht aus der Liste gelöscht werden, ohne die gesamte Liste abzurufen.
  • Eine Liste kann nicht länger gespeichert werden, als in die Zeichenfolgenspalte passt.
  • Es ist schwierig, nach allen Entitäten mit einem bestimmten Wert in der Liste zu suchen. Sie müssen einen ineffizienten Tabellenscan verwenden. Möglicherweise müssen reguläre Ausdrücke verwendet werden, z. B. in MySQL:
    idlist REGEXP '[[:<:]]2[[:>:]]'*
  • Schwer zu zählende Elemente in der Liste oder andere aggregierte Abfragen.
  • Es ist schwierig, die Werte mit der Nachschlagetabelle zu verknüpfen, auf die sie verweisen.
  • Es ist schwierig, die Liste in sortierter Reihenfolge abzurufen.

Um diese Probleme zu lösen, müssen Sie Tonnen von Anwendungscode schreiben und die Funktionen neu erfinden, die das RDBMS bereits wesentlich effizienter bietet .

Durch Kommas getrennte Listen sind so falsch, dass ich dies zum ersten Kapitel in meinem Buch gemacht habe: SQL Antipatterns: Vermeiden der Fallstricke der Datenbankprogrammierung .

Es gibt Zeiten, in denen Sie eine Denormalisierung anwenden müssen, aber wie @OMG Ponies erwähnt , sind dies Ausnahmefälle. Jede nicht relationale „Optimierung“ kommt einer Art von Abfrage auf Kosten anderer Verwendungszwecke der Daten zugute. Stellen Sie daher sicher, dass Sie wissen, welche Ihrer Abfragen so speziell behandelt werden müssen, dass sie eine Denormalisierung verdienen.


* MySQL 8.0 unterstützt diese Wortgrenzen-Ausdruckssyntax nicht mehr.


8
Ein ARRAY (eines beliebigen Datentyps) kann die Ausnahme beheben. Überprüfen Sie einfach PostgreSQL: postgresql.org/docs/current/static/arrays.html (@Bill: Tolles Buch, ein Muss für jeden Entwickler oder dba)
Frank Heikens

4
+1 Rechnung Karwin Tolle Antwort! Schöne prägnante Aufzählungszeichen. Das sieht auch nach einem großartigen Buch aus. Ich liebe das Cover auch +1 NullUserException. Ich bin dabei, das Schema für eine MySQL-Datenbank zu entwerfen, um ein auf Flatfiles basierendes Textsystem zu ersetzen. Ich bin bisher auf mehrere Dilemmata gestoßen. Es lohnt sich also, dieses Buch zu kaufen.
Therobyouknow

2
Die pragprog.com-Website sieht auch gut aus: schöner Stil, Layout, benutzerfreundliche Reinigung. Das muss ziemlich neu sein, ich konnte ihre E-Books in der Vergangenheit nicht kaufen. PS. Ich arbeite nicht für sie, habe irgendeine Verbindung zu den Autoren. Ich mag es, gute Produkte, Dienstleistungen und Hilfe zu feiern, wenn ich sie sehe.
Therobyouknow

2
Im Ernst, ich würde Ihrer Liste hinzufügen: Schwer zu suchen. Angenommen, Sie möchten alle Datensätze, die "2" enthalten. Natürlich können Sie nicht einfach nach foobar = '2' suchen, da dies bei anderen Werten fehlschlagen würde. Sie können nicht nach "% 2%" suchen, da dies zu falschen Treffern für 12 und 28 usw. führen würde. Sie können nicht nach '%, 2,%' suchen, da 2 möglicherweise das erste oder letzte Element der Liste ist und daher nur eines dieser Kommas enthält.
Jay

2
Ich weiß, dass es nicht empfohlen wird, aber das Spielen von Devils Advocate: Die meisten davon können entfernt werden, wenn es eine Benutzeroberfläche gibt, die mit Eindeutigkeit und Datentypen umgeht (andernfalls würde es zu Fehlern oder Fehlverhalten kommen). Die Benutzeroberfläche wird gelöscht und trotzdem erstellt. Es gibt eine Treiber-Tabelle, in der es eine Treiber-Tabelle gibt Die Werte stammen von, um sie eindeutig zu machen. Felder wie '% P%' können verwendet werden. Die Werte sind P, R, S, T, das Zählen spielt keine Rolle und das Sortieren spielt keine Rolle. Abhängig von der Benutzeroberfläche können Werte aufgeteilt werden [], z. B. um Kontrollkästchen in einer Liste aus der Treibertabelle im am wenigsten verbreiteten Szenario zu aktivieren, ohne zu einer anderen Tabelle wechseln zu müssen, um sie abzurufen.
jmcclure

44

"Ein Grund war Faulheit".

Dies läutet Alarmglocken. Der einzige Grund, warum Sie so etwas tun sollten, ist, dass Sie wissen, wie man es "richtig" macht, aber Sie sind zu dem Schluss gekommen, dass es einen konkreten Grund gibt, es nicht so zu machen.

Allerdings: Wenn es sich bei den Daten, die Sie auf diese Weise speichern möchten, um Daten handelt, nach denen Sie niemals abfragen müssen, kann es sinnvoll sein, sie so zu speichern, wie Sie es ausgewählt haben.

(Einige Benutzer bestreiten die Aussage in meinem vorherigen Absatz und sagen, dass "Sie nie wissen können, welche Anforderungen in Zukunft hinzugefügt werden". Diese Benutzer sind entweder irregeführt oder geben eine religiöse Überzeugung an. Manchmal ist es vorteilhaft, an den Anforderungen zu arbeiten, die Sie haben vor dir haben.)


Ich höre immer einige Leute sagen, dass "mein Design flexibler ist als deins", wenn ich sie mit Dingen wie dem Nichteinrichten von Fremdschlüsseleinschränkungen oder dem Speichern von Listen in einem einzelnen Feld konfrontiere. Für mich Flexibilität (in solchen Fällen) == keine Disziplin == Faulheit.
Foresightyj

41

Es gibt zahlreiche Fragen zu SO:

  • So erhalten Sie eine Anzahl bestimmter Werte aus der durch Kommas getrennten Liste
  • wie man Datensätze, die nur den gleichen 2/3 / etc spezifischen Wert haben, aus dieser durch Kommas getrennten Liste erhält

Ein weiteres Problem mit der durch Kommas getrennten Liste besteht darin, sicherzustellen, dass die Werte konsistent sind. Das Speichern von Text bedeutet die Möglichkeit von Tippfehlern ...

Dies sind alles Symptome von denormalisierten Daten und zeigen, warum Sie immer für normalisierte Daten modellieren sollten. Denormalisierung kann eine Abfrageoptimierung sein, die angewendet wird, wenn sich der Bedarf tatsächlich ergibt .


19

Im Allgemeinen kann alles verteidigt werden, wenn es den Anforderungen Ihres Projekts entspricht. Dies bedeutet nicht, dass die Leute Ihrer Entscheidung zustimmen oder sie verteidigen wollen ...

Im Allgemeinen ist das Speichern von Daten auf diese Weise nicht optimal (z. B. schwieriger, effiziente Abfragen durchzuführen) und kann zu Wartungsproblemen führen, wenn Sie die Elemente in Ihrem Formular ändern. Vielleicht hätten Sie einen Mittelweg finden und stattdessen eine Ganzzahl verwenden können, die eine Reihe von Bit-Flags darstellt?


10

Ja, ich würde sagen, dass es wirklich so schlimm ist. Es ist eine vertretbare Wahl, aber das macht es nicht richtig oder gut.

Es bricht die erste Normalform.

Ein zweiter Kritikpunkt ist, dass das direkte Einfügen von Roheingabeergebnissen in eine Datenbank ohne Validierung oder Bindung Sie für SQL-Injection-Angriffe offen lässt.

Was Sie Faulheit und mangelndes SQL-Wissen nennen, ist das Zeug, aus dem Neophyten bestehen. Ich würde empfehlen, sich die Zeit zu nehmen, um es richtig zu machen und es als Gelegenheit zum Lernen zu betrachten.

Oder lassen Sie es wie es ist und lernen Sie die schmerzhafte Lektion eines SQL-Injection-Angriffs.


19
Ich sehe in dieser Frage nichts, was darauf hindeutet, dass er für SQL-Injection anfällig ist. SQL-Injection und Datenbanknormalisierung sind orthogonale Themen, und Ihr Exkurs zur Injection ist für die Frage irrelevant.
Hammerite

5
@Paul: Und vielleicht führt die gleiche Einstellung dazu, dass er von einem Bus angefahren wird, wenn er vor dem Überqueren der Straße nicht in beide Richtungen schaut, aber Sie haben ihn nicht davor gewarnt. Edit: Ich hatte gedacht, du wärst das Plakat dieser Antwort, mein Fehler.
Hammerite

1
@ Hammerite - Ihre Extrapolation auf Busse ist lächerlich.
Duffymo

4
Ja, es sollte lächerlich sein. Seine Lächerlichkeit verdeutlicht den Punkt, den ich mache, nämlich dass es keinen Sinn macht, ihn vor etwas zu warnen, vor dem man keinen Grund zu der Annahme hat, dass er gewarnt werden muss.
Hammerite

1
Ja ich sehe. Ich glaube, ich hatte weit mehr Grund als Ihre Warnung vor Bussen.
Duffymo

7

Nun, ich verwende seit mehr als 4 Jahren eine durch Tabulatoren getrennte Liste von Schlüssel / Wert-Paaren in einer NTEXT-Spalte in SQL Server und sie funktioniert. Sie verlieren zwar die Flexibilität, Abfragen zu stellen, aber wenn Sie andererseits eine Bibliothek haben, die das Schlüsselwertpaar beibehält / ableitet, ist dies keine so schlechte Idee.


13
Nein, das ist eine schreckliche Idee. Sie haben es geschafft, damit durchzukommen, aber die Kosten für Ihre wenigen Minuten Entwicklungszeit haben Sie miese Abfrageleistung, Flexibilität und Wartbarkeit Ihres Codes gekostet.
Paul Tomblin

5
Paul, ich stimme zu. Aber wie gesagt, ich habe es für einen bestimmten Zweck verwendet, und das ist für eine Dateneingabeoperation, bei der Sie viele Arten von Formularen haben. Ich überarbeite das Design jetzt, da ich NHibernate gelernt habe, aber damals brauchte ich die Flexibilität, um das Formular in ASP.NET zu entwerfen und die Textfeld-IDs als Schlüssel im Schlüssel / Wert-Paar zu verwenden.
Raj

28
+1 nur um den Abstimmungen entgegenzuwirken. Es ist etwas anmaßend, jemandem, der die App 4 Jahre lang gewartet hat, über Wartungsprobleme zu informieren. Es gibt nur sehr wenige "schreckliche" Ideen in der SW-Entwicklung - meistens handelt es sich nur um Ideen mit sehr begrenzter Anwendbarkeit. Es ist vernünftig, die Leute vor den Einschränkungen zu warnen, aber diejenigen zu bestrafen, die es getan und durchlebt haben, scheint mir eine Haltung zu sein, auf die ich verzichten kann.
Mark Brackett

7

Ich brauchte eine mehrwertige Spalte, die als XML-Feld implementiert werden konnte

Es kann bei Bedarf in ein Komma umgewandelt werden

Abfragen einer XML-Liste in SQL Server mit Xquery .

Als XML-Feld können einige der Probleme behoben werden.

Mit CSV: kann nicht sichergestellt werden, dass jeder Wert der richtige Datentyp ist: Keine Möglichkeit, 1,2,3, Banane, 5 zu verhindern

Mit XML: Werte in einem Tag können gezwungen werden, den richtigen Typ zu haben


Mit CSV: können keine Fremdschlüsseleinschränkungen verwendet werden, um Werte mit einer Nachschlagetabelle zu verknüpfen. Keine Möglichkeit, die referenzielle Integrität durchzusetzen.

Mit XML: immer noch ein Problem


Mit CSV: Eindeutigkeit kann nicht erzwungen werden: Keine Möglichkeit, 1,2,3,3,3,5 zu verhindern

Mit XML: immer noch ein Problem


Mit CSV: Ein Wert kann nicht aus der Liste gelöscht werden, ohne die gesamte Liste abzurufen.

Mit XML: Einzelne Elemente können entfernt werden


Mit CSV: schwierig, nach allen Entitäten mit einem bestimmten Wert in der Liste zu suchen. Sie müssen einen ineffizienten Tabellenscan verwenden.

Mit XML: XML-Feld kann indiziert werden


Mit CSV: Schwer zu zählende Elemente in der Liste oder andere aggregierte Abfragen. **

Mit XML: nicht besonders schwer


Mit CSV: Es ist schwierig, die Werte mit der Nachschlagetabelle zu verknüpfen, auf die sie verweisen. **

Mit XML: nicht besonders schwer


Mit CSV: Es ist schwierig, die Liste in sortierter Reihenfolge abzurufen.

Mit XML: nicht besonders schwer


Mit CSV: Das Speichern von Ganzzahlen als Zeichenfolgen benötigt etwa doppelt so viel Speicherplatz wie das Speichern von binären Ganzzahlen.

Mit XML: Speicher ist noch schlechter als ein CSV


Mit CSV: Plus viele Kommazeichen.

Bei XML werden Tags anstelle von Kommas verwendet


Kurz gesagt, die Verwendung von XML umgeht einige der Probleme mit der begrenzten Liste UND kann bei Bedarf in eine begrenzte Liste konvertiert werden


6

Ja, es ist so schlimm. Meiner Ansicht nach gibt es viele interessante "NOSQL" -Projekte mit einigen wirklich erweiterten Funktionen, wenn Sie keine relationalen Datenbanken verwenden möchten und nach einer Alternative suchen, die besser zu Ihnen passt.


0

Ich würde wahrscheinlich den Mittelweg einschlagen: Machen Sie jedes Feld in der CSV zu einer separaten Spalte in der Datenbank, aber machen Sie sich (zumindest vorerst) keine großen Sorgen um die Normalisierung. Irgendwann wird die Normalisierung vielleicht interessant, aber wenn alle Daten in einer einzigen Spalte gespeichert sind, profitieren Sie praktisch nicht mehr von der Verwendung einer Datenbank. Sie müssen die Daten in logische Felder / Spalten / wie auch immer Sie sie aufrufen möchten, trennen, bevor Sie sie überhaupt sinnvoll bearbeiten können.


Das Formular enthält einige weitere Felder, dies ist nur ein Teil des Formulars (was ich in der Frage nicht gut erklärt habe).
Mad Scientist

0

Wenn Sie eine feste Anzahl von Booleschen Feldern haben, können Sie für jedes ein INT(1) NOT NULL(oder BIT NOT NULLfalls vorhanden) oder CHAR (0)(nullbar) verwenden. Sie können auch a verwenden SET(ich vergesse die genaue Syntax).


1
INT(1)dauert 4 Bytes; das (1)ist bedeutungslos.
Rick James
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.