Meus requisitos são:
- Precisa ser capaz de adicionar dinamicamente campos definidos pelo usuário de qualquer tipo de dados
- Precisa ser capaz de consultar UDFs rapidamente
- Precisa ser capaz de fazer cálculos em UDFs com base no tipo de dados
- Precisa ser capaz de classificar UDFs com base no tipo de dados
Outra informação:
- Estou procurando desempenho principalmente
- Existem alguns milhões de registros mestre que podem ter dados UDF anexados
- Quando eu verifiquei pela última vez, havia mais de 50mil de registros UDF em nosso banco de dados atual
- Na maioria das vezes, uma UDF é anexada apenas a alguns milhares de registros mestre, nem todos eles
- UDFs não são unidos ou usados como chaves. São apenas dados usados para consultas ou relatórios
Opções:
Crie uma tabela grande com StringValue1, StringValue2 ... IntValue1, IntValue2, ... etc. Eu odeio essa idéia, mas a considerarei se alguém puder me dizer que é melhor do que outras idéias e por quê.
Crie uma tabela dinâmica que adicione uma nova coluna sob demanda, conforme necessário. Também não gosto dessa ideia, pois sinto que o desempenho seria lento, a menos que você indexasse todas as colunas.
Crie uma única tabela contendo UDFName, UDFDataType e Value. Quando um novo UDF é adicionado, gere uma View que extraia apenas esses dados e os analise em qualquer tipo especificado. Os itens que não atendem aos critérios de análise retornam NULL.
Crie várias tabelas UDF, uma por tipo de dados. Portanto, teríamos tabelas para UDFStrings, UDFDates, etc. Provavelmente, faria o mesmo que o número 2 e geraria automaticamente uma visualização a qualquer momento em que um novo campo fosse adicionado
DataTypes XML? Eu não trabalhei com isso antes, mas já os vi mencionados. Não tenho certeza se eles me dariam os resultados que eu quero, especialmente com o desempenho.
Algo mais?
Respostas:
Se o desempenho é a principal preocupação, eu recomendaria o número 6 ... uma tabela por UDF (realmente, essa é uma variante do número 2). Esta resposta é especificamente adaptada a esta situação e à descrição dos padrões de acesso e distribuição de dados descritos.
Prós:
Como você indica que alguns UDFs têm valores para uma pequena parte do conjunto de dados geral, uma tabela separada forneceria o melhor desempenho, pois essa tabela terá apenas o tamanho necessário para suportar o UDF. O mesmo vale para os índices relacionados.
Você também obtém um aumento de velocidade limitando a quantidade de dados que precisam ser processados para agregações ou outras transformações. Dividir os dados em várias tabelas permite executar algumas análises estatísticas agregadas e outras sobre os dados UDF e associar esse resultado à tabela mestre por meio de chave estrangeira para obter os atributos não agregados.
Você pode usar nomes de tabela / coluna que refletem quais são realmente os dados.
Você tem controle completo para usar tipos de dados, verificar restrições, valores padrão etc. para definir os domínios de dados. Não subestime o impacto no desempenho resultante da conversão instantânea de tipos de dados. Essas restrições também ajudam os otimizadores de consulta do RDBMS a desenvolver planos mais eficazes.
Se você precisar usar chaves estrangeiras, a integridade referencial declarativa interna raramente é executada pela imposição de restrições no nível do aplicativo ou baseada em acionador.
Contras:
Isso pode criar muitas tabelas. A imposição da separação de esquemas e / ou de uma convenção de nomenclatura aliviaria isso.
É necessário mais código de aplicativo para operar a definição e o gerenciamento de UDF. Espero que ainda seja necessário menos código do que para as opções originais 1, 3 e 4.
Outras considerações:
Se houver algo sobre a natureza dos dados que faça sentido para as UDFs serem agrupadas, isso deve ser incentivado. Dessa forma, esses elementos de dados podem ser combinados em uma única tabela. Por exemplo, digamos que você tenha UDFs para cores, tamanhos e custos. A tendência nos dados é que a maioria das instâncias desses dados se parece com
ao invés de
Nesse caso, você não sofrerá uma penalidade de velocidade perceptível combinando as 3 colunas em 1 tabela, porque poucos valores seriam NULL e você evita criar mais 2 tabelas, o que significa 2 menos junções necessárias quando você precisa acessar todas as 3 colunas .
Se você atingir uma parede de desempenho de uma UDF que é altamente preenchida e usada com frequência, isso deve ser considerado para inclusão na tabela mestre.
O design da tabela lógica pode levá-lo a um determinado ponto, mas quando a contagem de registros se torna realmente grande, você também deve começar a analisar quais opções de particionamento de tabela são fornecidas pelo seu RDBMS de sua escolha.
fonte
Eu tenho escrito sobre este problema muito . A solução mais comum é o antipadrão de Entidade-Atributo-Valor, que é semelhante ao que você descreve em sua opção nº 3. Evite esse design como uma praga .
O que eu uso para esta solução quando preciso de campos personalizados verdadeiramente dinâmicos é armazená-los em um blob de XML, para que eu possa adicionar novos campos a qualquer momento. Mas, para agilizar, crie também tabelas adicionais para cada campo em que você precisa pesquisar ou classificar (você não cria uma tabela por campo - apenas uma tabela por campo pesquisável ). Isso às vezes é chamado de design de índice invertido.
Você pode ler um artigo interessante de 2009 sobre esta solução aqui: http://backchannel.org/blog/friendfeed-schemaless-mysql
Ou você pode usar um banco de dados orientado a documentos, onde é esperado que você tenha campos personalizados por documento. Eu escolheria Solr .
fonte
fieldname
outablename
está armazenando identificadores de metadados como cadeias de dados, e esse é o começo de muitos problemas. Veja também en.wikipedia.org/wiki/Inner-platform_effectEu provavelmente criaria uma tabela da seguinte estrutura:
Os tipos exatos de curso dependem de suas necessidades (e, é claro, dos dbms que você está usando). Você também pode usar o campo NumberValue (decimal) para int e booleanos. Você pode precisar de outros tipos também.
Você precisa de algum link para os registros mestre que possuem o valor. Provavelmente é mais fácil e rápido criar uma tabela de campos do usuário para cada tabela mestre e adicionar uma chave estrangeira simples. Dessa forma, você pode filtrar os registros mestre pelos campos do usuário com facilidade e rapidez.
Você pode querer ter algum tipo de informação de metadados. Então você acaba com o seguinte:
Tabela UdfMetaData
Table MasterUdfValues
Faça o que fizer, eu não mudaria a estrutura da tabela dinamicamente. É um pesadelo de manutenção. Eu também não usaria estruturas XML, elas são muito lentas.
fonte
Isso soa como um problema que pode ser melhor resolvido por uma solução não relacional, como MongoDB ou CouchDB.
Ambos permitem a expansão dinâmica do esquema e permitem manter a integridade da tupla que você procura.
Eu concordo com Bill Karwin, o modelo EAV não é uma abordagem de alto desempenho para você. O uso de pares nome-valor em um sistema relacional não é intrinsecamente ruim, mas só funciona bem quando o par nome-valor cria uma tupla completa de informações. Ao usá-lo obriga a reconstruir dinamicamente uma tabela em tempo de execução, todos os tipos de coisas começam a ficar difíceis. A consulta se torna um exercício de manutenção de pivô ou obriga a empurrar a reconstrução da tupla para a camada de objeto.
Você não pode determinar se um valor nulo ou ausente é uma entrada válida ou falta de entrada sem incorporar regras de esquema em sua camada de objeto.
Você perde a capacidade de gerenciar com eficiência seu esquema. Um varchar de 100 caracteres é o tipo certo para o campo "value"? 200 caracteres? Em vez disso, deveria ser nvarchar? Pode ser uma troca difícil e termina com você ter que colocar limites artificiais na natureza dinâmica do seu aparelho. Algo como "você pode ter apenas x campos definidos pelo usuário e cada um pode ter apenas y caracteres.
Com uma solução orientada a documentos, como MongoDB ou CouchDB, você mantém todos os atributos associados a um usuário em uma única tupla. Como as junções não são um problema, a vida é feliz, pois nenhuma dessas duas se dá bem com as junções, apesar do hype. Seus usuários podem definir quantos atributos quiserem (ou você permitirá) por períodos que não serão difíceis de gerenciar até atingir cerca de 4 MB.
Se você tiver dados que exijam integridade no nível do ACID, considere dividir a solução, com os dados de alta integridade que vivem em seu banco de dados relacional e os dados dinâmicos em um armazenamento não relacional.
fonte
Mesmo se você fornecer um usuário adicionando colunas personalizadas, não será necessariamente o caso de a consulta nessas colunas ter um bom desempenho. Há muitos aspectos que entram no design de consultas que permitem um bom desempenho, o mais importante deles é a especificação adequada sobre o que deve ser armazenado em primeiro lugar. Portanto, fundamentalmente, você deseja permitir que os usuários criem esquemas sem pensar nas especificações e conseguir derivar rapidamente informações desse esquema? Nesse caso, é improvável que qualquer solução desse tipo seja dimensionada bem, especialmente se você quiser permitir que o usuário faça análises numéricas nos dados.
Opção 1
Na IMO, essa abordagem fornece um esquema sem o conhecimento do significado do esquema, que é uma receita para um desastre e um pesadelo para os designers de relatórios. Ou seja, você deve ter os metadados para saber qual coluna armazena quais dados. Se esses metadados forem confusos, ele poderá potencializar seus dados. Além disso, facilita colocar os dados errados na coluna errada. ("O quê? String1 contém o nome de conventos? Eu pensei que eram as drogas favoritas de Chalie Sheen.")
Opção 3,4,5
Os requisitos 2, 3 e 4 da IMO eliminam qualquer variação de um EAV. Se você precisar consultar, classificar ou fazer cálculos com esses dados, um EAV é o sonho de Cthulhu e o pesadelo de sua equipe de desenvolvimento e do DBA. Os EAVs criarão um gargalo em termos de desempenho e não fornecerão a integridade dos dados necessários para obter rapidamente as informações desejadas. As consultas rapidamente se transformarão em nós górdio de crosstab.
Opção 2,6
Isso realmente deixa uma escolha: reunir especificações e criar o esquema.
Se o cliente deseja o melhor desempenho nos dados que deseja armazenar, ele precisa passar pelo processo de trabalhar com um desenvolvedor para entender suas necessidades, para que ele seja armazenado da maneira mais eficiente possível. Ele ainda pode ser armazenado em uma tabela separada do restante das tabelas com código que cria dinamicamente um formulário com base no esquema da tabela. Se você tiver um banco de dados que permita propriedades estendidas nas colunas, poderá usá-las para ajudar o construtor de formulários a usar rótulos, dicas de ferramentas etc. para que tudo o que seja necessário seja adicionar o esquema. De qualquer maneira, para criar e executar relatórios com eficiência, os dados precisam ser armazenados corretamente. Se os dados em questão tiverem muitos valores nulos, alguns bancos de dados poderão armazenar esse tipo de informação. Por exemplo,
Se esse fosse apenas um conjunto de dados nos quais nenhuma análise, filtragem ou classificação deveria ser feita, eu diria que alguma variação de um EAV pode ajudar. No entanto, considerando seus requisitos, a solução mais eficiente será obter as especificações adequadas, mesmo se você armazenar essas novas colunas em tabelas separadas e criar formulários dinamicamente a partir dessas tabelas.
Colunas esparsas
fonte
De acordo com minha pesquisa, várias tabelas baseadas no tipo de dados não ajudarão você no desempenho. Especialmente se você tiver dados em massa, como 20K ou 25K com mais de 50 UDFs. O desempenho foi o pior.
Você deve ir com uma única tabela com várias colunas, como:
fonte
Esta é uma situação problemática e nenhuma das soluções parece "correta". No entanto, a opção 1 é provavelmente a melhor, tanto em termos de simplicidade quanto em termos de desempenho.
Essa também é a solução usada em alguns aplicativos empresariais comerciais.
EDITAR
outra opção que está disponível agora, mas não existia (ou pelo menos não estava madura) quando a pergunta foi feita originalmente é usar os campos json no banco de dados.
muitos bancos de dados relacionais agora suportam campos baseados em json (que podem incluir uma lista dinâmica de subcampos) e permitem a consulta neles
postgress
mysql
fonte
Eu tive experiência ou 1, 3 e 4 e todos acabam confusos, com não ficando claro quais são os dados ou são realmente complicados com algum tipo de categorização simples para decompor os dados em tipos dinâmicos de registro.
Eu ficaria tentado a experimentar XML, você poderá aplicar esquemas contra o conteúdo do xml para verificar a digitação de dados etc., o que ajudará a manter conjuntos diferentes de dados UDF. Nas versões mais recentes do SQL Server, você pode indexar nos campos XML, o que deve ajudar no desempenho. (consulte http://blogs.technet.com/b/josebda/archive/2009/03/23/sql-server-2008-xml-indexing.aspx ) por exemplo
fonte
Se você estiver usando o SQL Server, não negligencie o tipo sqlvariant. É muito rápido e deve fazer o seu trabalho. Outros bancos de dados podem ter algo semelhante.
Os tipos de dados XML não são tão bons por razões de desempenho. Se você estiver fazendo cálculos no servidor, precisará constantemente desserializá-los.
A opção 1 parece ruim e parece grosseira, mas em termos de desempenho pode ser sua melhor aposta. Eu criei tabelas com colunas denominadas Field00-Field99 antes, porque você simplesmente não consegue superar o desempenho. Pode ser necessário considerar também o desempenho do INSERT; nesse caso, esse também é o caminho a ser seguido. Você sempre pode criar modos de exibição nesta tabela se quiser que ele fique bonito!
fonte
O SharePoint usa a opção 1 e tem um desempenho razoável.
fonte
Eu consegui isso com muito sucesso no passado, usando nenhuma dessas opções (opção 6? :)).
Eu crio um modelo para os usuários brincarem (armazenar como xml e expor por meio de uma ferramenta de modelagem personalizada) e a partir das tabelas e visualizações geradas pelo modelo para unir as tabelas base às tabelas de dados definidas pelo usuário. Portanto, cada tipo teria uma tabela base com dados principais e uma tabela de usuário com campos definidos pelo usuário.
Tome um documento como exemplo: campos típicos seriam nome, tipo, data, autor, etc. Isso seria exibido na tabela principal. Os usuários definiriam seus próprios tipos de documentos especiais com seus próprios campos, como contract_end_date, renewal_clause, blá blá blá. Para esse documento definido pelo usuário, haveria a tabela principal de documentos, a tabela xcontract, unida em uma chave primária comum (portanto, a chave primária xcontracts também é estrangeira na chave primária da tabela principal). Então eu geraria uma visualização para agrupar essas duas tabelas. O desempenho ao consultar foi rápido. regras de negócios adicionais também podem ser incorporadas às visualizações. Isso funcionou muito bem para mim.
fonte
Nosso banco de dados fornece um aplicativo SaaS (software de helpdesk) em que os usuários têm mais de 7k "campos personalizados". Utilizamos uma abordagem combinada:
(EntityID, FieldID, Value)
tabela para pesquisar os dadosentities
tabela, que contém todos os valores da entidade, usados para exibir os dados. (dessa forma, você não precisa de um milhão de JOIN para obter os valores dos valores).Você pode dividir ainda mais o número 1 para ter uma "tabela por tipo de dados", como sugere esta resposta , dessa maneira você pode até indexar suas UDFs.
PS Algumas palavras para defender a abordagem "Entidade-Atributo-Valor" que todo mundo continua contestando. Usamos o número 1 sem o número 2 por décadas e funcionou muito bem. Às vezes é uma decisão de negócios. Você tem tempo para reescrever seu aplicativo e redesenhar o banco de dados ou pode gastar alguns dólares em servidores em nuvem, que são realmente baratos hoje em dia? A propósito, quando estávamos usando a abordagem nº 1, nosso banco de dados possuía milhões de entidades, acessadas por centenas de milhares de usuários, e um servidor db de núcleo duplo de 16 GB estava indo muito bem
fonte
custom_fields
tabela que armazena valores como 1 =>last_concert_year
, 2 =>band
, 3 =>music
e, em seguida, umacustom_fields_values
tabela com valores 001, 1, 1976 002, 1, 1977 003, 2,Iron Maiden
003, 3 ,Metal
Espero que o exemplo faça sentido para você e desculpe pela formatação!bands
tabela com uma linha1,'Iron Maiden'
, em seguida,custom_fields
com linhas1,'concert_year' | 2,'music'
, em seguida,custom_fields_values
com filas1,1,'1977'|1,2,'metal'
Nos comentários, vi você dizendo que os campos UDF devem despejar dados importados que não são mapeados corretamente pelo usuário.
Talvez outra opção seja rastrear o número de UDFs feitas por cada usuário e forçá-los a reutilizar campos dizendo que eles podem usar 6 (ou algum outro limite igualmente aleatório) no máximo de campos personalizados.
Quando você se deparar com um problema de estruturação de banco de dados como esse, geralmente é melhor voltar ao design básico do aplicativo (sistema de importação no seu caso) e colocar mais algumas restrições nele.
Agora, o que eu faria é a opção 4 (EDIT) com a adição de um link para os usuários:
Agora, certifique-se de fazer visualizações para otimizar o desempenho e obter seus índices corretos. Esse nível de normalização diminui o tamanho do banco de dados, mas seu aplicativo é mais complexo.
fonte
Eu recomendaria o número 4, pois esse tipo de sistema foi usado no Magento, que é uma plataforma CMS de comércio eletrônico altamente credenciada. Use uma única tabela para definir seus campos personalizados usando as colunas fieldId e label . Em seguida, tenha tabelas separadas para cada tipo de dados e, dentro de cada uma dessas tabelas, tenha um índice que indexe por fieldId e as colunas de valor do tipo de dados . Em seguida, em suas consultas, use algo como:
Isso garantirá o melhor desempenho possível para tipos definidos pelo usuário, na minha opinião.
Na minha experiência, trabalhei em vários sites Magento que atendem a milhões de usuários por mês, hospeda milhares de produtos com atributos personalizados e o banco de dados lida com a carga de trabalho com facilidade, mesmo para geração de relatórios.
Para gerar relatórios, você pode usar
PIVOT
para converter os valores dos rótulos da tabela Fields em nomes de colunas e dinamizar os resultados da consulta de cada tabela de tipo de dados nessas colunas dinâmicas.fonte