Comment voulez-vous concevoir une base de données de l'utilisateur avec des champs personnalisés

18

Cette question porte sur la façon de concevoir une base de données, il peut s'agir de bases de données relationnelles / nosql, en fonction de la meilleure solution


Étant donné une exigence où vous devrez créer un système qui impliquera une base de données pour suivre "Société" et "Utilisateur". Un seul utilisateur appartiennent toujours à un seul société

  • Un utilisateur peut appartenir à une société
  • Une entreprise peut avoir plusieurs utilisateurs

La conception de la table "Entreprise" est assez simple. La société aura les attributs / colonnes suivants: (restons simples)

ID, COMPANY_NAME, CREATED_ON

premier scénario

Simple et direct, les utilisateurs ont tous le même attribut, donc cela peut être facilement fait dans un style relationnel, table utilisateur:

ID, COMPANY_ID, FIRST_NAME, LAST_NAME, EMAIL, CREATED_ON

deuxième scénario

Que se passe-t-il si différentes entreprises souhaitent stocker un attribut de profil différent pour leur utilisateur. Chaque entreprise aura un ensemble défini d'attributs qui s'appliqueraient à tous les utilisateurs de cette entreprise.

Par exemple:

  • La société A souhaite stocker: LIKE_MOVIE (booléen), LIKE_MUSIC (booléen)
  • Société B veut magasin: FAV_CUISINE (String)
  • La société C veut stocker: OWN_DOG (Boolean), DOG_COUNT (int)

approche 1

la voie de la force brute est d'avoir un seul schéma pour l'utilisateur et leur laisser quand ils ne nulls appartiennent à la société:

ID, COMPANY_ID, FIRST_NAME, LAST_NAME, EMAIL, LIKE_MOVIE, LIKE_MUSIC, FAV_CUISINE, OWN_DOG, DOG_COUNT, CREATED_ON

Ce qui est un peu méchante parce que vous finirez avec beaucoup de lignes NULLS et utilisateurs qui ont des colonnes qui ne sont pas pertinents pour les (p. Tous les utilisateurs appartenant à la société A a des valeurs NULL pour FAV_CUISINE, OWN_DOG, DOG_COUNT)

approche 2

une deuxième approche, est d'avoir « champ forme libre »:

ID, COMPANY_ID, FIRST_NAME, LAST_NAME, EMAIL, CUSTOM_1, CUSTOM_2, CUSTOM_3, CREATED_ON

Ce qui serait désagréable en soi puisque vous n'avez aucune idée de ce que sont les champs personnalisés, le type de données ne reflétera pas les valeurs stockées (par exemple, nous stockons la valeur int en tant que VARCHAR).

approche 3

J'ai cherché dans le champ PostgreSQL JSON, auquel cas vous aurez:

ID, COMPANY_ID, FIRST_NAME, LAST_NAME, EMAIL, CUSTOM_PROFILE_JSON, CREATED_ON

Dans ce cas, comment pourriez-vous appliquer différents schémas à un utilisateur? Un utilisateur avec la société A aura un schéma qui ressemble à

 {"LIKE_MOVIE":"boolean", "LIKE_MUSIC": "boolean"}

Alors qu'un utilisateur avec la société C aura un schéma différent:

 {"OWN_DOG ":"boolean", "DOG_COUNT": "int"}

Comment dois-je résoudre ce problème?

solution relationnelle? solution NoSQL?


Edit: J'ai aussi pensé à une table « CUSTOM_PROFILE » qui l' essentiel stocker des attributs d' utilisateur dans les lignes plutôt que des colonnes.

Il y a 2 problèmes avec cette approche:

1) Les données grandissent par utilisateur croissent sous forme de lignes plutôt que de colonnes - et cela signifie pour obtenir une image complète de l'utilisateur, beaucoup de jointures doivent être effectuées, plusieurs jointures dans le tableau "profil personnalisé" sur les différents attributs personnalisés

2) La valeur des données est toujours stockée en tant que VARCHAR pour être générique, même si nous savons que les données sont censées être des nombres entiers ou booléens, etc.

