¿Cómo diseñaría una base de datos de usuario con campos personalizados?

18

Esta pregunta es sobre cómo debería diseñar una base de datos, pueden ser bases de datos relacionales / nosql, dependiendo de cuál será la mejor solución


Dado un requisito en el que deberá crear un sistema que incluya una base de datos para rastrear "Compañía" y "Usuario". Un solo usuario siempre pertenece a una sola empresa.

  • Un usuario solo puede pertenecer a una empresa
  • Una empresa puede tener muchos usuarios

El diseño de la tabla "Empresa" es bastante sencillo. La empresa tendrá los siguientes atributos / columnas: (hagámoslo simple)

ID, COMPANY_NAME, CREATED_ON

Primer escenario

Simple y directo, todos los usuarios tienen el mismo atributo, por lo que esto se puede hacer fácilmente en estilo relacional, tabla de usuario:

ID, COMPANY_ID, FIRST_NAME, LAST_NAME, EMAIL, CREATED_ON

Segundo escenario

¿Qué sucede si diferentes compañías desean almacenar diferentes atributos de perfil para sus usuarios? Cada compañía tendrá un conjunto definido de atributos que se aplicarán a todos los usuarios de esa compañía.

Por ejemplo:

  • La empresa A quiere almacenar: LIKE_MOVIE (boolean), LIKE_MUSIC (boolean)
  • La empresa B quiere almacenar: FAV_CUISINE (String)
  • La empresa C quiere almacenar: OWN_DOG (boolean), DOG_COUNT (int)

Enfoque 1

La forma de fuerza bruta es tener un esquema único para el usuario y dejar que tengan valores nulos cuando no pertenecen a la empresa:

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

Lo cual es un poco desagradable porque terminará con una gran cantidad de NULLS y filas de usuarios que tienen columnas que no son relevantes para ellos (es decir, todos los usuarios que pertenecen a la Compañía A tienen valores NULL para FAV_CUISINE, OWN_DOG, DOG_COUNT)

Enfoque 2

Un segundo enfoque es tener un "campo de forma libre":

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

Lo cual sería desagradable por sí solo, ya que no tiene idea de qué son los campos personalizados, el tipo de datos no reflejará los valores almacenados (por ejemplo, almacenaremos el valor int como VARCHAR).

Enfoque 3

He investigado el campo PostgreSQL JSON, en cuyo caso tendrá:

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

En este caso, ¿cómo podría aplicar diferentes esquemas a un usuario? Un usuario con la empresa A tendrá un esquema similar

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

Mientras que un usuario con la Compañía C tendrá un esquema diferente:

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

¿Cómo debo resolver este problema? ¿Cómo puedo diseñar la base de datos correctamente para permitir este esquema flexible para un solo "objeto" (Usuario) en función de la relación que tienen (Empresa)?

solución relacional? solución nosql?


Editar: También he pensado en una tabla "CUSTOM_PROFILE" que esencialmente almacenará los atributos del usuario en filas en lugar de columnas.

Hay 2 problemas con este enfoque:

1) Los datos crecen por usuario como filas en lugar de columnas, y esto significa que para obtener una imagen completa del usuario, es necesario realizar muchas uniones, varias uniones a la tabla de "perfil personalizado" en los diferentes atributos personalizados

2) El valor de los datos siempre se almacena como VARCHAR para que sea genérico, incluso si sabemos que se supone que los datos son enteros o booleanos, etc.

noobcser
fuente
3
Si diferentes compañías tienen conjuntos de datos diferentes y de valores múltiples para cada cliente, entonces absolutamente necesita una tabla de enlace COMPANY_CUSTOMER. Todo lo demás le causará mucho dolor muy pronto.
Kilian Foth
¿Cómo ayudaría una tabla de enlace con los datos personalizados? las columnas todavía tendrán que ser diferentes
noobcser
1
Debe representar el hecho de que "la contraseña de Kilian para IKEA es 'gatito'" con una tupla como "EMPRESA: IKEA, CLIENTE: Kilian, ATRIBUTO: contraseña, VALOR: gatito". Cualquier cosa más simple no hará el trabajo.
Kilian Foth
3
Un esquema es una cosa fija, por definición; no puede configurar uno si no sabe cuáles son los campos que necesita. Eche un vistazo a Entity-Attribute-Value para problemas unidireccionales como este tienden a resolverse en una base de datos relacional.
Mason Wheeler

Respuestas:

13

Por favor considere esto como una alternativa. Los dos ejemplos anteriores requerirán que realice cambios en el esquema a medida que crece el alcance de la aplicación, además de que la solución "custom_column" es difícil de ampliar y mantener. Eventualmente terminarás con Custom_510 y luego imagina lo horrible que será trabajar con esta tabla.

