Como você projetaria um banco de dados do usuário com campos personalizados


18

Esta questão é sobre como devo projetar um banco de dados, ele pode ser um banco de dados relacional / nosql, dependendo de qual será a melhor solução


Dado um requisito, você precisará criar um sistema que envolva um banco de dados para rastrear "Empresa" e "Usuário". Um único usuário sempre pertence apenas a uma empresa

  • Um usuário pode pertencer apenas a uma empresa
  • Uma empresa pode ter muitos usuários

O design da tabela "Empresa" é bastante direto. A empresa terá os seguintes atributos / colunas: (vamos simplificar)

ID, COMPANY_NAME, CREATED_ON

Primeiro cenário

Simples e direto, todos os usuários têm o mesmo atributo, portanto, isso pode ser feito facilmente no estilo relacional, tabela de usuários:

ID, COMPANY_ID, FIRST_NAME, LAST_NAME, EMAIL, CREATED_ON

Segundo cenário

O que acontece se diferentes empresas quiserem armazenar atributos de perfil diferentes para seus usuários. Cada empresa terá um conjunto definido de atributos que se aplicariam a todos os usuários dessa empresa.

Por exemplo:

  • A empresa A deseja armazenar: LIKE_MOVIE (booleano), LIKE_MUSIC (booleano)
  • A empresa B deseja armazenar: FAV_CUISINE (String)
  • A empresa C deseja armazenar: OWN_DOG (booleano), DOG_COUNT (int)

Abordagem 1

a maneira da força bruta é ter um esquema único para o usuário e permitir que eles tenham nulos quando não pertencem à empresa:

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

O que é meio desagradável, porque você terá muitos NULLS e linhas de usuário que têm colunas que são irrelevantes para eles (ou seja, todos os usuários pertencentes à empresa A têm valores NULL para FAV_CUISINE, OWN_DOG, DOG_COUNT)

Abordagem 2

uma segunda abordagem, é ter "campo de forma livre":

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

O que seria desagradável por si só, já que você não tem idéia do que são campos personalizados, o tipo de dados não refletirá os valores armazenados (por exemplo, armazenaremos o valor int como VARCHAR).

Abordagem 3

Eu examinei o campo JSON do PostgreSQL; nesse caso, você terá:

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

Nesse caso, como você seria capaz de aplicar esquemas diferentes a um usuário? Um usuário da empresa A terá um esquema parecido com

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

Enquanto um usuário com a empresa C terá um esquema diferente:

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

Como devo resolver esse problema? Como posso projetar o banco de dados corretamente para permitir esse esquema flexível para um único "objeto" (Usuário) com base no relacionamento que eles têm (Empresa)?

solução relacional? solução nosql?


Editar: Eu também pensei em uma tabela "CUSTOM_PROFILE" que essencialmente armazena atributos do usuário em linhas e não em colunas.

Existem 2 problemas com esta abordagem:

1) Os dados crescem por usuário, à medida que as linhas aumentam, em vez de colunas - e isso significa que, para obter uma imagem completa do usuário, muitas associações precisam ser feitas, várias associações à tabela "perfil personalizado" nos diferentes atributos personalizados

2) O valor dos dados é sempre armazenado como VARCHAR para ser genérico, mesmo se sabemos que os dados devem ser inteiros ou booleanos, etc.


3
Se diferentes empresas tiverem conjuntos de dados diferentes e com vários valores em cada cliente, você precisará absolutamente de uma tabela de vinculação COMPANY_CUSTOMER. Tudo o resto causará muita dor em breve.
Kilian Foth

Como uma tabela de vinculação ajudaria com os dados personalizados? as colunas ainda terá de ser diferente
noobcser

1
Você deve representar o fato "A senha de Kilian para a IKEA é 'gatinho'" com uma tupla como "EMPRESA: IKEA, CLIENTE: Kilian, ATRIBUTO: senha, VALOR: gatinho". Qualquer coisa mais simples não fará o trabalho.
Kilian Foth

3
Um esquema é uma coisa fixa, por definição; você não pode configurar um se não souber quais são os campos necessários. Dê uma olhada no Entity-Attribute-Value para uma maneira que problemas como esse tendem a ser resolvidos em um banco de dados relacional.
Mason Wheeler

Respostas:


13

Por favor, considere isso como uma alternativa. Os dois exemplos anteriores exigirão que você faça alterações no esquema à medida que o escopo do aplicativo aumenta. Além disso, é difícil estender e manter a solução "custom_column". Eventualmente, você terminará com Custom_510 e depois imaginará como essa tabela será péssima.

Primeiro, vamos usar o esquema da sua empresa.

