Come progettare una tabella di prodotti per molti tipi di prodotti in cui ogni prodotto ha molti parametri


140

Non ho molta esperienza nella progettazione di tavoli. Il mio obiettivo è creare una o più tabelle di prodotti che soddisfino i requisiti di seguito:

  • Supporta molti tipi di prodotti (TV, telefono, PC, ...). Ogni tipo di prodotto ha un diverso set di parametri, come:

    • Il telefono avrà colore, dimensioni, peso, sistema operativo ...

    • Il PC avrà CPU, HDD, RAM ...

  • L'insieme di parametri deve essere dinamico. Puoi aggiungere o modificare qualsiasi parametro che ti piace.

Come posso soddisfare questi requisiti senza una tabella separata per ogni tipo di prodotto?

Risposte:


233

Hai almeno queste cinque opzioni per modellare la gerarchia dei tipi che descrivi:

  • Eredità tabella singola : una tabella per tutti i tipi di prodotto, con colonne sufficienti per memorizzare tutti gli attributi di tutti i tipi. Ciò significa molte colonne, la maggior parte delle quali sono NULL su una determinata riga.

  • Ereditarietà delle classi : una tabella per i prodotti, che memorizza gli attributi comuni a tutti i tipi di prodotti. Quindi una tabella per tipo di prodotto, memorizzando gli attributi specifici per quel tipo di prodotto.

  • Ereditarietà concreta della tabella : nessuna tabella per gli attributi comuni dei prodotti. Invece, una tabella per tipo di prodotto, che memorizza sia gli attributi comuni del prodotto, sia gli attributi specifici del prodotto.

  • LOB serializzato : una tabella per i prodotti, che memorizza gli attributi comuni a tutti i tipi di prodotto. Una colonna aggiuntiva memorizza un BLOB di dati semi-strutturati, in XML, YAML, JSON o in qualche altro formato. Questo BLOB consente di memorizzare gli attributi specifici per ciascun tipo di prodotto. È possibile utilizzare modelli di design fantasiosi per descriverlo, come Facciata e Memento. Tuttavia, indipendentemente dal fatto che tu abbia una serie di attributi che non possono essere facilmente interrogati in SQL; devi recuperare l'intero BLOB indietro nell'applicazione e ordinarlo là fuori.

  • Entity-Attribute-Value : una tabella per i prodotti e una tabella che ruota gli attributi sulle righe anziché sulle colonne. L'EAV non è un progetto valido rispetto al paradigma relazionale, ma molte persone lo usano comunque. Questo è il "Modello di proprietà" menzionato da un'altra risposta. Vedi altre domande con il tag eav su StackOverflow per alcune insidie.

Ho scritto di più su questo in una presentazione, Extensible Data Modeling .


Ulteriori pensieri sull'EAV: anche se molte persone sembrano favorire l'EAV, io no. Sembra la soluzione più flessibile e quindi la migliore. Tuttavia, tieni presente l'adagio TANSTAAFL . Ecco alcuni degli svantaggi di EAV:

  • Non è possibile rendere obbligatoria una colonna (equivalente di NOT NULL).
  • Non è possibile utilizzare tipi di dati SQL per convalidare le voci.
  • Nessun modo per garantire che i nomi degli attributi siano scritti in modo coerente.
  • Non è possibile inserire una chiave esterna sui valori di un dato attributo, ad esempio per una tabella di ricerca.
  • Il recupero dei risultati in un layout tabellare convenzionale è complesso e costoso, perché per ottenere gli attributi da più righe è necessario fare JOINper ogni attributo.

Il grado di flessibilità che EAV ti offre richiede sacrifici in altre aree, probabilmente rendendo il tuo codice più complesso (o peggio) di quanto sarebbe stato risolvere il problema originale in un modo più convenzionale.

E nella maggior parte dei casi, non è necessario avere quel grado di flessibilità. Nella domanda del PO sui tipi di prodotto, è molto più semplice creare una tabella per tipo di prodotto per attributi specifici del prodotto, quindi è necessario applicare una struttura coerente almeno per le voci dello stesso tipo di prodotto.

Utilizzerei EAV solo se ogni riga deve avere potenzialmente un set distinto di attributi. Quando si dispone di un set finito di tipi di prodotto, EAV è eccessivo. L'ereditarietà delle classi sarebbe la mia prima scelta.


