L'uso di più chiavi esterne separate da virgole è errato e, in caso affermativo, perché?


31

Ci sono due tabelle: Deale DealCategories. Un affare può avere molte categorie di affari.

Quindi il modo corretto dovrebbe essere quello di creare una tabella chiamata DealCategoriescon la seguente struttura:

DealCategoryId (PK)
DealId (FK)
DealCategoryId (FK)

Tuttavia, il nostro team di outsourcing ha memorizzato le molteplici categorie nella Dealtabella in questo modo:

DealId (PK)
DealCategory -- In here they store multiple deal ids separated by commas like this: 18,25,32.

Sento che quello che hanno fatto è sbagliato, ma non so come spiegare chiaramente perché questo non sia giusto.

Come dovrei spiegare loro che questo è sbagliato? O forse sono io quello che ha torto e questo è accettabile?



7
licenzia subito quella squadra in outsourcing prima che facciano più danno ... (-_-)
Rafa

Risposte:


49

Sì, è un'idea terribile.

Invece di andare:

SELECT Deal.Name, DealCategory.Name
FROM Deal
  INNER JOIN
     DealCategories ON Deal.DealID = DealCategories.DealID
  INNER JOIN
     DealCategory ON DealCategories.DealCategoryID = DealCategory.DealCategoryID
WHERE Deal.DealID = 1234

Ora devi andare:

SELECT Deal.ID, Deal.Name, DealCategories
FROM Deal
WHERE Deal.DealID = 1234

Quindi è necessario eseguire operazioni nel codice dell'applicazione per suddividere l'elenco di virgole in singoli numeri, quindi interrogare separatamente il database:

SELECT DealCategory.Name
FROM DealCategory
WHERE DealCategory.DealCategoryID IN (<<that list from before>>)

Questo design antipattern nasce da un completo fraintendimento della modellazione relazionale (non devi aver paura dei tavoli. I tavoli sono i tuoi amici. Usali), o una convinzione bizzarramente sbagliata è più veloce prendere un elenco separato da virgole e dividerlo nel codice dell'applicazione rispetto ad aggiungere una tabella dei collegamenti ( non lo è mai ). La terza opzione è che non sono abbastanza sicuri / competenti con SQL per essere in grado di impostare chiavi esterne, ma in tal caso non dovrebbero avere nulla a che fare con la progettazione di un modello relazionale.

SQL Antipatterns (Karwin, 2010) dedica un intero capitolo a questo antipattern (che lui chiama "Jaywalking"), pagine 15-23. Inoltre, l'autore ha pubblicato una domanda simile su SO . I punti chiave che nota (come applicato in questo esempio) sono:

  • Interrogare per tutte le offerte in una categoria specifica è piuttosto complicato (il modo più semplice per risolvere quel problema è un'espressione regolare, ma un'espressione regolare è un problema in sé e per sé).
  • Non è possibile applicare l'integrità referenziale senza relazioni di chiave esterna. Se elimini DealCategory nr. # 26, quindi, nel codice dell'applicazione, devi esaminare ogni affare alla ricerca di riferimenti alla categoria # 26 ed eliminarli. Questo è qualcosa che dovrebbe essere gestito a livello di dati e doverlo gestire nell'applicazione è una cosa molto brutta .
  • Le query aggregate ( COUNT, SUMecc.), Di nuovo, variano da "complicate" a "quasi impossibili". Chiedi ai tuoi sviluppatori come ti farebbero ottenere un elenco di tutte le categorie con un conteggio del numero di offerte in quella categoria. Con un design adeguato, sono quattro righe di SQL.
  • Gli aggiornamenti diventano molto più difficili (vale a dire che hai un affare che è in cinque categorie, ma vuoi rimuoverne due e aggiungerne altre tre). Sono tre righe di SQL con un design adeguato.
  • Alla fine ti imbatterai in VARCHARlimiti di lunghezza dell'elenco. Anche se se hai un elenco separato da virgole di oltre 4000 caratteri, è probabile che il mostro stia andando lento come l'inferno.
  • Estrarre un elenco dal database, suddividerlo e quindi tornare al database per un'altra query è intrinsecamente più lento di una query.

TLDR: è un design fondamentalmente imperfetto, non si ridimensiona bene, introduce ulteriore complessità anche per le query più semplici e, immediatamente, rallenta l'applicazione.