noobcser
la source
3
Si plusieurs sociétés ont des ensembles de données à plusieurs valeurs chaque client, alors vous devez absolument une table qui relie COMPANY_CUSTOMER.
Kilian Foth
Comment une aide de la table de liaison avec les données personnalisé? les colonnes devront toujours être différentes
noobcser
1
Rien de plus simple ne fera pas l'affaire.
Kilian Foth
3
Un schéma est une chose fixe, par définition; vous ne pouvez pas en créer un si vous ne savez pas ce que les champs que vous avez besoin sont. Jetez un oeil à Entity-attribute-value pour problems façon comme cela a tendance à se résoudre dans une base de données relationnelle.
Mason Wheeler

Réponses:

13

Les deux précédents exemples à la fois exiger que vous apportez des modifications au schéma de la portée de l'application augmente en plus la solution « custom_column » est difficile à étendre et à entretenir. Finalement, vous finirez avec Custom_510 puis imaginer combien ce tableau terrible consiste à travailler avec.

D'abord Utilisons votre schéma entreprises.

[Companies] ComnpanyId, COMPANY_NAME, CREATED_ON

Ensuite, nous allons utiliser aussi les utilisateurs schémas de haut niveau des attributs nécessaires qui seront utilisées par / partagées toutes les sociétés.

[Users] UserId, COMPANY_ID, FIRST_NAME, LAST_NAME, EMAIL, CREATED_ON

Voici donc une valeur d'exemple de la colonne d'attribut serait « LikeMusic »:

[UserAttributeDefinition] UserAttributeDefinitionId, CompanyId, Attribute

Ensuite, nous définissons une table userattributes qui contiendra des valeurs d'attributs utilisateur

[UserAttributes] UserAttributeDefinitionId, UserId, Value

Cela peut être modifié de plusieurs façons pour améliorer les performances. Vous pouvez utiliser plusieurs tables pour userattributes faire de chacun spécifique au type de données stockées dans la valeur ou tout simplement le laisser comme VARCHAR et travailler avec elle comme un magasin keyvalue.

Vous pouvez également déplacer COMPANYID hors de la table UserAttributeDefiniton et dans une table de référence croisée pour l'épreuvage avenir.

P. Roe
la source
merci - Je pensais à propos de cette approche - s'il vous plaît voir modifier. 2 problèmes: 1) Les données se développent sous forme de lignes, ce qui signifie que pour obtenir une image complète d'un utilisateur, vous devrez faire beaucoup de jointures. 2) "valeur" sera toujours stockée en tant que VARCHAR pour être générique, même si la valeur est en fait int ou booléenne, etc.
noobcser
1
Si vous utilisez int / bigint pour les identités de table et à vous joindre à ceux que vous ne serez avez des problèmes de performance jusqu'à ce que vous êtes un numéro d'extrême de lignes. Maintenant , si vous commencez à recherche en fonction de l'attribut valeurs cela pourrait présenter un problème si vous commencez à obtenir un grand nombre de dossiers. Dans ce cas , je travaille avec un DBA pour déterminer s'il y a des indices qui pourraient être créés ou peut - être une vue indexée qui pourrait accélérer ce genre de recherches. Je l' ai utilisé un schéma similaire et il prend dans 100 millions de disques par an, sans problèmes de performance que ce soit si la conception de base fonctionne assez bien l' OMI
P. Roe
Si la création de rapports, le filtrage, l'interrogation sont nécessaires et différents attributs peuvent appartenir à différents ensembles de données. Cette approche serait-elle meilleure que NoSQL? J'essaie de comprendre la différence de performance. Dans une situation similaire, seul l'utilisateur peut définir des rapports contenant des champs définis par l'utilisateur.
kos
Dans l'approche ci-dessus, comment implémenter la recherche, comme diff. les entreprises souhaitent effectuer des recherches dans leurs domaines, y compris les domaines d'utilisateurs. Quelle est la bonne approche pour fournir une recherche évolutive en plus de cela
techagrammer
Vous pouvez le rechercher normalement avec beaucoup de jointures. Vous pouvez utiliser un script ETL pour extraire les données que vous souhaitez rechercher et les placer dans une structure plus dénormalisée. Enfin, vous pouvez essayer d'utiliser des vues indexées comme méthode de recherche. Personnellement, je recommande la méthode ETL pour générer des structures dénormalisées faciles à rechercher.
P. Roe
7

