Modello di progettazione dell'elenco di attributi del prodotto


9

Sto lavorando per aggiornare il database dei prodotti del nostro sito Web. È costruito in MySQL ma questa è più una domanda generale sul modello di progettazione del database.

Sto pensando di passare a un modello Supertipo / Sottotipo. Il nostro database attuale / precedente è principalmente una singola tabella che contiene dati su un singolo tipo di prodotto. Stiamo cercando di espandere la nostra offerta di prodotti per includere prodotti diversi.

Questo nuovo progetto di bozza è così:

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

Ho una domanda relativa alle tabelle degli attributi del prodotto. L'idea qui è che un prodotto può avere un elenco di attributi dati come colore: rosso, verde, blu o materiale: plastica, legno, cromo, alluminio, ecc.

Questo elenco verrebbe archiviato in una tabella e la chiave primaria (PK) per quell'elemento dell'attributo verrà utilizzata nella tabella del prodotto specifico come chiave esterna (FK).

(Il libro Patterns of Enterprise Application Architecture di Martin Fowler lo chiama " Foreign Key Mapping ")

Ciò consente a un'interfaccia di un sito Web di estrarre l'elenco di attributi per un determinato tipo di attributo e di sputarlo in un menu di selezione a discesa o in qualche altro elemento dell'interfaccia utente. Questo elenco può essere considerato un elenco "autorizzato" di valori di attributo.

Il numero di join che si verificano quando si estrae un prodotto specifico mi sembra eccessivo. È necessario unire ogni tabella degli attributi del prodotto al prodotto in modo da poter ottenere i campi di quell'attributo. Comunemente, quel campo potrebbe semplicemente essere nient'altro che una stringa (varchar) per il suo nome.

Questo modello di progettazione finisce per creare un gran numero di tabelle e si finisce con una tabella per ogni attributo. Un'idea per contrastare questo sarebbe quella di creare qualcosa di più di una tabella "grab bag" per tutti gli attributi del prodotto. Qualcosa come questo:

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

In questo modo, il tuo tavolo potrebbe apparire così:

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

Questo potrebbe aiutare a ridurre lo scorrimento della tabella ma non riduce il numero di join e sembra un po 'sbagliato combinare così tanti tipi diversi in una singola tabella. Ma saresti in grado di ottenere abbastanza facilmente tutti gli attributi di "colore" disponibili.

Tuttavia, potrebbe esserci un attributo che ha più campi di un semplice "nome" come il valore RGB di un colore. Ciò richiederebbe che l'attributo specifico abbia eventualmente un'altra tabella o abbia un singolo campo per name: value pair (che ha i suoi svantaggi).

L'ultimo modello di progettazione che mi viene in mente è l'archiviazione del valore dell'attributo effettivo nella tabella del prodotto specifico e non esiste affatto una "tabella degli attributi". Qualcosa come questo:

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

Invece di una chiave esterna per un'altra tabella, conterrebbe il valore effettivo come:

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

Ciò eliminerebbe i join e impedirebbe lo scorrimento della tabella (forse?). Tuttavia, ciò impedisce di avere un "elenco autorizzato" di attributi. È possibile restituire tutti i valori attualmente immessi per un determinato campo (ad esempio: colore) ma ciò elimina anche l'idea di disporre di un "elenco autorizzato" di valori per un determinato attributo.

Per avere quell'elenco, dovresti comunque creare una tabella degli attributi "grab bag" o avere più tabelle (scorrimento della tabella) per ciascun attributo.

Questo crea l'inconveniente più grande (e perché non ho mai usato questo approccio) di avere ora il nome del prodotto in più posizioni.

Se hai il valore di colore "rosso" nella "tabella degli attributi principali" e lo memorizzi anche nella tabella "product_ [tipo]", un aggiornamento alla tabella "principale" causerà un potenziale problema di integrità dei dati se l'applicazione non lo fa aggiorna anche tutti i record con il vecchio valore nella tabella "product_type".

Quindi, dopo la mia lunga spiegazione e analisi di questo scenario, la mia realizzazione è che questo non può essere uno scenario insolito e potrebbe persino esserci un nome per questo tipo di situazione.

Esistono soluzioni generalmente accettate per questa sfida progettuale? Il numero potenzialmente elevato di join è accettabile se le tabelle sono relativamente piccole? La memorizzazione del nome dell'attributo, anziché dell'attributo PK, è accettabile in alcune situazioni? C'è un'altra soluzione a cui non sto pensando?

