Patrón de diseño de la lista de atributos del producto

9

Estoy trabajando para actualizar la base de datos de productos de nuestro sitio web. Está construido en MySQL, pero esta es más una pregunta de patrón de diseño de base de datos general.

Estoy planeando cambiar a un patrón de Supertipo / Subtipo. Nuestra base de datos actual / anterior es principalmente una tabla única que tiene datos sobre un solo tipo de producto. Estamos buscando expandir nuestra oferta de productos para incluir productos diferentes.

Este nuevo diseño borrador es así:

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

Tengo una pregunta sobre las tablas de atributos del producto. La idea aquí es que un producto puede tener una lista de atributos dados, como color: rojo, verde, azul o material: plástico, madera, cromo, aluminio, etc.

Esta lista se almacenaría en una tabla y la clave primaria (PK) para ese elemento de atributo se utilizará en la tabla de productos específica como una clave externa (FK).

(El libro de Martin Fowler, Patterns of Enterprise Application Architecture, llama a esto " Mapeo de claves externas ")

Esto permite que la interfaz de un sitio web extraiga la lista de atributos para un tipo de atributo dado y lo escupe en un menú de selección desplegable o en algún otro elemento de la interfaz de usuario. Esta lista puede considerarse una lista "autorizada" de valores de atributos.

El número de uniones que termina sucediendo cuando se tira de un producto específico me parece excesivo. Debe unir todas las tablas de atributos del producto al producto para poder obtener los campos de ese atributo. Comúnmente, ese campo podría ser simplemente una cadena (varchar) para su nombre.

Este patrón de diseño termina creando una gran cantidad de tablas, así como también termina con una tabla para cada atributo. Una idea para contrarrestar esto sería crear algo más de una tabla de "bolsa de agarre" para todos los atributos del producto. Algo como esto:

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

De esta manera, su tabla podría verse así:

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

Esto podría ayudar a reducir el deslizamiento de la tabla, pero no reduce el número de uniones y se siente un poco mal combinar tantos tipos diferentes en una sola tabla. Pero podría obtener todos los atributos de "color" disponibles con bastante facilidad.

Sin embargo, puede haber un atributo que tenga más campos que solo "nombre", como el valor RGB de un color. Esto requeriría que ese atributo específico posiblemente tenga otra tabla o que tenga un solo campo para el nombre: par de valores (que tiene sus propios inconvenientes).

El último patrón de diseño que se me ocurre es almacenar el valor de atributo real en la tabla de productos específica y no tener una "tabla de atributos" en absoluto. Algo como esto:

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

En lugar de una clave externa para otra tabla, contendría el valor real como:

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

Esto eliminaría las uniones y evitaría el deslizamiento de la tabla (¿tal vez?). Sin embargo, esto evita tener una "lista autorizada" de atributos. Puede devolver todos los valores ingresados ​​actualmente para un campo determinado (es decir, color), pero esto también elimina la idea de tener una "lista autorizada" de valores para un atributo dado.

Para tener esa lista, aún tendría que crear una tabla de atributos de “bolsa de agarre” o tener varias tablas (arrastre de tabla) para cada atributo.

Esto crea el mayor inconveniente (y por qué nunca he usado este enfoque) de tener ahora el nombre del producto en varias ubicaciones.

Si tiene el valor de color de "rojo" en la "tabla de atributos maestros" y también lo almacena en la tabla "producto_ [tipo]", una actualización de la tabla "maestra" causará un posible problema de integridad de datos si la aplicación no No actualice todos los registros con el valor anterior en la tabla "product_type" también.

Entonces, después de mi larga explicación y análisis de este escenario, me doy cuenta de que este no puede ser un escenario poco común e incluso podría haber un nombre para este tipo de situación.

¿Existen soluciones generalmente aceptadas para este desafío de diseño? ¿Es aceptable el número potencialmente grande de uniones si las tablas son relativamente pequeñas? ¿Almacenar el nombre del atributo, en lugar de un atributo PK, es aceptable en alguna situación? ¿Hay otra solución en la que no estoy pensando?