Utilisez une base de données NoSQL. Il y aurait des documents d'entreprise et d'utilisateur. Les utilisateurs auraient une partie de leur schéma créée dynamiquement sur la base d'un modèle d'utilisateur (texte pour indiquer les champs / types pour cette entreprise.

\Company\<uniqueidentifier>
    - Name: <Name>
    - CreatedOn: <datetime>
    - UserTemplate: <Text>

\User\<uniqueidentifier>
    - COMPANY_ID: <ID>
    - FIRST_NAME: <Text>
    - LAST_NAME: <Text>
    - EMAIL: <Text>
    - CREATED_ON: <datetime>
    - * Dynamically created fields per company

Voici à quoi cela pourrait ressembler dans quelque chose comme Firebase.com. Vous devriez apprendre à le faire dans celui que vous choisissez.

JeffO
la source
ce que je pense à ou peut - être des colonnes JSON. Comment est la performance sur l' interrogation, les rapports de filtrage par rapport à la solution proposée par Proe.
kos
1
Chaque fois que vous compressez des données en json ou xml, puis les jetez dans une colonne, la recherche sera terriblement lente. Si vous devez rechercher les données présentées dans ma réponse ci-dessus, je vous conseille d'utiliser des vues indexées pour récupérer les données. Si cette solution n'est pas idéale, je recommanderais d'utiliser ETL pour copier les données dans une structure qui peut être facilement recherchée et rapportée.
P. Roe
Dans l'approche ci-dessus, comment implémenter la recherche, comme diff. les entreprises souhaitent effectuer des recherches dans leurs domaines, y compris les domaines d'utilisateurs. Quelle est la bonne approche pour fournir une recherche évolutive en plus de cela
techagrammer
Dans les bases de données nosql, vous pouvez avoir des données redondantes, mais elles sont structurées de manière à pouvoir être consultées. Celui montré ci-dessus est par identifiant unique. Un autre peut être \ Company \ Name. C'est comme avoir plusieurs index.
JeffO
3

Si vous allez fréquemment rencontrer des demandes de champs personnalisés, je les modéliserais de manière assez similaire à la base de données. Créer une table qui contient les métadonnées sur chaque champ personnalisé, CompanyCustomField (à qui il appartient, le type de données, etc.) et une autre table CompanyCustomFieldValues ​​qui contient le CustomerId, FieldID et la valeur. Si vous utilisez quelque chose comme Microsoft Sql Server, la colonne de valeur devrait être un type de données sql_variant.

Bien sûr, cela n'est pas facile car vous aurez besoin d'une interface qui permet aux administrateurs de définir des champs personnalisés pour chaque client, et d'une autre interface qui utilise réellement ces métadonnées pour créer une interface utilisateur pour collecter les valeurs des champs. Et si vous avez d'autres exigences, telles que le regroupement des champs ou la nécessité de faire un type de champ de liste de sélection, vous devrez l'accompagner de plus de métadonnées / autres tables (par exemple, CompanyCustomFieldPickListOptions).

Ce n'est pas anodin, mais il a l'avantage de ne pas nécessiter de modifications de base de données / de code pour chaque nouveau champ personnalisé. Toutes les autres fonctionnalités des champs personnalisés devront également être codées (par exemple, si vous souhaitez valider regex une valeur de chaîne, ou autoriser uniquement les dates entre certaines plages, ou si vous devez activer un champ personnalisé basé sur une autre valeur de champ personnalisé ).

Andy
la source
merci - Je pensais à propos de cette approche - s'il vous plaît voir modifier. 2 problèmes: 1) Les données se développent sous forme de lignes, ce qui signifie que pour obtenir une image complète d'un utilisateur, vous devrez faire beaucoup de jointures. 2) "valeur" sera toujours stockée en tant que VARCHAR pour être générique, même si la valeur est en fait int ou booléenne, etc.
noobcser
1
@noobcser Les données qui se développent sous forme de lignes n'ont pas vraiment d'importance, une fois que toutes les bases de données sont conçues autour des lignes et des jointures. Dans tous les cas, vous utiliseriez plus probablement des expressions de table communes pour cela, ce qui est assez bon dans ce genre de chose. Je ne suis pas sûr si vous avez manqué la partie où j'ai dit que vous pouvez utiliser sql_variant comme type de données pour la colonne de valeur, qui stocke la valeur comme n'importe quel type dans lequel vous vous en tenez. Pendant que je nomme les noms des fonctionnalités du serveur MS SQL, je m'attends à ce que d'autres SGBD matures aient des fonctionnalités similaires.
Andy
1
@noobcser Pour votre information , je l' ai en fait rencontré ces exigences assez souvent dans ma carrière et ont l' expérience de chacune des solutions proposées, donc je suggère celui qui a le mieux dans mon expérience. Utilisation des types de données XML pour ce genre de chose est en partie pourquoi je déteste que MS ajoutant xml comme type de données natif.
Andy
1

