Le stockage d'une liste délimitée dans une colonne de base de données est-il vraiment si mauvais?

363

Imaginez un formulaire Web avec un ensemble de cases à cocher (tout ou partie d'entre eux peuvent être sélectionnés). J'ai choisi de les enregistrer dans une liste de valeurs séparées par des virgules stockées dans une colonne de la table de base de données.

Maintenant, je sais que la bonne solution serait de créer une deuxième table et de normaliser correctement la base de données. La mise en œuvre de la solution simple a été plus rapide et je voulais avoir une preuve de concept de cette application rapidement et sans avoir à y consacrer trop de temps.

Je pensais que le temps gagné et le code plus simple en valaient la peine dans ma situation, est-ce un choix de conception défendable, ou aurais-je dû le normaliser dès le départ?

Un peu plus de contexte, il s'agit d'une petite application interne qui remplace essentiellement un fichier Excel qui a été stocké sur un dossier partagé. Je demande aussi parce que je pense à nettoyer le programme et à le rendre plus maintenable. Il y a certaines choses là-dedans dont je ne suis pas entièrement satisfait, l'une d'elles est le sujet de cette question.

Scientifique fou
la source
21
dans ce cas, pourquoi déranger la base de données?, l'enregistrement dans un fichier fera l'affaire.
thavan le
6
D'accord avec @thavan. Pourquoi même sauvegarder les données pour une preuve de concept? Une fois la preuve terminée, ajoutez correctement une base de données. Votre fine légèreté pour la preuve de concept, ne faites tout simplement pas des choses que vous devrez défaire plus tard.
Jeff Davis
1
Dans Postgres, une colonne de tableau doit être préférée à une liste séparée par des virgules. Cela garantit au moins le type de données approprié, n'a aucun problème à distinguer le délimiteur des données réelles et il peut être indexé efficacement.
a_horse_with_no_name

Réponses:

568

En plus de violer la première forme normale en raison du groupe répétitif de valeurs stockées dans une seule colonne, les listes séparées par des virgules ont beaucoup d'autres problèmes plus pratiques:

  • Je ne peux pas m'assurer que chaque valeur est le bon type de données: aucun moyen d'empêcher 1,2,3, banane, 5
  • Impossible d'utiliser des contraintes de clé étrangère pour lier des valeurs à une table de recherche; aucun moyen de faire respecter l'intégrité référentielle.
  • Impossible d'imposer l'unicité: aucun moyen d'empêcher 1,2,3,3,3,5
  • Impossible de supprimer une valeur de la liste sans récupérer la liste entière.
  • Impossible de stocker une liste plus longtemps que ce qui tient dans la colonne chaîne.
  • Difficile de rechercher toutes les entités avec une valeur donnée dans la liste; vous devez utiliser un scan de table inefficace. Peut avoir à recourir à des expressions régulières, par exemple dans MySQL:
    idlist REGEXP '[[:<:]]2[[:>:]]'*
  • Difficile de compter les éléments de la liste ou d'effectuer d'autres requêtes agrégées.
  • Difficile de joindre les valeurs à la table de recherche à laquelle elles font référence.
  • Difficile de récupérer la liste dans l'ordre trié.

Pour résoudre ces problèmes, vous devez écrire des tonnes de code d'application, réinventant des fonctionnalités que le SGBDR fournit déjà beaucoup plus efficacement .

Les listes séparées par des virgules sont suffisamment erronées pour que j'en fasse le premier chapitre de mon livre: Antipatterns SQL: éviter les pièges de la programmation de base de données .

Il y a des moments où vous devez utiliser la dénormalisation, mais comme le mentionne @OMG Ponies , ce sont des cas d'exception. Toute «optimisation» non relationnelle profite à un type de requête au détriment d'autres utilisations des données, alors assurez-vous de savoir lesquelles de vos requêtes doivent être traitées de manière si spéciale qu'elles méritent une dénormalisation.


* MySQL 8.0 ne prend plus en charge cette syntaxe d'expression de limite de mot.

Bill Karwin
la source
8
Un tableau (de n'importe quel type de données) peut corriger l'exception, il suffit de vérifier PostgreSQL: postgresql.org/docs/current/static/arrays.html (@Bill: Great book, a must read for any developer or dba)
Frank Heikens
4
+1 facture Karwin Excellente réponse! Belles balles concises. Cela ressemble aussi à un grand livre. J'adore aussi la couverture +1 NullUserException. Je suis en train de concevoir le schéma d'une base de données MySQL pour remplacer un système basé sur du texte à fichier plat. J'ai rencontré plusieurs dilemmes jusqu'à présent. Donc, ce livre vaudra la peine d'être acheté.
therobyouknow
2
Le site pragprog.com a également fière allure: style agréable, mise en page, nettoyage facile à utiliser. Cela doit être assez récent, je n'ai pas pu acheter leurs ebooks par le passé. PS. Je ne travaille pas pour eux ont aucun lien avec les auteurs. J'aime célébrer les bons produits, services et aide quand je le vois.
therobyouknow
2
Du côté sérieux, j'ajouterais à votre liste: Difficile à rechercher. Supposons que vous souhaitiez tous les enregistrements contenant "2". Bien sûr, vous ne pouvez pas simplement rechercher foobar = '2' car cela le manquerait s'il y avait d'autres valeurs. Vous ne pouvez pas rechercher foobar comme '% 2%' car cela obtiendrait de faux résultats pour 12 et 28 et ainsi de suite. Vous ne pouvez pas rechercher foobar comme '%, 2,%' car 2 peut être le premier ou le dernier élément de la liste et donc ne comporter qu'une de ces virgules.
Jay
2
Je sais que ce n'est pas recommandé, mais jouer à l'avocat des démons: la plupart de ceux-ci peuvent être supprimés s'il existe une interface utilisateur qui gère l'unicité et les types de données (sinon cela entraînerait une erreur ou un mauvais comportement), l'interface utilisateur la crée et la crée de toute façon, il y a une table de pilotes où les valeurs viennent pour les rendre uniques, des champs comme '% P%' peuvent être utilisés, les valeurs étant P, R, S, T, le comptage n'a pas d'importance et le tri n'a pas d'importance. Selon l'interface utilisateur, les valeurs peuvent être divisées [], par exemple pour cocher les cases d'une liste de la table des pilotes dans le scénario le moins courant sans avoir à aller dans une autre table pour les obtenir.
jmcclure
44

"Une des raisons était la paresse".

Cela fait sonner l'alarme. La seule raison pour laquelle vous devriez faire quelque chose comme ça est que vous savez comment le faire "de la bonne façon", mais vous êtes arrivé à la conclusion qu'il y a une raison tangible de ne pas le faire de cette façon.

Cela dit: si les données que vous choisissez de stocker de cette manière sont des données que vous n'aurez jamais besoin d'interroger, alors il peut être judicieux de les stocker de la manière que vous avez choisie.

(Certains utilisateurs contesteraient la déclaration de mon paragraphe précédent, disant que "vous ne pouvez jamais savoir quelles exigences seront ajoutées à l'avenir". Ces utilisateurs sont soit mal avisés, soit déclarent une conviction religieuse. Parfois, il est avantageux de travailler selon les exigences que vous avoir devant vous.)

Hammerite
la source
J'entends toujours certaines personnes dire que "ma conception est plus flexible que la vôtre" lorsque je les confronte à des choses comme ne pas configurer de contraintes de clé étrangère ou stocker des listes dans un seul champ. Pour moi, la flexibilité (dans de tels cas) == pas de discipline == la paresse.
foresightyj
41

Il y a de nombreuses questions sur le SO:

  • comment obtenir un nombre de valeurs spécifiques à partir de la liste séparée par des virgules
  • comment obtenir des enregistrements qui n'ont que la même valeur spécifique 2/3 / etc à partir de cette liste séparée par des virgules

Un autre problème avec la liste séparée par des virgules est de s'assurer que les valeurs sont cohérentes - le stockage de texte signifie la possibilité de fautes de frappe ...

Ce sont tous des symptômes de données dénormalisées, et soulignez pourquoi vous devez toujours modéliser des données normalisées. La dénormalisation peut être une optimisation de requête, à appliquer lorsque le besoin se présente réellement .

Poneys OMG
la source
19

En général, tout peut être défendable s'il répond aux exigences de votre projet. Cela ne signifie pas que les gens seront d'accord ou voudront défendre votre décision ...

En général, le stockage des données de cette manière n'est pas optimal (par exemple, il est plus difficile d'effectuer des requêtes efficaces) et peut entraîner des problèmes de maintenance si vous modifiez les éléments de votre formulaire. Peut-être auriez-vous pu trouver un juste milieu et utiliser à la place un entier représentant un ensemble d'indicateurs de bits?

bobbymcr
la source
10

Oui, je dirais que c'est vraiment si mauvais. C'est un choix défendable, mais cela ne le rend pas correct ou bon.

Il rompt la première forme normale.

Une deuxième critique est que le fait de mettre les résultats d'entrée bruts directement dans une base de données, sans aucune validation ou liaison du tout, vous laisse ouvert aux attaques par injection SQL.

Ce que vous appelez la paresse et le manque de connaissances SQL, c'est ce dont les néophytes sont faits. Je recommanderais de prendre le temps de le faire correctement et de le voir comme une occasion d'apprendre.

Ou laissez-le tel quel et apprenez la douloureuse leçon d'une attaque par injection SQL.

duffymo
la source
19
Je ne vois rien dans cette question qui suggère qu'il est vulnérable à l'injection SQL. L'injection SQL et la normalisation de la base de données sont des sujets orthogonaux, et votre digression sur l'injection n'est pas pertinente pour la question.
Hammerite
5
@Paul: Et peut-être que la même attitude le conduira à être heurté par un bus lorsqu'il ne regarde pas dans les deux sens avant de traverser la rue, mais vous ne l'avez pas prévenu de cela. Edit: j'avais pensé que vous étiez l'affiche de cette réponse, mon erreur.
Hammerite
1
@Hammerite - votre extrapolation aux bus est ridicule.
duffymo
4
Oui, c'était censé être ridicule. Son ridicule illustre le point que je soulève, à savoir que cela n'a aucun sens de le mettre en garde contre quelque chose dont vous n'avez aucune raison de penser qu'il doit être prévenu.
Hammerite
1
Oui je vois. Je pense que j'avais bien plus de raisons que votre avertissement concernant les bus.
duffymo
7

Eh bien, j'utilise une liste séparée par des tabulations de paires clé / valeur dans une colonne NTEXT dans SQL Server depuis plus de 4 ans maintenant et cela fonctionne. Vous perdez la flexibilité de faire des requêtes mais d'un autre côté, si vous avez une bibliothèque qui persiste / dérive la paire clé-valeur, ce n'est pas une mauvaise idée.

Raj
la source
13
Non, c'est une horrible idée. Vous avez réussi à vous en tirer, mais le coût de vos quelques minutes de développement vous a fait perdre les performances, la flexibilité et la maintenabilité de votre code.
Paul Tomblin
5
Paul, je suis d'accord. Mais comme je l'ai dit, je l'ai utilisé dans un but précis, et c'est pour une opération de saisie de données où vous avez de nombreux types de formulaires. Je révise la conception maintenant que j'ai appris NHibernate mais à l'époque j'avais besoin de la flexibilité pour concevoir le formulaire dans ASP.NET et utiliser les ID de zone de texte comme clé dans la paire clé / valeur.
Raj
28
+1 juste pour contrer les downvotes. Dire à quelqu'un qui a maintenu l'application pendant 4 ans des problèmes de maintenance est un peu présomptueux. Il y a très peu d'idées "horribles" dans le développement sw - la plupart du temps ce ne sont que des idées avec une applicabilité très limitée. Il est raisonnable d'avertir les gens des limites, mais châtier ceux qui l'ont fait et qui l'ont vécu me semble être une attitude plus sainte que toi dont je peux me passer.
Mark Brackett
7

J'avais besoin d'une colonne à valeurs multiples, elle pourrait être implémentée comme un champ xml

Il peut être converti en virgule délimitée si nécessaire

interroger une liste XML dans le serveur SQL à l'aide de Xquery .

En étant un champ xml, certaines des préoccupations peuvent être résolues.

Avec CSV: impossible de garantir que chaque valeur correspond au bon type de données: aucun moyen d'empêcher 1,2,3, banane, 5

Avec XML: les valeurs d'une balise peuvent être forcées pour être du bon type


Avec CSV: impossible d'utiliser des contraintes de clé étrangère pour lier des valeurs à une table de recherche; aucun moyen de faire respecter l'intégrité référentielle.

Avec XML: toujours un problème


Avec CSV: impossible d'appliquer l'unicité: aucun moyen d'empêcher 1,2,3,3,3,5

Avec XML: toujours un problème


Avec CSV: impossible de supprimer une valeur de la liste sans récupérer la liste entière.

Avec XML: les éléments uniques peuvent être supprimés


Avec CSV: difficile de rechercher toutes les entités avec une valeur donnée dans la liste; vous devez utiliser un scan de table inefficace.

Avec XML: le champ xml peut être indexé


Avec CSV: difficile de compter les éléments de la liste, ou effectuez d'autres requêtes agrégées. **

Avec XML: pas particulièrement difficile


Avec CSV: difficile de joindre les valeurs à la table de recherche à laquelle elles font référence. **

Avec XML: pas particulièrement difficile


Avec CSV: difficile de récupérer la liste dans l'ordre trié.

Avec XML: pas particulièrement difficile


Avec CSV: le stockage d'entiers sous forme de chaînes prend environ deux fois plus d'espace que le stockage d'entiers binaires.

Avec XML: le stockage est encore pire qu'un csv


Avec CSV: Plus beaucoup de virgules.

Avec XML: les balises sont utilisées à la place des virgules


En bref, l'utilisation de XML permet de contourner certains des problèmes liés à la liste délimitée ET peut être converti en liste délimitée selon les besoins

James A Mohler
la source
6

Oui, il est mauvais. Mon opinion est que si vous n'aimez pas utiliser les bases de données relationnelles, alors cherchez une alternative qui vous convient mieux, il existe de nombreux projets "NOSQL" intéressants avec des fonctionnalités vraiment avancées.

Robin
la source
0

Je prendrais probablement le juste milieu: faire de chaque champ du CSV une colonne distincte de la base de données, mais ne vous inquiétez pas beaucoup de la normalisation (du moins pour l'instant). À un moment donné, la normalisation peut devenir intéressante, mais avec toutes les données placées dans une seule colonne, vous n'retirez pratiquement aucun avantage de l'utilisation d'une base de données. Vous devez séparer les données en champs / colonnes logiques / tout ce que vous voulez les appeler avant de pouvoir les manipuler de manière significative.

Jerry Coffin
la source
Le formulaire contient quelques champs supplémentaires, ce n'est qu'une partie du formulaire (que je n'ai pas bien expliqué dans la question).
Mad Scientist
0

Si vous avez un nombre fixe de champs booléens, vous pouvez utiliser un INT(1) NOT NULL(ou BIT NOT NULLs'il existe) ou CHAR (0)(nullable) pour chacun. Vous pouvez également utiliser un SET(j'oublie la syntaxe exacte).

Solomon Ucko
la source
1
INT(1)prend 4 octets; le (1)n'a pas de sens.
Rick James