Como projetar uma tabela de produtos para muitos tipos de produtos em que cada produto possui muitos parâmetros

140

Não tenho muita experiência em design de tabelas. Meu objetivo é criar uma ou mais tabelas de produtos que atendam aos requisitos abaixo:

  • Suporte para vários tipos de produtos (TV, Telefone, PC, ...). Cada tipo de produto possui um conjunto diferente de parâmetros, como:

    • O telefone terá cor, tamanho, peso, sistema operacional ...

    • PC terá CPU, HDD, RAM ...

  • O conjunto de parâmetros deve ser dinâmico. Você pode adicionar ou editar qualquer parâmetro que desejar.

Como posso atender a esses requisitos sem uma tabela separada para cada tipo de produto?

Coração de pedra
fonte

Respostas:

233

Você tem pelo menos essas cinco opções para modelar a hierarquia de tipos que você descreve:

  • Herança de tabela única : uma tabela para todos os tipos de produtos, com colunas suficientes para armazenar todos os atributos de todos os tipos. Isso significa muitas colunas, a maioria das quais é NULL em qualquer linha.

  • Herança de tabela de classe : uma tabela para produtos, armazenando atributos comuns a todos os tipos de produtos. Em seguida, uma tabela por tipo de produto, armazenando atributos específicos para esse tipo de produto.

  • Herança de tabela concreta : nenhuma tabela para atributos comuns de produtos. Em vez disso, uma tabela por tipo de produto, armazenando atributos comuns do produto e atributos específicos do produto.

  • LOB serializado : uma tabela para produtos, armazenando atributos comuns a todos os tipos de produtos. Uma coluna extra armazena um BLOB de dados semiestruturados, em XML, YAML, JSON ou algum outro formato. Este BLOB permite armazenar os atributos específicos para cada tipo de produto. Você pode usar padrões de design sofisticados para descrever isso, como Fachada e Memento. Mas, independentemente de você ter um blob de atributos que não podem ser consultados facilmente no SQL; você precisa buscar o blob inteiro no aplicativo e classificá-lo por aí.

  • Valor de atributo da entidade : uma tabela para produtos e uma tabela que atribui atributos a linhas, em vez de colunas. O EAV não é um design válido em relação ao paradigma relacional, mas muitas pessoas o usam de qualquer maneira. Este é o "Padrão de propriedades" mencionado por outra resposta. Veja outras perguntas com a tag eav no StackOverflow para obter algumas das armadilhas.

Escrevi mais sobre isso em uma apresentação, Extensible Data Modeling .


Pensamentos adicionais sobre o EAV: Embora muitas pessoas pareçam favorecer o EAV, eu não. Parece a solução mais flexível e, portanto, a melhor. No entanto, lembre-se do ditado TANSTAAFL . Aqui estão algumas das desvantagens do EAV:

  • Não há como tornar uma coluna obrigatória (equivalente a NOT NULL).
  • Não há como usar tipos de dados SQL para validar entradas.
  • Não há como garantir que os nomes dos atributos sejam digitados de forma consistente.
  • Não há como colocar uma chave estrangeira nos valores de qualquer atributo, por exemplo, para uma tabela de pesquisa.
  • A busca de resultados em um layout tabular convencional é complexa e dispendiosa, porque para obter atributos de várias linhas, você precisa fazer JOINpara cada atributo.

O grau de flexibilidade que o EAV oferece exige sacrifícios em outras áreas, provavelmente tornando seu código tão complexo (ou pior) do que teria sido para resolver o problema original de uma maneira mais convencional.

E na maioria dos casos, é desnecessário ter esse grau de flexibilidade. Na pergunta do OP sobre tipos de produtos, é muito mais simples criar uma tabela por tipo de produto para atributos específicos do produto, para que você tenha uma estrutura consistente imposta pelo menos para entradas do mesmo tipo de produto.

Eu usaria o EAV apenas se todas as linhas tiverem potencial para ter um conjunto distinto de atributos. Quando você tem um conjunto finito de tipos de produtos, o EAV é um exagero. Herança de tabela de classe seria minha primeira escolha.