[Companies] ComnpanyId, COMPANY_NAME, CREATED_ON

Em seguida, também usaremos o esquema de Usuários para os atributos necessários de nível superior que serão usados ​​/ compartilhados por todas as empresas.

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

Em seguida, criamos uma tabela na qual definiremos nossos atributos dinâmicos específicos aos atributos de usuário personalizados de cada empresa. Portanto, aqui, um valor de exemplo da coluna Attribute seria "LikeMusic":

[UserAttributeDefinition] UserAttributeDefinitionId, CompanyId, Attribute

Em seguida, definimos uma tabela UserAttributes que manterá os valores dos atributos do usuário

[UserAttributes] UserAttributeDefinitionId, UserId, Value

Isso pode ser modificado de várias maneiras para melhorar o desempenho. Você pode usar várias tabelas para UserAttributes, tornando cada uma específica para o tipo de dados que está sendo armazenado em Value ou apenas deixá-lo como um VarChar e trabalhar com ele como um armazenamento de valor-chave.

Você também pode querer mover CompanyId da tabela UserAttributeDefiniton e entrar em uma tabela de referência cruzada para provas futuras.


obrigado - eu pensei sobre essa abordagem - por favor, veja editar. 2 problemas: 1) Os dados crescem como linhas, o que significa que para obter uma imagem completa de um usuário, você precisará fazer muitas junções. 2) "valor" será sempre armazenado como VARCHAR ser genérico, mesmo se o valor é realmente int ou boolean etc
noobcser

1
Se você usa int / bigint para as identidades da tabela e se une àquelas, não terá problemas de desempenho até estar em um número extremo de linhas. Agora, se você começar a pesquisar com base nos valores de atributo, isso poderá representar um problema se você começar a obter um grande número de registros. Nesse caso, eu trabalharia com um DBA para determinar se existem índices que poderiam ser criados ou talvez uma exibição indexada que pudesse acelerar esse tipo de pesquisa. Eu usei um esquema semelhante e leva em 100 milhões de registros de um ano sem problemas de desempenho que seja assim que o projeto de base funciona muito bem IMO
P. Roe

Se relatórios, filtros, consultas forem necessários e atributos diferentes podem pertencer a diferentes conjuntos de dados. Essa abordagem seria melhor que o NoSQL? Estou tentando entender a diferença de desempenho. Situação semelhante, somente o usuário pode definir relatórios que contenham campos definidos pelo usuário.
kos

Na abordagem acima, como implementamos a coisa de pesquisa, como diff. as empresas desejam pesquisar em seus campos, incluindo também campos de usuários. Qual é a abordagem correta para fornecer busca escalável no topo desta
techagrammer

Você pode procurá-lo normalmente com muitas junções. Você pode usar um script ETL para extrair os dados que deseja pesquisar e colocá-los em uma estrutura mais desnormalizada. Por fim, você pode tentar utilizar as visualizações indexadas como um método para pesquisar. Pessoalmente, recomendo o método ETL para gerar estruturas desnormalizadas que são fáceis de pesquisar.
P. Roe

7

Use um banco de dados NoSQL. Haveria documentos da empresa e do usuário. Os usuários teriam parte de seu esquema criado dinamicamente com base em um modelo de usuário (texto para indicar campos / tipos para essa 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

É assim que pode parecer em Firebase.com. Você teria que aprender como fazê-lo em qualquer que você escolher.


é isso que estou pensando ou talvez colunas JSON. Como é o desempenho na consulta, filtragem de relatórios em comparação com a solução proposta pelo PRoe.
kos

1
Sempre que você compactar dados em json ou xml e depois jogá-los em uma coluna, será muito lento para pesquisar. Se você precisar pesquisar os dados apresentados na minha resposta acima, aconselho o uso de visualizações indexadas para recuperar os dados. Se essa solução não for ideal, recomendo usar o ETL para copiar os dados em uma estrutura que possa ser facilmente pesquisada e relatada.
P. Roe

Na abordagem acima, como implementamos a coisa de pesquisa, como diff. as empresas desejam pesquisar em seus campos, incluindo também campos de usuários. Qual é a abordagem correta para fornecer busca escalável no topo desta
techagrammer

Nos bancos de dados nosql, você pode ter dados redundantes, mas eles são estruturados de maneira a serem pesquisáveis. O mostrado acima é por identificador único. Outro pode ser \ Company \ Name. É semelhante a ter vários índices.
Jeffo

3

Se você costuma executar solicitações de campo personalizadas, na verdade, eu o modelo de maneira bastante semelhante ao banco de dados. Crie uma tabela que contém os metadados sobre cada campo personalizado, CompanyCustomField (a quem pertence, o tipo de dados etc.) e outra tabela CompanyCustomFieldValues ​​que contém o CustomerId, FieldId e o valor. Se você estiver usando algo como o Microsoft Sql Server, a coluna de valor será um tipo de dados sql_variant.

Obviamente, isso não é fácil, pois você precisará de uma interface que permita aos administradores definir campos personalizados para cada cliente e outra interface que realmente use esses metadados para criar uma interface do usuário para coletar os valores dos campos. E se você tiver outros requisitos, como o agrupamento de campos ou a necessidade de fazer um tipo de campo da lista de opções, precisará acomodar isso com mais metadados / outras tabelas (por exemplo, CompanyCustomFieldPickListOptions).

Isso não é trivial, mas tem a vantagem de não exigir alterações no banco de dados / alterações de código para cada novo campo personalizado. Quaisquer outros recursos de campos personalizados também precisarão ser codificados (por exemplo, se você deseja regexar validar um valor de sequência, ou permitir apenas datas entre determinados intervalos, ou se você precisa ativar um campo personalizado com base em outro valor de campo personalizado )


obrigado - eu pensei sobre essa abordagem - por favor, veja editar. 2 problemas: 1) Os dados crescem como linhas, o que significa que para obter uma imagem completa de um usuário, você precisará fazer muitas junções. 2) "valor" será sempre armazenado como VARCHAR ser genérico, mesmo se o valor é realmente int ou boolean etc
noobcser