Aggiornamento 2019: più vedo le persone che utilizzano JSON come soluzione per il problema "molti attributi personalizzati", meno mi piace quella soluzione. Rende le query troppo complesse, anche quando si utilizzano funzioni JSON speciali per supportarle. Ci vuole molto più spazio di archiviazione per archiviare documenti JSON, rispetto alla memorizzazione in righe e colonne normali.

Fondamentalmente, nessuna di queste soluzioni è facile o efficiente in un database relazionale. L'intera idea di avere "attributi variabili" è fondamentalmente in contrasto con la teoria relazionale.

Ciò che si riduce è che devi scegliere una delle soluzioni in base alla quale è la meno dannosa per la tua app. Pertanto, è necessario sapere come eseguire la query dei dati prima di scegliere un progetto di database. Non c'è modo di scegliere una soluzione "migliore" perché una qualsiasi delle soluzioni potrebbe essere la migliore per una determinata applicazione.


11
L'opzione "4.5" di @HimalayaGarg è davvero l'opposto dell'intero punto del post di Bill.
user3308043,

2
A differenza di MySQL, SQL Server offre un ampio supporto per XML, XPath e XQuery. Quindi, per gli utenti di SQL Server, l'opzione migliore sarebbe quella di archiviare attributi extra in una colonna di tipo XML (opzione 4). In questo modo NON è necessario "ripristinare l'intero BLOB nell'applicazione e ordinarlo là fuori". È anche possibile creare indici su colonne XML in SQL Server.
Delphi.Boy


2
Preferisco il LOB serializzato per il mio caso. Ma è adatto per ORM? Uso EF.
Mahmood Jenami,

@utente2741577, certo, ma probabilmente dovrai scrivere un codice personalizzato per decomprimere i campi di dati non strutturati dal LOB e applicarli a ciascun campo di entità dell'oggetto ORM. Non conosco EF, ma suppongo che potresti creare una classe ORM di base che faccia questo. È necessario tenere traccia di quali campi provengono da campi concreti della riga del database e quali campi provengono da campi del LOB, quindi è possibile riformattare un LOB quando è il momento di salvare l'oggetto.
Bill Karwin,

12

@Cuore di pietra

Vorrei andare qui con EAV e MVC fino in fondo.

@Bill Karvin

Ecco alcuni degli svantaggi di EAV:

  • Non è possibile rendere obbligatoria una colonna (equivalente a NOT NULL).
  • Non è possibile utilizzare tipi di dati SQL per convalidare le voci.
  • Nessun modo per garantire che i nomi degli attributi siano scritti in modo coerente.
  • Non è possibile inserire una chiave esterna sui valori di un dato attributo, ad esempio per una tabella di ricerca.

Tutte quelle cose che hai menzionato qui:

  • convalida dei dati
  • convalida ortografica dei nomi degli attributi
  • colonne / campi obbligatori
  • gestire la distruzione di attributi dipendenti

a mio avviso, non appartengono affatto a un database perché nessuno dei database è in grado di gestire tali interazioni e requisiti a un livello adeguato come fa un linguaggio di programmazione di un'applicazione.

Secondo me usare un database in questo modo è come usare una roccia per martellare un chiodo. Puoi farlo con una roccia ma non pensi di usare un martello che è più preciso e specificamente progettato per questo tipo di attività?

Il recupero dei risultati in un layout tabellare convenzionale è complesso e costoso, perché per ottenere attributi da più righe è necessario eseguire JOIN per ciascun attributo.

Questo problema può essere risolto eseguendo alcune query su dati parziali ed elaborandoli in layout tabulare con l'applicazione. Anche se si dispone di 600 GB di dati di prodotto, è possibile elaborarli in batch se si richiedono dati da ogni singola riga in questa tabella.

Andare oltre Se si desidera migliorare le prestazioni delle query, è possibile selezionare determinate operazioni come, ad esempio, il reporting o la ricerca di testo globale e preparare per esse tabelle di indice che memorizzerebbero i dati richiesti e verrebbero rigenerati periodicamente, diciamo ogni 30 minuti.

Non è nemmeno necessario preoccuparsi del costo dell'archiviazione di dati extra perché diventa sempre più economico ogni giorno.

Se dovessi comunque preoccuparti delle prestazioni delle operazioni eseguite dall'applicazione, puoi sempre utilizzare Erlang, C ++, Go Language per pre-elaborare i dati e successivamente elaborare ulteriormente i dati ottimizzati nella tua app principale.