1
Simone, qualcuno ha fatto la stessa domanda ( dba.stackexchange.com/questions/17824/… ), ma non ho chiaro perché gli stessi FK e PK siano nella stessa tabella, che frenano il 3FN.
jcho360,

2
Non ero del tutto sicuro se volessero avere una relazione molti-a-molti tra Affari e Categorie, o una sorta di eredità delle Categorie. Ad ogni modo, è stato un margine laterale al punto principale, che essendo campi delimitati da virgole anziché una tabella di collegamenti è una cattiva idea.
Simon Righarts,

4

Tuttavia, il nostro team di outsourcing ha archiviato le diverse categorie nella tabella Deal in questo modo:

DealId (PK) DealCategory - Qui memorizzano più ID affare separati da virgole come questo: 18,25,32.

Questo è in realtà un buon design se devi solo cercare le categorie per un determinato affare.

Ma è terribile se vuoi conoscere tutte le offerte in una determinata categoria.

Inoltre, rende molto difficile e soggetto a errori fare qualsiasi altra cosa, come aggiornamenti, conteggi, join, ecc.

La denormalizzazione ha il suo posto, ma devi tenere a mente che ottimizza per un tipo di query a spese di tutti gli altri che potresti fare con gli stessi dati. Se sai che eseguirai sempre una query in un modello, potrebbe darti un vantaggio nell'utilizzare il design denormalizzato. Ma se c'è qualche possibilità che tu possa avere bisogno di maggiore flessibilità nei tipi di query, segui un design normalizzato.

Come ogni altra forma di ottimizzazione, devi sapere quali query eseguirai prima di poter decidere se la denormalizzazione è giustificata.


1
Pensi davvero che una stringa con ID figlio separati da virgola sia utile? Voglio dire, l'applicazione ha dovuto prima leggere, quindi analizzare gli ID e interrogare tutti i bambini, come select * from DealCategories where DealId in (1,2,3,4,...). Hai più esperienza, riguardo alla progettazione di database, di me, quindi forse hai delle buone ragioni in alcuni casi per una tale "ottimizzazione estrema" in casi molto specifici. La mia unica idea per giustificare questo è un selectcarico molto elevato su Deal / DealCategory. Questo mi sembra molto simile a un team di outsourcing senza alcuna conoscenza del progetto DB, oltre a creare tabelle, lo ha creato.
Erik Hart,

1
@ErikHart, questa è denormalizzazione e può essere utile, ma il mio punto è che dipende interamente dalle query che devi eseguire. Hai ragione sul fatto che la denormalizzazione rende peggiori tutte le query, tranne quella per cui ottimizza. Se devi solo eseguire quella query e non ti interessano le altre query, è una vittoria. Ma questi sono casi rari, perché in genere desideriamo flessibilità per eseguire query sui dati in vari modi.
Bill Karwin,

1
@ErikHart, se a quel team di outsourcing fossero state fornite specifiche di progetto che includevano solo una query rispetto a questi dati, avrebbero potuto progettare un'ottimizzazione solo per quella specifica query. In altre parole, "l'hai chiesto, l'hai preso". Ma il fornitore di outsourcing non ha motivo di pianificare usi futuri dei dati: implementano l'applicazione sulla lettera di ciò che è scritto nelle specifiche.
Bill Karwin,

1

Più valori in una colonna sono in contrasto con la prima forma normale.

Inoltre non ha assolutamente alcun guadagno di velocità, poiché le tabelle devono essere collegate nel database. Devi prima leggere e analizzare una stringa, quindi selezionare tutte le categorie per il "Deal".

L'implementazione corretta sarebbe una tabella di giunzione come "DealDealCategories", con DealId e DealCategoryId.

Cattiva implementazione della gerarchia?

Inoltre, un FK in DealCategories a un altro DealCategory sembra una cattiva implementazione di una gerarchia / albero di DealCategories. Lavorare con gli alberi attraverso una relazione ID genitore (la cosiddetta lista di adiacenza) è una seccatura!

Controlla i set nidificati (buono da leggere, ma difficile da modificare) e le Tabelle di chiusura (migliori prestazioni complessive, ma probabilmente un uso elevato della memoria - probabilmente non troppo per le tue DealCategories) durante l'implementazione delle gerarchie!

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.