Une alternative aux autres réponses est d'avoir une table appelée profile_attrib, ou similaire que le schéma est entièrement géré par votre application.

À mesure que des attributs personnalisés sont ajoutés ALTER TABLE profile_attrib ADD COLUMN like_movie TINYINT(1), vous pouvez interdire leur suppression. Cela minimiserait votre adhésion, tout en offrant une flexibilité.

Je suppose que le compromis bit est l'application a besoin maintenant des privilèges alter table à la base de données, et vous devez être intelligent au sujet désinfectante les noms de colonnes.

Chris Seufert
la source
L'expression régulière [^\w-]+devrait assez bien le faire, ne pas laisser tout ce qui est pas 0-9A-Za-z_---mais oui, désinfectante est un must ici pour se protéger contre la bêtise ou la méchanceté.
Regular Joe
0

Votre question a de nombreuses solutions potentielles. Une solution consiste à stocker les attributs supplémentaires au format XML. Le XML peut être stocké sous forme de texte ou si vous utilisez une base de données qui supporte les types de XML comme XML (SQL Server). Le stockage en tant que texte limite votre capacité d'interrogation (comme la recherche sur un attribut personnalisé), mais si le stockage et la récupération sont tous vos besoins, c'est une bonne solution. Si on a besoin de requête, puis stocker le XML comme un type XML serait une meilleure option (bien que ce soit plus spécifique du fournisseur).

Cela donnera une la possibilité de stocker un nombre illimité d'attributs à un client avec juste l'ajout d'une colonne d'addition sur la table des clients. On pourrait stocker les attributs sous forme de hachage ou de dictionnaire, on perdra la sécurité du type car tout sera une chaîne pour commencer, mais si l'on applique une chaîne de format standard pour les dates, les nombres, les booléens, cela fonctionnera bien.

Pour plus d'informations:

https://msdn.microsoft.com/en-us/library/hh403385.aspx

@ La réponse de WalterMitty est également valable, bien que si l'on a beaucoup de clients avec des attributs différents, on puisse se retrouver avec de nombreuses tables si l'on suit le modèle d'héritage. Cela dépend du nombre d'attributs personnalisés partagés entre les clients.

Jon Raynor
la source
Cela peut fonctionner aussi bien, mais je me sens devient limité une fois que vous avez réellement besoin de faire quelque chose contre les données stockées dans le champ XML / JSON.
Andy
@Andy - C'est vrai, il y a une autre couche. Interrogez la base de données et analysez XML par opposition à simplement interroger la base de données. Je ne sais pas si je dirais que c'est limitant, juste plus lourd. Mais, ce serait quelque chose à considérer si les attributs personnalisés étaient largement utilisés.
Jon Raynor
Dans T-SQL, il est possible de définir le contenu de la colonne XML / JSON par rapport à un espace de noms et de rechercher des éléments sur les données personnalisées. Ce n'est pas difficile
Stephen York
-1

Vous devez normaliser votre base de données de manière à disposer de 3 tables différentes pour chaque type de profil d'entreprise. En utilisant votre exemple, vous auriez des tables avec des colonnes:

USER_ID, LIKE_MOVIE, LIKE_MUSIC

USER_ID, FAVORITE_CUISINE