Atualização 2019: quanto mais vejo as pessoas usando o JSON como uma solução para o problema de "muitos atributos personalizados", menos eu gosto dessa solução. Isso torna as consultas muito complexas, mesmo ao usar funções JSON especiais para suportá-las. É necessário muito mais espaço de armazenamento para armazenar documentos JSON, em vez de armazenar em linhas e colunas normais.

Basicamente, nenhuma dessas soluções é fácil ou eficiente em um banco de dados relacional. Toda a idéia de ter "atributos variáveis" está fundamentalmente em desacordo com a teoria relacional.

O que se resume é que você precisa escolher uma das soluções com base nas que são menos ruins para o seu aplicativo. Portanto, você precisa saber como vai consultar os dados antes de escolher um design de banco de dados. Não há como escolher uma solução que seja "melhor" porque qualquer uma das soluções pode ser a melhor para um determinado aplicativo.

Bill Karwin
fonte
11
A opção "4.5" do @HimalayaGarg é realmente o oposto de todo o argumento do post de Bill.
user3308043
2
Ao contrário do MySQL, o SQL Server possui amplo suporte para XML, XPath e XQuery. Portanto, para usuários do SQL Server, a melhor opção seria armazenar atributos extras em uma coluna do tipo XML (opção 4). Dessa forma, você NÃO precisa "buscar todo o blob de volta no aplicativo e classificá-lo por aí". Você pode até criar índices em colunas XML no SQL Server.
precisa saber é o seguinte
2
Prefiro LOB serializado para o meu caso. Mas é adequado para ORM? Eu uso EF.
Mahmood Jenami 22/03
@ user2741577, claro, mas você provavelmente precisará escrever um código personalizado para descompactar os campos de dados não estruturados do LOB e aplicá-los a cada campo de entidade do seu objeto ORM. Não conheço EF, mas suponho que você possa criar uma classe ORM básica que faça isso. Você precisa acompanhar quais campos vieram de campos concretos da linha do banco de dados e quais campos vieram de campos do LOB, para poder reformar um LOB quando chegar a hora de salvar o objeto.
Bill Karwin
12

@Coração de pedra

Eu iria aqui com EAV e MVC todo o caminho.

@Bill Karvin

Aqui estão algumas das desvantagens do EAV:

  • Não há como tornar uma coluna obrigatória (equivalente a NOT NULL).
  • Não há como usar tipos de dados SQL para validar entradas.
  • Não há como garantir que os nomes dos atributos sejam digitados de forma consistente.
  • Não há como colocar uma chave estrangeira nos valores de qualquer atributo, por exemplo, para uma tabela de pesquisa.

Todas as coisas que você mencionou aqui:

  • data de validade
  • validação ortográfica de nomes de atributos
  • colunas / campos obrigatórios
  • lidar com a destruição de atributos dependentes

na minha opinião, não pertence a nenhum banco de dados porque nenhum deles é capaz de lidar com essas interações e requisitos em um nível adequado, como a linguagem de programação de um aplicativo.

Na minha opinião, usar um banco de dados dessa maneira é como usar uma pedra para martelar um prego. Você pode fazer isso com uma pedra, mas não deve usar um martelo mais preciso e projetado especificamente para esse tipo de atividade?

A busca de resultados em um layout tabular convencional é complexa e dispendiosa, porque para obter atributos de várias linhas, é necessário JOIN para cada atributo.

Esse problema pode ser resolvido fazendo poucas consultas sobre dados parciais e processando-as no layout tabular com seu aplicativo. Mesmo se você tiver 600 GB de dados do produto, poderá processá-los em lotes se precisar de dados de todas as linhas desta tabela.

Indo além Se você deseja melhorar o desempenho das consultas, pode selecionar determinadas operações como, por exemplo, relatórios ou pesquisa de texto global e preparar para elas tabelas de índice que armazenariam os dados necessários e seriam regenerados periodicamente, digamos a cada 30 minutos.

Você nem precisa se preocupar com o custo de armazenamento de dados extra, porque fica cada vez mais barato a cada dia.

Se você ainda se preocupa com o desempenho das operações realizadas pelo aplicativo, sempre pode usar o Erlang, C ++, Go Language para pré-processar os dados e, posteriormente, apenas processar os dados otimizados no aplicativo principal.