Primero usemos su esquema de Empresas.

[Companies] ComnpanyId, COMPANY_NAME, CREATED_ON

A continuación, también usaremos su esquema de Usuarios para los atributos requeridos de nivel superior que serán utilizados / compartidos por todas las compañías.

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

A continuación, creamos una tabla donde definiremos nuestros atributos dinámicos que son específicos de los atributos de usuario personalizados de cada empresa. Entonces, aquí un valor de ejemplo de la columna Attribute sería "LikeMusic":

[UserAttributeDefinition] UserAttributeDefinitionId, CompanyId, Attribute

A continuación, definimos una tabla de atributos de usuario que contendrá los valores de los atributos del usuario

[UserAttributes] UserAttributeDefinitionId, UserId, Value

Esto se puede modificar de muchas maneras para mejorar el rendimiento. Puede usar varias tablas para UserAttributes haciendo que cada una sea específica para el tipo de datos que se almacena en Value o simplemente dejarla como VarChar y trabajar con ella como un almacén de valores clave.

También es posible que desee mover CompanyId fuera de la tabla UserAttributeDefiniton a una tabla de referencia cruzada para futuras pruebas.

P. Roe
fuente
gracias, pensé en ese enfoque, por favor vea editar. 2 problemas: 1) Los datos crecen como filas, lo que significa que para obtener una imagen completa de un usuario, tendrá que hacer muchas uniones. 2) "valor" siempre se almacenará como VARCHAR para que sea genérico, incluso si el valor es realmente int o booleano, etc.
noobcser
1
Si usa int / bigint para las identidades de la tabla y se une a ellas, no tendrá problemas de rendimiento hasta que se encuentre en un número extremo de filas. Ahora, si comienza a buscar en función de los valores de los atributos, esto podría presentar un problema si comienza a obtener una gran cantidad de registros. En este caso, trabajaría con un DBA para determinar si hay índices que podrían crearse o tal vez una vista indizada que pueda acelerar este tipo de búsquedas. He usado un esquema similar y tiene 100 millones de registros al año sin problemas de rendimiento, por lo que el diseño base funciona bastante bien IMO
P. Roe
Si se necesitan informes, filtros, consultas y diferentes atributos pueden pertenecer a diferentes conjuntos de datos. ¿Sería este enfoque mejor que NoSQL? Estoy tratando de entender la diferencia de rendimiento. Situación similar que solo el usuario puede definir informes que contienen campos definidos por el usuario.
kos
En el enfoque anterior, ¿cómo implementamos la cosa de búsqueda, como diff. las empresas desean buscar en sus campos, incluidos los campos de los usuarios también. ¿Cuál es el enfoque correcto para proporcionar una búsqueda escalable además de esto
Techagrammer
Puede buscarlo normalmente con muchas combinaciones. Puede usar un script ETL para extraer los datos que desea buscar y colocarlos en una estructura más desnormalizada. Por último, puede intentar utilizar vistas indexadas como método de búsqueda. Personalmente, recomiendo el método ETL para generar estructuras desnormalizadas que sean fáciles de buscar.
P. Roe
7

Use una base de datos NoSQL. Habría documentos de la empresa y del usuario. Los usuarios tendrían parte de su esquema creado dinámicamente en función de una plantilla de usuario (texto para indicar campos / tipos para esa empresa).

\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

Así es como podría verse en algo como Firebase.com . Tendría que aprender cómo hacerlo en el que elija.

JeffO
fuente
esto es lo que estoy pensando o quizás las columnas JSON. ¿Cómo es el rendimiento de las consultas y los informes de filtrado en comparación con la solución propuesta por PRoe?
kos
1
Cada vez que comprima datos en json o xml y luego los arroje a una columna, será terriblemente lento para buscar. Si necesita buscar los datos presentados en mi respuesta anterior, le recomendaría usar vistas indizadas para recuperar los datos. Si esa solución no es ideal, recomendaría usar ETL para copiar los datos en una estructura que se pueda buscar e informar fácilmente.
P. Roe
En el enfoque anterior, ¿cómo implementamos la cosa de búsqueda, como diff. las empresas desean buscar en sus campos, incluidos los campos de los usuarios también. ¿Cuál es el enfoque correcto para proporcionar una búsqueda escalable además de esto
Techagrammer
En las bases de datos nosql, es posible que tenga datos redundantes, pero está estructurado de manera que se pueda buscar. El que se muestra arriba es por identificador único. Otro podría ser \ Empresa \ Nombre. Es similar a tener múltiples índices.
JeffO
3