1
@noobcser Os dados que crescem como linhas realmente não importam, depois que todos os bancos de dados estão projetando em torno de linhas e junções. De qualquer forma, é mais provável que você use expressões de tabela comum para isso, que são muito boas nesse tipo de coisa. Não tenho certeza se você perdeu a parte em que eu disse que pode usar sql_variant como o tipo de dados da coluna value, que armazena o valor como qualquer tipo que você cole nela. Enquanto eu estiver nomeando nomes de recursos do servidor MS SQL, eu esperaria que outros DBMS maduros tivessem recursos semelhantes.
Andy

1
@noobcser FYI Na verdade, encontrei esses requisitos com bastante frequência em minha carreira e tenho experiência com cada uma das soluções propostas, então estou sugerindo a que melhor funcionou em minha experiência. O uso de tipos de dados xml para esse tipo de coisa é parcialmente o motivo pelo qual odeio que a Microsoft adicione xml como um tipo de dados nativo.
214 Andy Andy

1

Uma alternativa para as outras respostas é ter uma tabela chamada profile_attrib, ou similar, para que o esquema seja completamente gerenciado pelo seu aplicativo.

À medida que os atributos personalizados são adicionados ALTER TABLE profile_attrib ADD COLUMN like_movie TINYINT(1), você pode proibir a exclusão deles. Isso minimizaria sua associação, ao mesmo tempo em que proporcionaria flexibilidade.

Eu acho que a troca de bits é que o aplicativo agora precisa alterar os privilégios da tabela no banco de dados, e você deve ser inteligente ao limpar os nomes das colunas.


A expressão regular [^\w-]+deve muito bem fazê-lo, não permitindo nada que não seja - 0-9A-Za-z_-mas sim, higienizar é uma obrigação aqui para se proteger contra malícia ou estupidez.
Regular Joe

0

Sua pergunta tem muitas soluções em potencial. Uma solução é armazenar os atributos adicionais como XML. O XML pode ser armazenado como texto ou se você estiver usando um banco de dados que suporte tipos XML como XML (SQL Server). Armazenar como texto limita sua capacidade de consulta (como pesquisar em um atributo personalizado), mas se armazenar e recuperar é tudo o que você precisa, é uma boa solução. Se for necessário consultar, armazenar o XML como um tipo XML seria uma opção melhor (embora isso seja mais específico do fornecedor).

Isso permitirá armazenar qualquer número de atributos em um cliente, basta adicionar uma coluna de adição na tabela do cliente. Pode-se armazenar os atributos como um hashset ou dicionário, perder-se-á a segurança do tipo, pois tudo será uma string para começar, mas se for aplicada uma string de formato padrão para datas, números, booleanos, tudo funcionará bem.

Para maiores informações:

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

A resposta de @ WalterMitty também é válida, embora, se houver muitos clientes com atributos diferentes, você possa terminar com muitas tabelas se seguir o modelo de herança. Depende de quantos atributos personalizados são compartilhados entre os clientes.


Isso também pode funcionar, mas acho que fica limitado quando você realmente precisa fazer alguma coisa com relação aos dados armazenados no campo XML / JSON.
214 Andy Andy

