Padrão de design da lista de atributos do produto


9

Estou trabalhando para atualizar o banco de dados de produtos do nosso site. Ele é construído no MySQL, mas essa é mais uma questão geral de padrão de design de banco de dados.

Estou pensando em mudar para um padrão Supertype / Subtype. Nosso banco de dados atual / anterior é principalmente uma tabela única que possui dados sobre um único tipo de produto. Estamos buscando expandir nossa oferta de produtos para incluir produtos diferentes.

Esse novo design de rascunho é assim:

Product             product_[type]          product_attribute_[name]
----------------    ----------------        ----------------------------
part_number (PK)    part_number (FK)        attributeId (PK)
UPC                 specific_attr1 (FK)     attribute_name
price               specific_attr2 (FK)
...                 ...

Eu tenho uma pergunta sobre as tabelas de atributos do produto. A idéia aqui é que um produto possa ter uma lista de atributos, como cor: vermelho, verde, azul ou material: plástico, madeira, cromo, alumínio, etc.

Essa lista seria armazenada em uma tabela e a chave primária (PK) para esse item de atributo será usada na tabela específica do produto como uma chave estrangeira (FK).

(O livro Patterns of Enterprise Application Architecture, de Martin Fowler, chama isso de " Mapeamento de Chave Estrangeira ")

Isso permite que uma interface de site puxe a lista de atributos para um determinado tipo de atributo e cuspa em um menu suspenso ou em outro elemento da interface do usuário. Essa lista pode ser considerada uma lista "autorizada" de valores de atributo.

O número de junções que acaba acontecendo ao puxar um produto específico parece excessivo para mim. Você deve associar todas as tabelas de atributos do produto ao produto para poder obter os campos desse atributo. Geralmente, esse campo pode ser apenas uma string (varchar) para seu nome.

Esse padrão de design acaba criando um grande número de tabelas, assim como você acaba com uma tabela para cada atributo. Uma idéia para neutralizar isso seria criar algo mais como uma tabela de “sacolas” para todos os atributos do produto. Algo assim:

product_attribute
----------------
attributeId (PK) 
name
field_name

Dessa forma, sua tabela pode ficar assim:

1  red     color
2  blue    color
3  chrome  material
4  plastic material
5  yellow  color
6  x-large size

Isso pode ajudar a reduzir a fluência da tabela, mas não reduz o número de junções e parece um pouco errado combinar tantos tipos diferentes em uma única tabela. Mas você seria capaz de obter todos os atributos de "cores" disponíveis com bastante facilidade.

No entanto, pode haver um atributo que tenha mais campos do que apenas "nome", como o valor RGB de uma cor. Isso exigiria que esse atributo específico possuísse outra tabela ou um único campo para o nome: par de valores (que possui suas próprias desvantagens).

O último padrão de design em que posso pensar é armazenar o valor real do atributo na tabela específica do produto e não ter uma "tabela de atributos". Algo assim:

Product             product_[type] 
----------------    ----------------
part_number (PK)    part_number (FK) 
UPC                 specific_attr1 
price               specific_attr2 
...                 ...

Em vez de uma chave estrangeira para outra tabela, ela conteria o valor real, como:

part_number    color    material
-----------    -----    --------
1234           red      plastic

Isso eliminaria as junções e impediria a fluência da tabela (talvez?). No entanto, isso evita ter uma "lista autorizada" de atributos. Você pode retornar todos os valores inseridos no momento para um determinado campo (por exemplo: cor), mas isso também elimina a idéia de ter uma "lista autorizada" de valores para um determinado atributo.

Para ter essa lista, você ainda precisa criar uma tabela de atributos "grab bag" ou ter várias tabelas (subida de tabela) para cada atributo.

Isso cria a maior desvantagem (e por que eu nunca usei essa abordagem) de agora ter o nome do produto em vários locais.