you can always use Erlang, C++, Go Language to pre-process the dataCosa intendevi? Invece di DB, usa Go lang? Potresti per favore approfondire questo?
Verde,

1
Sono totalmente d'accordo. EAV è una strada da percorrere, soprattutto se hai bisogno di un livello di flessibilità che ti consenta di aggiungere nuovi tipi di prodotti e parametri senza modifiche allo schema db, intendo vivere in produzione tramite la tua applicazione. Ci sono stato, l'ho fatto. Ha funzionato per me. A proposito di domande lente ... qualcuno qui ha mai sentito parlare di cache? ;)
pawel.kalisz,

@Verde Ho modificato l'ultimo paragrafo per renderlo più chiaro, ma riguarda il passaggio dei dati EAV grezzi a un processo in una lingua in grado di gestire trasformazioni di dati, ricerche in una struttura ad albero o qualsiasi mappa di base per ridurre le operazioni molto rapidamente e in modo efficiente della memoria. I dettagli qui dipenderebbero da ciò che deve essere ottimizzato
Pawel Barcik,

6

Se uso il Class Table Inheritancesignificato:

una tabella per i prodotti, che memorizza gli attributi comuni a tutti i tipi di prodotto. Quindi una tabella per tipo di prodotto, memorizzando gli attributi specifici per quel tipo di prodotto. -Bill Karwin

Che mi piace il migliore dei suggerimenti di Bill Karwin. Posso in qualche modo prevedere uno svantaggio, che cercherò di spiegare come evitare di diventare un problema.

Quale piano di emergenza dovrei avere in atto quando un attributo comune solo a 1 tipo, quindi diventa comune a 2, quindi a 3, ecc.?

Ad esempio: (questo è solo un esempio, non il mio vero problema)

Se vendiamo mobili, potremmo vendere sedie, lampade, divani, TV, ecc. Il tipo di TV potrebbe essere l'unico tipo che trasportiamo che ha un consumo di energia. Quindi metterei l' power_consumptionattributo su tv_type_table. Ma poi iniziamo a trasportare sistemi Home Theater che hanno anche una power_consumptionproprietà. OK, è solo un altro prodotto, quindi aggiungerò anche questo campo stereo_type_table, poiché a questo punto è probabilmente il più semplice. Ma col passare del tempo quando iniziamo a trasportare sempre più elettronica, ci rendiamo conto che power_consumptionè abbastanza ampio da essere inserito nelmain_product_table . Cosa dovrei fare ora?

Aggiungi il campo a main_product_table. Scrivi uno script per scorrere attraverso l'elettronica e inserire il valore corretto da ciascuno type_tablea main_product_table. Quindi rilasciare quella colonna da ciascunotype_table .

Ora, se usassi sempre la stessa GetProductDataclasse per interagire con il database per estrarre le informazioni sul prodotto; quindi se eventuali modifiche al codice ora necessitano di refactoring, dovrebbero appartenere solo a quella classe.


3

È possibile avere una tabella Product e una tabella ProductAdditionInfo separata con 3 colonne: ID prodotto, nome informazioni aggiuntive, valore informazioni aggiuntive. Se il colore viene utilizzato da molti, ma non da tutti i tipi di prodotti, è possibile che sia una colonna nullable nella tabella dei prodotti o semplicemente lo metta in ProductAdditionalInfo.

Questo approccio non è una tecnica tradizionale per un database relazionale, ma in pratica l'ho visto molto usato. Può essere flessibile e avere buone prestazioni.

Steve Yegge chiama questo modello Proprietà e ha scritto un lungo post sull'utilizzo.


4
Il modello Proprietà è solo Entity-Attribute-Value con un altro nome. È ampiamente utilizzato, ma memorizzarlo in un database relazionale infrange le regole di normalizzazione.
Bill Karwin,

2
Ad essere sincero, quando ho letto la descrizione di EAV nella risposta di @Bills non ho capito bene cosa stesse spiegando. Ma quando hai detto che 3 columns: product ID, additional info name, additional info valueho capito il concetto. E in realtà l'ho già fatto prima e ho riscontrato problemi. Tuttavia, al momento non ricordo quali fossero questi problemi.
JD Isaacks,

1
@JDIsaacks In questo modello, un problema comune è che non sappiamo quanti JOIN sono necessari per recuperare tutti gli attributi.
Omid
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.