Algunas notas sobre esta base de datos / aplicación del producto:

  • Los productos no se actualizan / agregan / eliminan con frecuencia
  • Los atributos no se actualizan / agregan / eliminan con frecuencia
  • La tabla se consulta con mayor frecuencia para leer / devolver información
  • El almacenamiento en caché del lado del servidor está habilitado para almacenar en caché el resultado de una consulta / resultado dado
  • Planeo comenzar con un solo tipo de producto y extender / agregar otros con el tiempo y tendré potencialmente más de 10 tipos diferentes
jmbertucci
fuente
1
¿Cuántos tipos de productos tendrás?
dezso
1
Buena pregunta. Comenzará pequeño 3-4, pero potencialmente crecerá más de 10+
jmbertucci
¿Qué quiere decir con "Lista autorizada de atributos"?
NoPuerto
Lo sentimos, debe ser "valor de atributo". La idea de que tiene una tabla que enumera todos los valores permitidos para un atributo. Es decir. Aquí hay una lista de 10 colores que puede ser este tipo de producto. Estos 10 son los valores de "autorización" que alguien podría elegir.
jmbertucci
Me pregunto si estaría bien tener todos estos valores de atributos unidos a la tabla de tipos de productos si finalmente creo una "vista" encima.
jmbertucci

Respuestas:

17

Yo personalmente usaría un modelo similar al siguiente:

La tabla de productos sería bastante básica, los detalles de su producto principal:

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

Segundo, la tabla de atributos para almacenar cada uno de los 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');

Finalmente, cree la tabla product_attribute como la tabla JOIN entre cada producto y sus atributos asociados.

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);

Dependiendo de cómo desee utilizar los datos, está viendo dos combinaciones:

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;

Ver SQL Fiddle con Demo . Esto devuelve datos en el 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

Pero si desea devolver los datos en un PIVOTformato donde tiene una fila con todos los atributos como columnas, puede usar CASEdeclaraciones con un 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;

Ver SQL Fiddle con Demo . Los datos se devuelven en el formato:

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

Como puede ver, los datos pueden estar en un mejor formato para usted, pero si tiene un número desconocido de atributos, se volverá insostenible fácilmente debido a los nombres de los atributos de codificación rígida, por lo que en MySQL puede usar declaraciones preparadas para crear pivotes dinámicos . Su código sería el siguiente (Ver 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;

Esto genera el mismo resultado que la segunda versión sin necesidad de codificar nada. Si bien hay muchas formas de modelar esto, creo que este diseño de base de datos es el más flexible.

Taryn
fuente
+1: una respuesta fantásticamente escrita. Todavía me estoy tomando unos minutos para volver a leer y digerir esta respuesta antes de aceptar. Parece una buena solución a mi pregunta sobre las uniones y los atributos del producto e incluso va más allá con ejemplos de pivotes y declaraciones preparadas. Entonces, comenzaré con un +1 para eso. =)
jmbertucci
@jmbertucci parecía preocupado por consultar las tablas, así que pensé que le proporcionaría algunas muestras. :)
Taryn
En efecto. Voy "doh" que no vi haciendo una tabla cruzada de producto para atribuir. Probablemente un caso de pensar demasiado, especialmente después de sumergir patrones de diseño y teorías. Además, mi experiencia en DBA es básica y hacer más con declaraciones preparadas es algo que necesito, por lo que su inclusión es de gran ayuda. Y esta respuesta ha ayudado a romper ese "bloqueo de escritores" que tenía para poder seguir adelante con este proyecto, lo que me alegra el día. =)
jmbertucci
bueno, una pregunta ... ¿es lenta? Me sentí como si
tomaras
@ ZenithS Tendría que probarlo para ver y posiblemente agregar índices en las columnas que consulta. No tengo una instancia de MySQL para hacer ninguna prueba.
Taryn
0

Me gustaría ampliar la respuesta de Taryn y modificar la tabla de atributos para tener fk_attribute_type_id columna que estará en lugar de nombre_atributo columna y puntos de attribute_type nueva mesa.

Por lo tanto, tiene tipos de atributos estructurados en una tabla y puede cambiarlos en cualquier momento en un solo lugar.

En mi opinión, es mejor trabajar con el tipo de "marcado" (tabla con tipos posibles) que con el tipo enum (como está en la columna nombre_atributo (y además de eso, en realidad no es nombre, su tipo de atributo)).

Ales
fuente