Se você tiver o valor de cor "vermelho" na "tabela de atributos principais" e também armazená-lo na tabela "produto_ [tipo]", uma atualização na tabela "principal" causará um possível problema de integridade de dados se o aplicativo não atualize todos os registros com o valor antigo na tabela "product_type" também.

Então, após minha longa explicação e análise sobre esse cenário, percebo que esse não pode ser um cenário incomum e pode haver até um nome para esse tipo de situação.

Existem soluções geralmente aceitas para esse desafio de design? O número potencialmente grande de junções é aceitável se as tabelas forem relativamente pequenas? O armazenamento do nome do atributo, em vez de um atributo PK, é aceitável em alguma situação? Existe outra solução em que não estou pensando?

Algumas notas sobre o banco de dados / aplicativo deste produto:

  • Os produtos não são atualizados / adicionados / removidos com frequência
  • Os atributos não são atualizados / adicionados / removidos com frequência
  • A tabela é mais frequentemente consultada para ler / retornar informações
  • O cache do servidor está ativado para armazenar em cache o resultado de uma determinada consulta / resultado
  • Pretendo começar com apenas um tipo de produto e estender / adicionar outros ao longo do tempo e terei potencialmente mais de 10 tipos diferentes

11
Quantos tipos de produtos você terá?
Dez20

11
Boa pergunta. Ele vai começar pequeno 3-4 mas potentionally crescer mais de 10+
jmbertucci

O que você quer dizer com "Lista autorizada de atributos"?
precisa saber é o seguinte

Desculpe, deve ser "valor do atributo". A ideia de que você tem uma tabela listando todos os valores permitidos para um atributo. Ou seja. Aqui está uma lista de 10 cores que esse tipo de produto pode ser. Esses 10 são os valores de "autorização" que alguém pode escolher.
jmbertucci

Eu estou querendo saber se seria bom ter todos esses valores de atributo ingressados ​​na tabela de tipo de produto, se eu finalmente criar uma "visualização" em cima dela?
jmbertucci

Respostas:


17

Eu pessoalmente usaria um modelo semelhante ao seguinte:

A tabela de produtos seria bem básica, os principais detalhes do produto:

create table product
(
  part_number int, (PK)
  name varchar(10),
  price int
);
insert into product values
(1, 'product1', 50),
(2, 'product2', 95.99);

Segundo a tabela de atributos para armazenar cada um dos diferentes atributos.

create table attribute
(
  attributeid int, (PK)
  attribute_name varchar(10),
  attribute_value varchar(50)
);
insert into attribute values
(1, 'color', 'red'),
(2, 'color', 'blue'),
(3, 'material', 'chrome'),
(4, 'material', 'plastic'),
(5, 'color', 'yellow'),
(6, 'size', 'x-large');

Por fim, crie a tabela product_attribute como a tabela JOIN entre cada produto e seus atributos associados a ele.

create table product_attribute
(
  part_number int, (FK)
  attributeid int  (FK) 
);
insert into product_attribute values
(1,  1),
(1,  3),
(2,  6),
(2,  2),
(2,  6);

Dependendo de como você deseja usar os dados, você está vendo duas junções:

select *
from product p
left join product_attribute t
  on p.part_number = t.part_number
left join attribute a
  on t.attributeid = a.attributeid;

Consulte SQL Fiddle com demonstração . Isso retorna dados no formato:

PART_NUMBER | NAME       | PRICE | ATTRIBUTEID | ATTRIBUTE_NAME | ATTRIBUTE_VALUE
___________________________________________________________________________
1           | product1   | 50    | 1           | color          | red
1           | product1   | 50    | 3           | material       | chrome
2           | product2   | 96    | 6           | size           | x-large
2           | product2   | 96    | 2           | color          | blue
2           | product2   | 96    | 6           | size           | x-large

Mas se você quiser retornar os dados em um PIVOTformato em que tenha uma linha com todos os atributos como colunas, poderá usar CASEinstruções com um agregado:

SELECT p.part_number,
  p.name,
  p.price,
  MAX(IF(a.ATTRIBUTE_NAME = 'color', a.ATTRIBUTE_VALUE, null)) as color,
  MAX(IF(a.ATTRIBUTE_NAME = 'material', a.ATTRIBUTE_VALUE, null)) as material,
  MAX(IF(a.ATTRIBUTE_NAME = 'size', a.ATTRIBUTE_VALUE, null)) as size
from product p
left join product_attribute t
  on p.part_number = t.part_number
left join attribute a
  on t.attributeid = a.attributeid
group by p.part_number, p.name, p.price;

Consulte SQL Fiddle com demonstração . Os dados são retornados no formato:

PART_NUMBER | NAME       | PRICE | COLOR | MATERIAL | SIZE
_________________________________________________________________
1           | product1   | 50    | red   | chrome   | null
2           | product2   | 96    | blue  | null     | x-large

Como você pode ver, os dados podem estar em um formato melhor para você, mas se você tiver um número desconhecido de atributos, ele se tornará facilmente insustentável devido a nomes de atributos codificados, então no MySQL você pode usar instruções preparadas para criar dinâmicas dinâmicas . Seu código seria o seguinte (consulte SQL Fiddle With Demo ):

SET @sql = NULL;
SELECT
  GROUP_CONCAT(DISTINCT
    CONCAT(
      'MAX(IF(a.attribute_name = ''',
      attribute_name,
      ''', a.attribute_value, NULL)) AS ',
      attribute_name
    )
  ) INTO @sql
FROM attribute;

SET @sql = CONCAT('SELECT p.part_number
                    , p.name
                    , ', @sql, ' 
                   from product p
                   left join product_attribute t
                     on p.part_number = t.part_number
                   left join attribute a
                     on t.attributeid = a.attributeid
                   GROUP BY p.part_number
                    , p.name');

PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;

Isso gera o mesmo resultado da segunda versão, sem a necessidade de codificar nada. Embora existam muitas maneiras de modelar isso, acho que esse design de banco de dados é o mais flexível.


+1 - Uma resposta fantasticamente escrita. Ainda estou demorando alguns minutos para reler e digerir esta resposta antes de aceitar. Parece uma boa solução para minha pergunta sobre junções e atributos do produto e vai além dos exemplos de pivôs e declarações preparadas. Então, vou começar com um +1 para isso. =)
jmbertucci 26/09/12

@ jmbertucci você parecia preocupado em consultar as tabelas, então achei que iria fornecer algumas amostras. :)
Taryn

De fato. Eu vou "doh" que não vi fazendo uma tabela cruzada de produto para atribuir. Provavelmente um caso de excesso de pensamento, especialmente depois de imergir padrões e teorias de design. Além disso, minha experiência com DBA é básica e fazer mais com instruções preparadas é algo que eu preciso, portanto sua inclusão é muito útil. E essa resposta ajudou a quebrar o "bloqueio de escritores" que eu estava tendo, para poder seguir em frente com esse projeto, que faz o meu dia. =)
jmbertucci 26/09/12

bem, uma pergunta ... é lento? Eu caí como você vai demorar mais de 30 segundos para consulta apenas 10K produtos com 10 atributos ..
CénitS

@ ZenithS Você teria que testá-lo para ver e possivelmente adicionar índices nas colunas consultadas. Eu não tenho uma instância do MySQL para testar.
Taryn

0

Eu expandiria a resposta de Taryn e modificaria a tabela de atributos para ter a coluna fk_attribute_type_id que será em vez da coluna attribute_name e apontará para a nova tabela attribute_type.

Portanto, você estruturou os tipos de atributo em uma tabela e pode alterá-lo a qualquer momento em um só lugar.

Na minha opinião, é melhor trabalhar com o tipo de "discagem" (tabela com tipos possíveis) do que com o tipo enum (como na coluna attribute_name (e ainda por cima, na verdade, não é o nome, o tipo de atributo)).

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.