Si va a encontrarse con frecuencia con solicitudes de campo personalizadas, en realidad lo modelaría de manera bastante similar a la base de datos. Cree una tabla que contenga los metadatos sobre cada campo personalizado, CompanyCustomField (a quién pertenece, el tipo de datos, etc.) y otra tabla CompanyCustomFieldValues ​​que contiene CustomerId, FieldId y el valor. Si está utilizando algo como Microsoft Sql Server, la columna de valor sería un tipo de datos sql_variant.

Por supuesto, esto no es fácil, ya que necesitará una interfaz que permita a los administradores definir campos personalizados para cada cliente, y otra interfaz que realmente use estos metadatos para crear una interfaz de usuario para recopilar los valores de los campos. Y si tiene otros requisitos, como la agrupación de campos o la necesidad de hacer un tipo de campo de lista de selección, deberá incluir eso con más metadatos / otras tablas (por ejemplo, CompanyCustomFieldPickListOptions).

Esto no es trivial, pero tiene la ventaja de no requerir cambios en la base de datos / cambios de código para cada nuevo campo personalizado. También deberá codificarse cualquier otra característica de los campos personalizados (por ejemplo, si desea regex validar un valor de cadena, o solo permitir fechas entre ciertos rangos, o si necesita habilitar un campo personalizado basado en otro valor de campo personalizado )

Andy
fuente
gracias, pensé en ese enfoque, por favor vea editar. 2 problemas: 1) Los datos crecen como filas, lo que significa que para obtener una imagen completa de un usuario, tendrá que hacer muchas uniones. 2) "valor" siempre se almacenará como VARCHAR para que sea genérico, incluso si el valor es realmente int o booleano, etc.
noobcser
1
@noobcser Los datos que crecen como filas realmente no importan, después de que todas las bases de datos se diseñan alrededor de filas y uniones. En cualquier caso, lo más probable es que use expresiones de tabla comunes para esto, que son bastante buenas en este tipo de cosas. No estoy seguro de si se perdió la parte donde dije que puede usar sql_variant como el tipo de datos para la columna de valor, que almacena el valor como el tipo que ingrese. Mientras estoy nombrando los nombres de las características del servidor MS SQL, espero que otros DBMS maduros tengan características similares.
Andy
1
@noobcser FYI En realidad, he encontrado estos requisitos con bastante frecuencia en mi carrera y tengo experiencia con cada una de las soluciones propuestas, por lo que sugiero la que mejor funcionó en mi experiencia. El uso de tipos de datos xml para este tipo de cosas es en parte por qué odio que MS agregue xml como tipo de datos nativo.
Andy
1

Una alternativa a las otras respuestas es tener una tabla llamada profile_attrib, o similar, para que su aplicación administre completamente el esquema.

A medida que se agregan atributos personalizados ALTER TABLE profile_attrib ADD COLUMN like_movie TINYINT(1), usted puede prohibir eliminarlos. Esto minimizaría su unión, al tiempo que proporciona flexibilidad.

Supongo que la compensación es que la aplicación ahora necesita alterar los privilegios de la tabla para la base de datos, y debe ser inteligente para desinfectar los nombres de las columnas.

Chris Seufert
fuente
La expresión regular [^\w-]+debería hacerlo bastante bien, sin permitir nada que no 0-9A-Za-z_-sea ​​así, pero sí, la desinfección es imprescindible aquí para protegerse contra la malicia o la estupidez.
Regular Joe
0

Su pregunta tiene muchas soluciones potenciales. Una solución es almacenar los atributos adicionales como XML. El XML se puede almacenar como texto o si está utilizando una base de datos que admite tipos XML como XML (SQL Server). El almacenamiento como texto limita su capacidad de consulta (como buscar en un atributo personalizado), pero si el almacenamiento y la recuperación es todo lo que necesita, entonces es una buena solución. Si necesita consultar, almacenar el XML como un tipo XML sería una mejor opción (aunque esto es más específico del proveedor).

Esto le dará a uno la capacidad de almacenar cualquier número de atributos para un cliente con solo agregar una columna de adición en la tabla de clientes. Se podrían almacenar los atributos como hashset o diccionario, se perderá la seguridad de los tipos, ya que todo será una cadena para empezar, pero si se aplica una cadena de formato estándar para fechas, números, booleanos, todo saldrá bien.

Para más información:

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

La respuesta de @ WalterMitty también es válida, aunque si uno tiene muchos clientes con diferentes atributos, podría terminar con muchas tablas si sigue el modelo de herencia. Depende de cuántos atributos personalizados se compartan entre los clientes.