@ Andy - Verdade, há uma outra camada. Consulte o banco de dados e analise o XML em vez de apenas consultar o banco de dados. Eu não sei se eu chamaria isso de limitador, apenas mais complicado. Mas seria algo a considerar se os atributos personalizados fossem usados ​​extensivamente.
Jon Raynor

No T-SQL, é possível definir o conteúdo da coluna XML / JSON em um namespace e consultar elementos nos dados customizados. Não é difícil
Stephen York

-1

Você deve normalizar seu banco de dados para ter 3 tabelas diferentes para cada tipo diferente de perfil da empresa. Usando seu exemplo, você teria tabelas com colunas:

USER_ID, LIKE_MOVIE, LIKE_MUSIC

USER_ID, FAVORITE_CUISINE

USER_ID, OWN_DOG, DOG_COUNT

Essa abordagem pressupõe que você conhecerá o formato das informações que uma empresa deseja armazenar antes e que não mudará frequentemente. Se a forma dos dados for desconhecida no momento do design, provavelmente seria melhor usar esse campo JSON ou um banco de dados nosql.


-1

Por um motivo ou outro, os bancos de dados são o único campo em que o efeito da plataforma interna aparece com mais frequência. Este é apenas mais um caso do anti-padrão surgindo.

Nesse caso, você está tentando combater a solução natural e correta. Os usuários da empresa A não são usuários da empresa B e devem ter suas próprias tabelas para seus próprios campos.

O fornecedor do banco de dados não cobra pela tabela e você não precisa do dobro do espaço em disco para o dobro das tabelas (na verdade, ter duas tabelas é mais eficiente porque você não armazena os atributos de A para os usuários de B. Até mesmo armazenando apenas NULLs ocupa espaço).

Obviamente, se houver campos comuns suficientes, você poderá fatorá-los em uma tabela Usuários compartilhada e ter uma chave estrangeira em cada uma das tabelas de usuários específicas da empresa. Essa é uma estrutura tão simples que nenhum otimizador de consulta de banco de dados luta com ela. Qualquer JOIN necessário é trivial.


3
E se você tiver milhares de clientes, uma tabela para cada um poderá se tornar rapidamente insustentável, sem mencionar que você precisará de um código personalizado para os campos personalizados de cada cliente.
214 Andy Andy

@ Andy: Adivinha o quê? A situação será ainda mais insustentável se você misturar mil esquemas diferentes em uma única tabela! E sim, você provavelmente precisa de um código personalizado para campos personalizados. Novamente, isso é mais simples, não mais difícil, se cada cliente tiver uma tabela limpa e separada. Tentar escolher os campos da empresa X de milhares de outros é uma bagunça sangrenta.
MSalters

Você está se referindo à minha resposta ou à idéia dos OPs de colocar todas as colunas extras na mesa do cliente?
Andy

2
O objetivo aqui é encontrar uma solução sustentável e escalável. Criar uma tabela por cliente é definitivamente o oposto disso. Sempre que você entra em um novo cliente, não é realista: executar um script de criação de tabela, atualizar seu código (objetos de entidade) e reimplementar.
tsOverflow

Toda essa idéia de usar tabelas compartilhadas para todos os clientes é uma discussão separada da arquitetura SaaS, e existem alguns bons motivos para manter os clientes em tabelas diferentes (ou mesmo em bancos de dados diferentes, permitindo backup / restauração e dimensionamento por cliente). Nesse cenário, a criação de colunas cusotm na tabela principal é um acéfalo. Votei de forma positiva e me pergunto por que as pessoas votam negativamente nisso apenas porque não gostam dessa abordagem. O efeito plataforma interior é uma realidade: usando um modelo EVA sua consulta será mais difícil, economizando mais difícil, a integridade mais difícil, etc.
Drizin

-1

Minha solução pressupõe que você chamaria essa consulta de um programa e deveria poder executar o pós-processamento. Você pode ter as seguintes colunas:

ID, COMPANY_ID, FIRST_NAME, LAST_NAME, EMAIL, CUSTOM_VALUES

CUSTOM_VALUES será do tipo string armazenando chave e par de valores. chave será o nome da coluna e o valor será o valor da coluna, por exemplo

LIKE_MOVIE;yes;LIKE_MUSIC;no;FAV_CUISINE;rice

neste CUSTOM_VALUES, você salvará apenas as informações existentes. Quando você consulta um programa, pode dividir essa sequência e usá-la.

Eu tenho usado essa lógica e ela funciona bem, basta que você aplique a lógica de filtragem no código e não na consulta.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.