Pawel Barcik
fonte
you can always use Erlang, C++, Go Language to pre-process the dataO que você quis dizer? Em vez de DB, use Go lang? Você poderia por favor explicar isso?
Verde
1
Eu concordo totalmente. O EAV é um caminho a percorrer, especialmente se você precisar de um nível de flexibilidade que permita adicionar novos tipos de produtos e parâmetros sem alterações no esquema db, quero dizer, viver em produção por meio de seu aplicativo. Estive lá, fiz isso. Trabalhou para mim. Sobre consultas lentas ... alguém aqui já ouviu falar sobre caches? ;)
pawel.kalisz
@Green Editei o último parágrafo para torná-lo mais claro, mas trata-se de passar os dados brutos do EAV para um processo em uma linguagem que pode lidar com transformações de dados, pesquisas em uma estrutura em árvore ou qualquer mapa básico, reduzindo as operações muito rapidamente e de maneira eficiente em memória. As especificidades aqui iria depender do que precisa ser otimizado
Pawel Barcik
6

Se eu usar Class Table Inheritancesignificado:

uma tabela para Produtos, armazenando atributos comuns a todos os tipos de produtos. Em seguida, uma tabela por tipo de produto, armazenando atributos específicos para esse tipo de produto. -Bill Karwin

Que eu mais gosto das sugestões de Bill Karwin. Posso prever uma desvantagem, que tentarei explicar como evitar que se torne um problema.

Qual plano de contingência devo ter quando um atributo que é comum apenas a 1 tipo e depois se torna comum a 2 e depois a 3 etc.?

Por exemplo: (este é apenas um exemplo, não é meu problema real)

Se vendermos móveis, poderemos vender cadeiras, luminárias, sofás, TVs etc. O tipo de TV pode ser o único tipo que carregamos que consome energia. Então, eu colocaria o power_consumptionatributo no tv_type_table. Mas então começamos a transportar sistemas de home theater que também possuem uma power_consumptionpropriedade. OK, é apenas mais um produto. Por isso, adicionarei esse campo stereo_type_table, pois provavelmente é o mais fácil neste momento. Mas, com o tempo, à medida que começamos a transportar mais e mais eletrônicos, percebemos que power_consumptioné amplo o suficiente para estar no main_product_table. O que eu deveria fazer agora?

Adicione o campo ao main_product_table. Escreva um script para percorrer os componentes eletrônicos e coloque o valor correto de cada um type_tablepara o main_product_table. Em seguida, solte essa coluna de cada uma type_table.

Agora, se eu estivesse sempre usando a mesma GetProductDataclasse para interagir com o banco de dados e obter as informações do produto; então, se qualquer alteração no código precisar agora de refatoração, elas devem ser apenas para essa classe.

JD Isaacks
fonte
3

Você pode ter uma tabela Produto e uma tabela ProductAdditionInfo separada com 3 colunas: ID do produto, nome da informação adicional, valor da informação adicional. Se a cor for usada por muitos, mas nem todos os tipos de produtos, você poderá fazer com que seja uma coluna anulável na tabela Produto ou apenas colocá-la em ProductAdditionalInfo.

Essa abordagem não é uma técnica tradicional para um banco de dados relacional, mas vi que ele era muito usado na prática. Pode ser flexível e ter bom desempenho.

Steve Yegge chama esse padrão de Propriedades e escreveu um longo post sobre como usá-lo.

RossFabricant
fonte
4
O padrão de propriedades é apenas valor de atributo de entidade com outro nome. É amplamente utilizado, mas armazená-lo em um banco de dados relacional quebra as regras de normalização.
Bill Karwin 30/03/09
2
Para ser sincero, quando li a descrição do EAV na resposta do @Bills, não entendi direito o que ele estava explicando. Mas quando você disse que 3 columns: product ID, additional info name, additional info valueeu entendi o conceito. E eu já fiz isso antes e tive problemas. No entanto, não me lembro no momento quais eram esses problemas.
JD Isaacks
1
@JDIsaacks Nesse padrão, um problema comum é que não sabemos quantos JOINs precisamos buscar todos os atributos.
Omid