Jon Raynor
fuente
Esto también puede funcionar, pero creo que se vuelve limitado una vez que realmente necesita hacer algo contra los datos almacenados en el campo XML / JSON.
Andy
@Andy: es cierto, hay otra capa. Consultar DB y analizar XML en lugar de solo consultar DB. No sé si lo llamaría limitante, solo más engorroso. Pero, sería algo a considerar si los atributos personalizados se usaran ampliamente.
Jon Raynor
En T-SQL es posible definir el contenido en la columna XML / JSON en un espacio de nombres y consultar elementos en los datos personalizados. No es difícil
Stephen York
-1

Debería normalizar su base de datos de modo que tenga 3 tablas diferentes para cada tipo diferente de perfil de empresa. Usando su ejemplo, tendría tablas con columnas:

USER_ID, LIKE_MOVIE, LIKE_MUSIC

USER_ID, FAVORITE_CUISINE

USER_ID, OWN_DOG, DOG_COUNT

Este enfoque supone que conocerá de antemano la forma de la información que una empresa desea almacenar y que no cambiará con frecuencia. Si se desconoce la forma de los datos en el momento del diseño, probablemente sería mejor usar ese campo JSON o una base de datos nosql.

mortalapeman
fuente
-1

Por una razón u otra, las bases de datos son el único campo en el que el efecto de plataforma interna se muestra con mayor frecuencia. Este es solo otro caso de la aparición de antipatrón.

En este caso, estás tratando de luchar contra la solución natural y correcta. Los usuarios de la empresa A no son usuarios de la empresa B, y deben tener sus propias tablas para sus propios campos.

El proveedor de su base de datos no le cobra por la tabla, y no necesita el doble del espacio en disco para el doble de las tablas (de hecho, tener dos tablas es más eficiente porque no almacena los atributos de A para los usuarios de B. Incluso almacenando solo NULL ocupa espacio)

Por supuesto, si hay suficientes campos comunes, puede factorizarlos en una tabla de usuarios compartida y tener una clave externa en cada una de las tablas de usuarios específicas de la compañía. Esta es una estructura tan simple que ningún optimizador de consultas de bases de datos lucha con ella. Cualquier UNIÓN necesaria es trivial.

MSalters
fuente
3
Y si tiene miles de clientes, una tabla por cada uno puede volverse inservible rápidamente, sin mencionar que necesitará un código personalizado para los campos personalizados de cada cliente.
Andy
@Andy: ¿Adivina qué? ¡La situación será aún más insostenible si combina mil esquemas diferentes en una sola tabla! Y sí, probablemente necesite un código personalizado para los campos personalizados. Nuevamente, eso es más simple, no más difícil, si cada cliente tiene una mesa limpia y separada. Tratar de elegir los campos de la compañía X de otros miles es un desastre sangriento.
MSalters
¿Te refieres a mi respuesta o a la idea de los OP de agregar todas las columnas adicionales a la tabla del cliente?
Andy
2
El objetivo aquí es encontrar una solución sostenible y escalable. Crear una tabla por cliente es definitivamente lo contrario de eso. Cada vez que se incorpora a un nuevo cliente, no es realista: ejecutar un script de creación de tabla, actualizar su código (objetos de entidad) y volver a implementarlo.
tsOverflow
Toda esta idea de usar tablas compartidas para todos los clientes es en sí misma una discusión de arquitectura SaaS separada, y hay algunas buenas razones para mantener a los clientes en diferentes tablas (o incluso en diferentes bases de datos, lo que permite la copia de seguridad / restauración por cliente). En este escenario, crear columnas cusotm en la tabla principal es obvio. He votado a favor y me pregunto por qué la gente rechaza esto solo porque no les gusta este enfoque. El efecto de plataforma interna es una realidad: al usar un modelo de EVA, su consulta será más difícil, ahorrará más, integridad más difícil, etc.
drizin
-1

Mi solución asume que llamaría a esta consulta desde un programa y que debería poder realizar el procesamiento posterior. Puede tener las siguientes columnas:

ID, COMPANY_ID, FIRST_NAME, LAST_NAME, EMAIL, CUSTOM_VALUES

CUSTOM_VALUES será de tipo cadena que almacena la clave y el par de valores. la clave será el nombre de la columna y el valor será el valor de la columna, por ejemplo

LIKE_MOVIE;yes;LIKE_MUSIC;no;FAV_CUISINE;rice

en estos CUSTOM_VALUES solo guardará la información que exista. Cuando consulta desde el programa, puede dividir esta cadena y usarla.

He estado usando esta lógica y funciona bien, es solo que tendrás que aplicar la lógica de filtrado en el código y no en la consulta.

techExplorer
fuente