Alcune note su questo database / applicazione di questo prodotto:

  • I prodotti non vengono aggiornati / aggiunti / rimossi frequentemente
  • Gli attributi non vengono aggiornati / aggiunti / rimossi frequentemente
  • La tabella viene più frequentemente richiesta per leggere / restituire informazioni
  • La memorizzazione nella cache lato server è abilitata per memorizzare nella cache il risultato di una determinata query / risultato
  • Ho intenzione di iniziare con un solo tipo di prodotto e di estenderne / aggiungerne altri nel tempo e conterrà potenzialmente più di 10 tipi diversi

1
Quanti tipi di prodotto avrai?
dezso,

1
Buona domanda. Inizierà piccolo 3-4 ma potenziosamente aumenterà a 10+
jmbertucci,

Cosa intendi con "Elenco autorizzato di attributi"?
NoChance,

Siamo spiacenti, dovrebbe essere "valore attributo". L'idea di avere una tabella che elenca tutti i valori consentiti per un attributo. Vale a dire. ecco un elenco di 10 colori che questo tipo di prodotto può essere. Questi 10 sono i valori "autorizza" che qualcuno potrebbe scegliere.
jmbertucci,

Mi chiedo se sarebbe bene avere tutti questi valori di attributo uniti alla tabella dei tipi di prodotto se alla fine creo una "vista" su di essa?
jmbertucci,

Risposte:


17

Personalmente userei un modello simile al seguente:

La tabella dei prodotti sarebbe piuttosto semplice, i dettagli principali del tuo prodotto:

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

Secondo la tabella degli attributi per memorizzare ciascuno dei diversi attributi.

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

Infine, crea la tabella product_attribute come tabella JOIN tra ciascun prodotto e i relativi attributi associati.

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

A seconda di come si desidera utilizzare i dati che si stanno osservando due join:

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;

Vedi SQL Fiddle with Demo . Ciò restituisce i dati nel 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

Ma se vuoi restituire i dati in un PIVOTformato in cui hai una riga con tutti gli attributi come colonne, puoi usare le CASEistruzioni con un aggregato:

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;

Vedi SQL Fiddle with Demo . I dati vengono restituiti nel formato:

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

Come vedrai, i dati potrebbero essere in un formato migliore per te, ma se hai un numero sconosciuto di attributi, diventeranno facilmente insostenibili a causa dei nomi degli attributi codificati, quindi in MySQL puoi usare istruzioni preparate per creare pivot dinamici . Il tuo codice sarebbe il seguente (Vedi 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;

Questo genera lo stesso risultato della seconda versione senza la necessità di codificare nulla. Mentre ci sono molti modi per modellarlo, penso che la progettazione di questo database sia la più flessibile.


+1 - Una risposta scritta in modo fantastico. Mi sto ancora prendendo qualche momento per rileggere e digerire questa risposta prima di accettare. Sembra una buona soluzione alla mia domanda su join e attributi del prodotto e va anche al di là di esempi di pivot e dichiarazioni preparate. Quindi, inizierò con un +1 per quello. =)
jmbertucci,

@jmbertucci sembravi preoccupato per l'interrogazione delle tabelle, quindi ho pensato che ti avrei fornito alcuni esempi. :)
Taryn

Infatti. Sto andando "doh" che non ho visto fare una tabella incrociata di prodotto da attribuire. Probabilmente un caso di pensiero eccessivo, soprattutto dopo aver immerso teorie e schemi di progettazione. Inoltre, la mia esperienza DBA è di base e fare di più con le dichiarazioni preparate è qualcosa di cui ho bisogno, quindi la tua inclusione è di grande aiuto. E questa risposta mi ha aiutato a smantellare quel "blocco degli scrittori" che stavo avendo in modo da poter andare avanti con questo progetto, il che rende la mia giornata. =)
jmbertucci,

bene, una domanda ... è lento? Sono caduto come se dovessi impiegare più di 30 secondi per interrogare solo 10K prodotti con 10 attributi ..
ZenithS,

@ZenithS Dovresti testarlo per vedere e possibilmente aggiungere indici sulle colonne che richiedi. Non ho un'istanza di MySQL per fare alcun test.
Taryn

0

Avrei ampliare la risposta di Taryn e modificare la tabella attributi di avere fk_attribute_type_id colonna che sarà invece attribute_name colonna e punti di nuova tabella attribute_type.

Quindi hai tipi di attributi strutturati in una tabella e puoi cambiarli in qualsiasi momento in un unico posto.

A mio avviso è meglio lavorare con una sorta di "dial" (tabella con tipi possibili) piuttosto che con il tipo enum (come nella colonna nome_attributo (e soprattutto non è il nome, il tipo di attributo)).

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.