Qual é a melhor prática para este problema (propriedades diferentes para diferentes categorias)?
-
03-07-2019 - |
Pergunta
Eu tenho alguns produtos que pertence à alguma categoria.
Cada categoria pode ter propriedades diferentes.
Por exemplo,
- Categoria carros tem propriedades cor , poder, ...
- categoria animais de estimação tem propriedades peso , idade , ...
Número de categorias é de cerca de 10-15. Número de propriedades em cada categoria é 3-15. Número de produtos é muito grande.
A principal exigência para este aplicativo é muito boa pesquisa. Vamos selecionar a categoria e inserir critérios para cada propriedade nesta categoria.
tem que projetar banco de dados para este cenário. (SQL Server 2005)
Solução
A abordagem de design clássico seria (a estrela denota a coluna de chave primária):
Product
ProductId*
CategoryId: FK to Category.CategroyId
Name
Category
CategoryId*
Name
Property
PropertyId*
Name
Type
CategoryProperty
CategoryId*: FK to Category.CategoryId
PropertyId*: FK to Property.PropertyId
ProductProperty
ProductId*: FK to Product.ProductId
PropertyId*: FK to Property.PropertyId
ValueAsString
Se você pode viver com o fato de que cada valor da propriedade iria para o DB como uma informação de corda e conversão de tipo é armazenado na tabela de propriedade, esta disposição seria suficiente.
A consulta seria algo como isto:
SELECT
Product.ProductId,
Product.Name AS ProductName,
Category.CategoryId,
Category.Name AS CategoryName,
Property.PropertyId,
Property.Name AS PropertyName,
Property.Type AS PropertyType,
ProductProperty.ValueAsString
FROM
Product
INNER JOIN Category ON Category.CategoryId = Product.CategoryId
INENR JOIN CategoryProperty ON CategoryProperty.CategoryId = Category.CategoryId
INNER JOIN Property ON Property.PropertyId = CategoryProperty.PropertyId
INNER JOIN ProductProperty ON ProductProperty.PropertyId = Property.PropertyId
AND ProductProperty.ProductId = Product.ProductId
WHERE
Product.ProductId = 1
O mais condições WHERE você fornecer (conjunctively, por exemplo, usando AND), mais rápida a consulta será. Se você tiver indexado adequadamente suas tabelas, é isso.
Como é, a solução não é ideal para uma situação de indexação de texto completo. Uma tabela adicional que armazena todo o texto associado a um ProductId de uma forma mais denormalized poderia ajudar aqui. Esta tabela seria necessário atualizar através de gatilhos que escutar alterações na tabela de ProductProperty.
Outras dicas
Se o usuário do aplicativo tem para selecionar uma categoria antes que eles possam procurar, eu iria separar seus produtos em diferentes tabelas de banco de dados por categoria. Esta solução também é indicada pelo fato de que os próprios categorias têm muito pouco em comum. Dividi-lo por categoria também vai fazer cada pesquisa muito mais rápido, pois o tempo não será desperdiçado procurando por carros quando seu usuário está procurando um animal de estimação.
Uma vez que você tem os produtos divididos em categorias, que deve ser fácil para criar as tabelas usando as propriedades comuns dos produtos em cada categoria. A interface do usuário do seu aplicativo deve ser dinâmico (Estou pensando em um formulário web), em que as propriedades que o usuário pode escolher deve mudar quando o usuário seleciona uma categoria.
Por favor note que se você tem produtos que deseja listados em várias categorias, esta solução irá resultar em dados duplicados em suas tabelas. Há um trade-off entre velocidade e normalização ao projetar um banco de dados. Se você não tem produtos que se encaixam em várias categorias, então eu penso que esta será a solução mais rápida (em termos de velocidade de pesquisa).
A maioria das pessoas estão aconselhando a usar variações do projeto do atributo de entidade Valor (EAV). Este projeto é um exagero para a sua situação, e introduz um monte de problemas, por exemplo:
- Você não pode definir o tipo de dados para um atributo; você pode digitar "banana" para um atributo inteiro
- Você não pode declarar um atributo como obrigatório (ou seja NOT NULL em uma mesa convencional)
- Você não pode declarar uma restrição de chave estrangeira em um atributo
Se você tem um pequeno número de categorias, é melhor usar a solução A na resposta de Bogdan Maxim. Ou seja, definir uma tabela Produtos com atributos comuns a todas as categorias, e uma tabela adicional para cada categoria, para armazenar os atributos específicos da categoria.
Só se você tem um número infinito de categorias ou se você deve potencialmente suportar um conjunto diferente de atributos por linha em produtos é EAV uma boa solução. Mas então você não está usando um banco de dados relacional em tudo, desde EAV viola várias regras de normalização.
Se você realmente precisa que muita flexibilidade, você seria melhor fora de armazenar seus dados em XML. Na verdade, você pode olhar para RDF e frameworks web semânticas como Sesame .
Você pode querer considerar um Entidade-Atributo-Valor tipo de arranjo, onde você pode "marcar" cada produto com pares nome arbitrário / valor de atributos.
Você pode tentar isso. Eu não estou muito certo dos detalhes reais de sua pergunta, talvez alguém possa ajudar a traduzir um pouco melhor.
5 mesas. 3 para o armazenamento de dados, dois para armazenar os mapeamentos entre dados.
tProduct
productID
<other product details>
tCategory
categoryID
<other category details>
tProperty
propertyID
<other property details>
tProductXCategory
productyID
categoryID
tCategoryXProperty
categoryID
propertyID
As consultas terão de juntar-se os dados utilizando as tabelas de mapeamento, mas isso vai permitir que você tenha diferentes muitos para muitos relacionamentos entre a categoria, propriedades e produtos.
Use procedimentos ou consultas parametrizadas armazenado para obter um melhor desempenho das suas pesquisas.
Se você quiser ser flexível em suas categorias e propriedades, você deve criar tabelas seguintes:
- produto: ProductID
- categoria: CategoryID, ProductID
- propriedade: Imóvel, CódigoDaCategoria
quando você quiser compartilhar uma categoria mais mroe de um produto, você tem que criar uma tabela de ligação para o n: m juntar-se:
- productCategoryPointer:. ProdCatID, ProductID, CódigoDaCategoria
Você terá que para alguns se junta em suas consultas, mas com os índices certos, você shoulb ser capaz de consultar seus dados rapidamente.
Você poderia tentar orientada algo mais objeto.
1. Definir uma tabela base para os produtos
Products(ProductID, CategoryID, <any other common properties>)
2. Definir uma tabela Categorias
Categories(CategoryID, Name, Description, ..)
A partir daqui você tem um monte de opções e quase todos eles vão quebrar a normalização do seu banco de dados.
Solução A.
Será um pesadelo maintaince se você precisa adicionar novos produtos
A1. Definir uma tabela separada para cada uma das categorias
Cars(CarID, ProductID, ..)
Pets(PetID, ProductID, ..)
A2. Associar as tabelas com base nas relações, a fim de utilizar os dados
SELECT <fields> FROM Cars INNER JOIN Products ON Cars.ProductID = Products.ProductID
Solução B.
Maintainance pesadelo para diferentes tipos de propriedades (isto é int, VARCHAR, etc)
B1. Definir uma tabela de Propriedades
CategoryProperty (CPID, Name, Type)
B2. Definir uma tabela para manter as associações entre as categorias e as propriedades
PropertyAssociation (CPID, PropertyID)
B12. Definir uma tabela para manter as propriedades (Alternativa para B1 e B2)
Properties(CategoryID, PropertyID, Name, Type)
B3. Para cada tipo de propriedade (int, double, varchar, etc.) adicionar uma tabela de valores
PropertyValueInt(ProductID, CPID, PropertyID, Value)
- para int
PropertyValueString(ProductID, CPID, PropertyID, Value)
- para cordas
PropertyValueMoney(ProductID, CPID, PropertyID, Value)
- por dinheiro
B4. Junte-se a todas as tabelas para recuperar a propriedade desejada.
Usando essa abordagem, você não tem que gerenciar todas as propriedades na tabela separada, mas os tipos de valor deles. Basicamente todas as tabelas envolvidas será lookup tabelas. A desvantagem, é que, a fim de recuperá cada valor, você tem que "Case" para cada tipo de valor.
Leve em mente estes artigos ( aqui e aqui ) ao escolher qualquer uma destas abordagens. Este post no fórum Também é interessante e de alguma forma relacionados com o tema, mesmo embora seja sobre a localização.
Você também pode usar ea resposta de Tomalak adicionar tipagem forte, se você sente a necessidade.
Recentemente, tive de fazer isso e eu estou usando NHibernate onde eu tenho três entidades
Categoria do produto Opção OptionCategory
Um produto tem 1 * Categorias
Um produto tem 1 * Opção
Uma opção tem 1 OptionCategory
Uma vez que este está configurado você pode usar Nhibernate cache
Felicidades