USER_ID, OWN_DOG, DOG_COUNT

Cette approche suppose que vous connaîtrez à l'avance la forme des informations qu'une entreprise souhaite stocker et qu'elle ne changera pas souvent. Si la forme des données est inconnue au moment de la conception, il serait probablement préférable d'utiliser ce champ JSON ou une base de données nosql.

mortalapeman
la source
-1

Pour une raison ou une autre, les bases de données sont le seul domaine dans lequel l'effet de plate-forme interne apparaît le plus souvent. Ceci est juste un autre cas de pop-up anti-motif.

Dans ce cas, vous essayez de combattre la solution naturelle et correcte. Les utilisateurs de la société A ne sont pas des utilisateurs de la société B et doivent avoir leurs propres tables pour leurs propres champs.

Votre fournisseur de base de données ne vous facture pas à la table et vous n'avez pas besoin de deux fois l'espace disque pour deux fois les tables (en fait, avoir deux tables est plus efficace car vous ne stockez pas les attributs de A pour les utilisateurs de B. Même en stockant uniquement des valeurs NULL prend de la place).

Bien sûr, s'il y a suffisamment de champs communs, vous pouvez les factoriser dans une table d'utilisateurs partagée et avoir une clé étrangère dans chacune des tables d'utilisateurs spécifiques à l'entreprise. Il s'agit d'une structure si simple qu'aucun optimiseur de requête de base de données ne lutte avec elle. Tout JOIN nécessaire est trivial.

MSalters
la source
3
Et si vous avez des milliers de clients, une table par personne peut rapidement devenir impossible à gérer, sans oublier que vous aurez besoin d'un code personnalisé pour les champs personnalisés de chaque client.
Andy
@Andy: Devinez quoi? La situation sera encore plus difficile à gérer si vous mélangez un millier de schémas différents dans une seule table! Et oui, vous avez probablement besoin d'un code personnalisé pour les champs personnalisés. Encore une fois, c'est plus simple, pas plus difficile, si chaque client a une table propre et séparée. Essayer de choisir les champs de la société X parmi mille autres est un bordel sanglant.
MSalters
Faites-vous référence à ma réponse ou à l'idée du PO de coller toutes les colonnes supplémentaires sur la table client?
Andy
2
Le but ici est de trouver une solution maintenable et évolutive. Créer une table par client est définitivement le contraire. Chaque fois que vous embarquez un nouveau client, il n'est pas réaliste: d'exécuter un script de création de table, de mettre à jour votre code (objets Entity) et de redéployer.
tsOverflow
Cette idée d'utiliser des tables partagées pour tous les clients est en soi une discussion distincte sur l'architecture SaaS, et il y a de bonnes raisons de garder les clients dans différentes tables (ou même dans différentes bases de données, permettant la sauvegarde / restauration et la mise à l'échelle par client). Dans ce scénario, la création de colonnes personnalisées dans la table principale est une évidence. J'ai voté positivement et je me demande pourquoi les gens votent contre cela simplement parce qu'ils n'aiment pas cette approche. L'effet de plateforme interne est une réalité: en utilisant un modèle EVA, votre requête sera plus difficile, économisant plus, intégrité plus difficile, etc.
drizin
-1

Ma solution suppose que vous appelleriez cette requête à partir d'un programme et que vous devriez pouvoir effectuer un post-traitement. Vous pouvez avoir les colonnes suivantes:

ID, COMPANY_ID, FIRST_NAME, LAST_NAME, EMAIL, CUSTOM_VALUES

CUSTOM_VALUES sera de type chaîne stockant la paire clé / valeur. la clé sera le nom de la colonne et la valeur sera la valeur de la colonne, par exemple

LIKE_MOVIE;yes;LIKE_MUSIC;no;FAV_CUISINE;rice

dans ces CUSTOM_VALUES, vous n'enregistrerez que les informations existantes. Lorsque vous interrogez à partir d'un programme, vous pouvez diviser cette chaîne et l'utiliser.

J'ai utilisé cette logique et cela fonctionne bien, c'est juste que vous devrez appliquer une logique de filtrage dans le code et non dans la requête.

techExplorer
la source