Mes exigences sont:
- Besoin de pouvoir ajouter dynamiquement des champs définis par l'utilisateur de tout type de données
- Besoin de pouvoir interroger rapidement les UDF
- Besoin de pouvoir effectuer des calculs sur les UDF en fonction du type de données
- Besoin de pouvoir trier les UDF en fonction du type de données
Les autres informations:
- Je recherche principalement la performance
- Il y a quelques millions d'enregistrements maîtres auxquels des données UDF peuvent être jointes
- Lors de ma dernière vérification, il y avait plus de 50 millions d'enregistrements UDF dans notre base de données actuelle
- La plupart du temps, un UDF n'est attaché qu'à quelques milliers de fiches maîtresses, pas toutes
- Les UDF ne sont pas jointes ou utilisées comme clés. Ce ne sont que des données utilisées pour des requêtes ou des rapports
Options:
Créez une grande table avec StringValue1, StringValue2 ... IntValue1, IntValue2, ... etc. Je déteste cette idée, mais je la considérerai si quelqu'un peut me dire qu'elle est meilleure que d'autres idées et pourquoi.
Créez une table dynamique qui ajoute une nouvelle colonne à la demande si nécessaire. Je n'aime pas non plus cette idée car je pense que les performances seraient lentes à moins que vous n'indexiez chaque colonne.
Créez une table unique contenant UDFName, UDFDataType et Value. Lorsqu'un nouvel UDF est ajouté, générez une vue qui extrait uniquement ces données et les analyse dans le type spécifié. Les éléments qui ne répondent pas aux critères d'analyse retournent NULL.
Créez plusieurs tables UDF, une par type de données. Nous aurions donc des tables pour les UDFStrings, les UDFDates, etc.
XML DataTypes? Je n'ai jamais travaillé avec ces derniers mais je les ai vus mentionnés. Je ne sais pas s'ils me donneraient les résultats que je souhaite, surtout avec la performance.
Autre chose?
Réponses:
Si la performance est la principale préoccupation, j'irais avec # 6 ... une table par UDF (vraiment, c'est une variante de # 2). Cette réponse est spécifiquement adaptée à cette situation et à la description de la distribution des données et des modèles d'accès décrits.
Avantages:
Étant donné que vous indiquez que certains UDF ont des valeurs pour une petite partie de l'ensemble de données global, une table distincte vous offrira les meilleures performances car cette table sera aussi grande que nécessaire pour prendre en charge l'UDF. Il en va de même pour les indices associés.
Vous bénéficiez également d'un gain de vitesse en limitant la quantité de données à traiter pour les agrégations ou autres transformations. Le fractionnement des données en plusieurs tables vous permet d'effectuer une partie de l'agrégation et d'autres analyses statistiques sur les données UDF, puis de joindre ce résultat à la table principale via une clé étrangère pour obtenir les attributs non agrégés.
Vous pouvez utiliser des noms de table / colonne qui reflètent ce que sont réellement les données.
Vous avez un contrôle total pour utiliser les types de données, vérifier les contraintes, les valeurs par défaut, etc. pour définir les domaines de données. Ne sous-estimez pas les performances résultant de la conversion de type de données à la volée. Ces contraintes aident également les optimiseurs de requêtes du SGBDR à développer des plans plus efficaces.
Si jamais vous avez besoin d'utiliser des clés étrangères, l'intégrité référentielle déclarative intégrée est rarement surpassée par l'application de contraintes basées sur les déclencheurs ou au niveau de l'application.
Les inconvénients:
Cela pourrait créer de nombreuses tables. L'application de la séparation des schémas et / ou d'une convention de dénomination atténuerait ce problème.
Il y a plus de code d'application nécessaire pour faire fonctionner la définition et la gestion UDF. Je pense que c'est encore moins de code nécessaire que pour les options d'origine 1, 3 et 4.
Autres considérations:
S'il y a quelque chose dans la nature des données qui aurait du sens pour le regroupement des FDU, cela devrait être encouragé. De cette façon, ces éléments de données peuvent être combinés dans un seul tableau. Par exemple, disons que vous avez des UDF pour la couleur, la taille et le coût. La tendance dans les données est que la plupart des instances de ces données ressemblent à
plutôt que
Dans un tel cas, vous n'encourrez pas de pénalité de vitesse notable en combinant les 3 colonnes dans 1 table car peu de valeurs seraient NULL et vous évitez de créer 2 tables de plus, soit 2 jointures de moins nécessaires lorsque vous devez accéder aux 3 colonnes. .
Si vous rencontrez un mur de performances à partir d'un UDF qui est fortement peuplé et fréquemment utilisé, cela doit être pris en compte pour l'inclusion dans la table principale.
La conception de table logique peut vous amener à un certain point, mais lorsque le nombre d'enregistrements devient vraiment énorme, vous devriez également commencer à regarder quelles options de partitionnement de table sont fournies par votre SGBDR de choix.
la source
Je l' ai écrit à propos de ce problème beaucoup . La solution la plus courante est l'anti-modèle Entité-Attribut-Valeur, qui est similaire à ce que vous décrivez dans votre option # 3. Évitez cette conception comme la peste .
Ce que j'utilise pour cette solution lorsque j'ai besoin de champs personnalisés vraiment dynamiques, c'est de les stocker dans un blob de XML, afin que je puisse ajouter de nouveaux champs à tout moment. Mais pour accélérer les choses, créez également des tables supplémentaires pour chaque champ sur lequel vous devez rechercher ou trier (vous n'avez pas une table par champ - juste une table par champ de recherche ). Ceci est parfois appelé une conception d'index inversé.
Vous pouvez lire un article intéressant de 2009 sur cette solution ici: http://backchannel.org/blog/friendfeed-schemaless-mysql
Ou vous pouvez utiliser une base de données orientée document, dans laquelle il est prévu que vous ayez des champs personnalisés par document. Je choisirais Solr .
la source
fieldname
outablename
stocke des identificateurs de métadonnées sous forme de chaînes de données, et c'est le début de nombreux problèmes. Voir aussi en.wikipedia.org/wiki/Inner-platform_effectJe créerais très probablement un tableau de la structure suivante:
Les types exacts de cours dépendent de vos besoins (et bien sûr des dbms que vous utilisez). Vous pouvez également utiliser le champ NumberValue (decimal) pour les int et les booléens. Vous pouvez également avoir besoin d'autres types.
Vous avez besoin d'un lien vers les enregistrements maîtres qui possèdent la valeur. Il est probablement plus simple et plus rapide de créer une table de champs utilisateur pour chaque table maître et d'ajouter une simple clé étrangère. De cette façon, vous pouvez filtrer facilement et rapidement les enregistrements principaux par champs utilisateur.
Vous voudrez peut-être avoir une sorte d'informations de métadonnées. Vous vous retrouvez donc avec ce qui suit:
Tableau UdfMetaData
Table MasterUdfValues
Quoi que vous fassiez, je ne changerais pas la structure de la table de manière dynamique. C'est un cauchemar de maintenance. Je n'utiliserais pas non plus de structures XML, elles sont beaucoup trop lentes.
la source
Cela ressemble à un problème qui pourrait être mieux résolu par une solution non relationnelle, comme MongoDB ou CouchDB.
Ils permettent tous deux une expansion dynamique du schéma tout en vous permettant de conserver l'intégrité du tuple que vous recherchez.
Je suis d'accord avec Bill Karwin, le modèle EAV n'est pas une approche performante pour vous. L'utilisation de paires nom-valeur dans un système relationnel n'est pas intrinsèquement mauvaise, mais ne fonctionne bien que lorsque la paire nom-valeur crée un tuple complet d'informations. Lorsque vous l'utilisez vous oblige à reconstruire dynamiquement une table au moment de l'exécution, toutes sortes de choses commencent à devenir difficiles. L'interrogation devient un exercice de maintenance du pivot ou vous oblige à pousser la reconstruction de tuple vers le haut dans la couche d'objet.
Vous ne pouvez pas déterminer si une valeur nulle ou manquante est une entrée valide ou une absence d'entrée sans incorporer des règles de schéma dans votre couche d'objets.
Vous perdez la capacité de gérer efficacement votre schéma. Un varchar de 100 caractères est-il le bon type pour le champ «valeur»? 200 caractères? Devrait-il être nvarchar à la place? Cela peut être un compromis difficile et qui se termine par la nécessité de placer des limites artificielles sur la nature dynamique de votre ensemble. Quelque chose comme "vous ne pouvez avoir que x champs définis par l'utilisateur et chacun ne peut contenir que y caractères.
Avec une solution orientée document, comme MongoDB ou CouchDB, vous gérez tous les attributs associés à un utilisateur dans un seul tuple. Comme les jointures ne sont pas un problème, la vie est heureuse, car aucun de ces deux ne se débrouille bien avec les jointures, malgré le battage médiatique. Vos utilisateurs peuvent définir autant d'attributs qu'ils le souhaitent (ou vous l'autoriserez) à des longueurs qui ne seront pas difficiles à gérer jusqu'à ce que vous atteigniez environ 4 Mo.
Si vous avez des données qui nécessitent une intégrité de niveau ACID, vous pouvez envisager de diviser la solution, les données à haute intégrité vivant dans votre base de données relationnelle et les données dynamiques vivant dans un magasin non relationnel.
la source
Même si vous fournissez à un utilisateur l'ajout de colonnes personnalisées, il ne sera pas nécessairement le cas que l'interrogation sur ces colonnes fonctionnera bien. La conception des requêtes comporte de nombreux aspects qui leur permettent de bien fonctionner, le plus important étant la spécification appropriée de ce qui doit être stocké en premier lieu. Ainsi, fondamentalement, voulez-vous permettre aux utilisateurs de créer un schéma sans réfléchir aux spécifications et être en mesure de tirer rapidement des informations de ce schéma? Si tel est le cas, il est peu probable qu'une telle solution évolue bien, surtout si vous souhaitez permettre à l'utilisateur de faire une analyse numérique des données.
Option 1
IMO, cette approche vous donne un schéma sans aucune connaissance de ce que signifie le schéma, qui est une recette pour un désastre et un cauchemar pour les concepteurs de rapports. Par exemple, vous devez avoir les métadonnées pour savoir quelle colonne stocke quelles données. Si ces métadonnées sont faussées, elles risquent de détruire vos données. De plus, il est facile de mettre les mauvaises données dans la mauvaise colonne. ("Quoi? String1 contient le nom des couvents? Je pensais que c'était la drogue préférée de Chalie Sheen.")
Option 3,4,5
OMI, les exigences 2, 3 et 4 éliminent toute variation d'un EAV. Si vous avez besoin d'interroger, de trier ou de faire des calculs sur ces données, un EAV est le rêve de Cthulhu et le cauchemar de votre équipe de développement et de DBA. Les EAV créeront un goulot d'étranglement en termes de performances et ne vous donneront pas l'intégrité des données dont vous avez besoin pour accéder rapidement aux informations que vous souhaitez. Les requêtes se transformeront rapidement en nœuds gordiens de tableau croisé.
Option 2,6
Cela ne laisse vraiment qu'un choix: rassembler les spécifications, puis construire le schéma.
Si le client souhaite obtenir les meilleures performances sur les données qu'il souhaite stocker, il doit passer par le processus de collaboration avec un développeur pour comprendre ses besoins afin de les stocker le plus efficacement possible. Il peut toujours être stocké dans une table séparée du reste des tables avec du code qui crée dynamiquement un formulaire basé sur le schéma de la table. Si vous avez une base de données qui permet des propriétés étendues sur les colonnes, vous pouvez même les utiliser pour aider le générateur de formulaires à utiliser de jolies étiquettes, info-bulles, etc. afin que tout ce qui était nécessaire soit d'ajouter le schéma. Dans tous les cas, pour créer et exécuter des rapports efficacement, les données doivent être stockées correctement. Si les données en question ont beaucoup de valeurs nulles, certaines bases de données ont la capacité de stocker ce type d'informations. Par exemple,
S'il ne s'agissait que d'un sac de données sur lequel aucune analyse, aucun filtrage ou tri ne devait être effectué, je dirais qu'une variante d'un EAV pourrait faire l'affaire. Cependant, compte tenu de vos besoins, la solution la plus efficace sera d'obtenir les spécifications appropriées même si vous stockez ces nouvelles colonnes dans des tables séparées et créez des formulaires de manière dynamique à partir de ces tables.
Colonnes clairsemées
la source
Selon mes recherches, plusieurs tables basées sur le type de données ne vous aideront pas en termes de performances. Surtout si vous avez des données en vrac, comme des enregistrements 20K ou 25K avec plus de 50 UDF. La performance était la pire.
Vous devriez aller avec une seule table avec plusieurs colonnes comme:
la source
Il s'agit d'une situation problématique et aucune des solutions ne semble «correcte». Cependant, l'option 1 est probablement la meilleure tant en termes de simplicité qu'en termes de performances.
C'est également la solution utilisée dans certaines applications d'entreprise commerciales.
ÉDITER
une autre option qui est disponible maintenant, mais qui n'existait pas (ou du moins n'était pas mature) lorsque la question a été initialement posée est d'utiliser les champs json dans la base de données.
de nombreuses bases de données relationnelles prennent désormais en charge les champs basés sur json (qui peuvent inclure une liste dynamique de sous-champs) et permettent de les interroger
postgress
mysql
la source
J'ai eu de l'expérience ou 1, 3 et 4 et ils finissent tous soit compliqués, sans savoir quelles sont les données, soit vraiment compliqué avec une sorte de catégorisation douce pour diviser les données en types d'enregistrement dynamiques.
Je serais tenté d'essayer XML, vous devriez être en mesure d'appliquer des schémas au contenu du xml pour vérifier le typage des données, etc., ce qui aidera à contenir des ensembles de données UDF différents. Dans les versions plus récentes du serveur SQL, vous pouvez indexer les champs XML, ce qui devrait améliorer les performances. (voir http://blogs.technet.com/b/josebda/archive/2009/03/23/sql-server-2008-xml-indexing.aspx ) par exemple
la source
Si vous utilisez SQL Server, ne négligez pas le type sqlvariant. C'est assez rapide et devrait faire votre travail. D'autres bases de données peuvent avoir quelque chose de similaire.
Les types de données XML ne sont pas très bons pour des raisons de performances. Si vous effectuez des calculs sur le serveur, vous devez constamment les désérialiser.
L'option 1 sonne mal et semble grossière, mais la performance peut être votre meilleure option. J'ai déjà créé des tables avec des colonnes nommées Field00-Field99 parce que vous ne pouvez tout simplement pas battre les performances. Vous devrez peut-être également tenir compte de vos performances INSERT, auquel cas c'est également celui qu'il vous faut. Vous pouvez toujours créer des vues sur cette table si vous voulez qu'elle soit soignée!
la source
SharePoint utilise l'option 1 et a des performances raisonnables.
la source
J'ai réussi cela avec beaucoup de succès dans le passé en utilisant aucune de ces options (option 6? :)).
Je crée un modèle avec lequel les utilisateurs peuvent jouer (stocker au format XML et exposer via un outil de modélisation personnalisé) et à partir des tables et vues générées par le modèle pour joindre les tables de base avec les tables de données définies par l'utilisateur. Ainsi, chaque type aurait une table de base avec des données de base et une table utilisateur avec des champs définis par l'utilisateur.
Prenons un document comme exemple: les champs typiques seraient le nom, le type, la date, l'auteur, etc. Cela irait dans la table principale. Ensuite, les utilisateurs définiraient leurs propres types de documents spéciaux avec leurs propres champs, tels que contract_end_date, renouveler_clause, bla bla bla. Pour ce document défini par l'utilisateur, il y aurait la table des documents principaux, la table xcontract, jointe sur une clé primaire commune (la clé primaire xcontracts est donc également étrangère sur la clé primaire de la table principale). Ensuite, je générerais une vue pour envelopper ces deux tableaux. Les performances lors des requêtes étaient rapides. des règles métier supplémentaires peuvent également être intégrées dans les vues. Cela a très bien fonctionné pour moi.
la source
Notre base de données alimente une application SaaS (logiciel de helpdesk) où les utilisateurs disposent de plus de 7k "champs personnalisés". Nous utilisons une approche combinée:
(EntityID, FieldID, Value)
table de recherche des donnéesentities
table, qui contient toutes les valeurs d'entité, utilisé pour afficher les données. (de cette façon, vous n'avez pas besoin d'un million de JOIN pour obtenir les valeurs des valeurs).Vous pouvez diviser davantage le n ° 1 pour avoir une "table par type de données" comme cette réponse suggère , de cette façon vous pouvez même indexer vos UDF.
PS Quelques mots pour défendre l'approche «Entité-Attribut-Valeur» que tout le monde continue de dénigrer. Nous avons utilisé le # 1 sans le # 2 pendant des décennies et cela a très bien fonctionné. Parfois, c'est une décision commerciale. Avez-vous le temps de réécrire votre application et de repenser la base de données ou vous pouvez jeter quelques dollars sur les serveurs cloud, qui sont vraiment bon marché de nos jours? À propos, lorsque nous utilisions l'approche n ° 1, notre base de données contenait des millions d'entités, accédées par des centaines de milliers d'utilisateurs, et un serveur de base de données double cœur de 16 Go fonctionnait très bien
la source
custom_fields
table contenant des valeurs telles que 1 =>last_concert_year
, 2 =>band
, 3 =>music
puis unecustom_fields_values
table avec les valeurs 001, 1, 1976 002, 1, 1977 003, 2,Iron Maiden
003, 3 ,Metal
J'espère que l'exemple a du sens pour vous et désolé pour le formatage!bands
tableau avec une ligne1,'Iron Maiden'
puiscustom_fields
avec des lignes1,'concert_year' | 2,'music'
puiscustom_fields_values
avec des lignes1,1,'1977'|1,2,'metal'
Dans les commentaires, je vous ai vu dire que les champs UDF doivent vider les données importées qui ne sont pas correctement mappées par l'utilisateur.
Une autre option est peut-être de suivre le nombre d'UDF créés par chaque utilisateur et de les forcer à réutiliser des champs en disant qu'ils peuvent utiliser 6 (ou une autre limite également aléatoire) en haut de champs personnalisés.
Lorsque vous êtes confronté à un problème de structuration de base de données comme celui-ci, il est souvent préférable de revenir à la conception de base de l'application (système d'importation dans votre cas) et de lui imposer quelques contraintes supplémentaires.
Maintenant, ce que je ferais, c'est l'option 4 (EDIT) avec l'ajout d'un lien vers les utilisateurs:
Assurez-vous maintenant de créer des vues pour optimiser les performances et obtenir les bons index. Ce niveau de normalisation réduit l'encombrement de la base de données, mais votre application plus complexe.
la source
Je recommanderais le n ° 4 car ce type de système a été utilisé dans Magento, une plate-forme CMS de commerce électronique hautement accréditée. Utilisez une table unique pour définir vos champs personnalisés à l'aide des colonnes fieldId et label . Ensuite, ayez des tables séparées pour chaque type de données et dans chacune de ces tables ont un index qui indexe par fieldId et les colonnes de valeur de type de données . Ensuite, dans vos requêtes, utilisez quelque chose comme:
Cela garantira à mon avis les meilleures performances possibles pour les types définis par l'utilisateur.
D'après mon expérience, j'ai travaillé sur plusieurs sites Web Magento qui servent des millions d'utilisateurs par mois, hébergent des milliers de produits avec des attributs de produit personnalisés et la base de données gère facilement la charge de travail, même pour les rapports.
Pour la création de rapports, vous pouvez
PIVOT
convertir les valeurs de libellé de votre table Fields en noms de colonne, puis faire pivoter les résultats de votre requête de chaque table de type de données dans ces colonnes pivotées.la source