Memorizzare un elenco delimitato in una colonna del database è davvero così male?


363

Immagina un modulo web con una serie di caselle di controllo (è possibile selezionarne una o tutte). Ho scelto di salvarli in un elenco separato da virgole di valori memorizzati in una colonna della tabella del database.

Ora, so che la soluzione corretta sarebbe quella di creare una seconda tabella e normalizzare correttamente il database. È stata più rapida implementare la soluzione semplice e volevo avere una prova di concetto di quell'applicazione in modo rapido e senza doverci dedicare troppo tempo.

Ho pensato che il tempo risparmiato e il codice più semplice fossero valsi la pena nella mia situazione, è una scelta progettuale difendibile o avrei dovuto normalizzarlo dall'inizio?

Un po 'più di contesto, questa è una piccola applicazione interna che essenzialmente sostituisce un file Excel archiviato in una cartella condivisa. Lo chiedo anche perché sto pensando di ripulire il programma e renderlo più gestibile. Ci sono alcune cose in cui non sono del tutto contento, uno di questi è l'argomento di questa domanda.


21
in tal caso, perché disturbare il database ?, farà il salvataggio in un file.
Thavan,

6
Concordato con @thavan. Perché anche salvare i dati per una prova di concetto? Dopo aver completato la prova, quindi aggiungere un database correttamente. Stai bene facendo il peso leggero per la prova del concetto, semplicemente non fare cose che devi disfare in seguito.
Jeff Davis,

1
In Postgres, una colonna di array dovrebbe essere preferita a un elenco separato da virgole. Ciò garantisce almeno il tipo di dati corretto, non ha problemi a distinguere il delimitatore dai dati effettivi e può essere indicizzato in modo efficiente.
a_horse_with_no_name

Risposte:


568

Oltre a violare la prima forma normale a causa del gruppo ripetuto di valori archiviati in una singola colonna, gli elenchi separati da virgole presentano molti altri problemi più pratici:

  • Non è possibile garantire che ogni valore sia il giusto tipo di dati: nessun modo per prevenire 1,2,3, banana, 5
  • Impossibile utilizzare i vincoli di chiave esterna per collegare i valori a una tabella di ricerca; nessun modo per applicare l'integrità referenziale.
  • Impossibile applicare l'unicità: nessun modo per prevenire 1,2,3,3,3,5
  • Impossibile eliminare un valore dall'elenco senza recuperare l'intero elenco.
  • Non è possibile memorizzare un elenco più lungo di quello che si adatta nella colonna stringa.
  • Difficile cercare tutte le entità con un determinato valore nell'elenco; devi usare una scansione della tabella inefficiente. Potrebbe essere necessario ricorrere a espressioni regolari, ad esempio in MySQL:
    idlist REGEXP '[[:<:]]2[[:>:]]'*
  • Difficile contare gli elementi nell'elenco o eseguire altre query aggregate.
  • Difficile unire i valori alla tabella di ricerca a cui fanno riferimento.
  • Difficile recuperare l'elenco in ordine.

Per risolvere questi problemi, devi scrivere tonnellate di codice applicativo, reinventando le funzionalità che RDBMS fornisce già in modo molto più efficiente .

Le liste separate da virgole sono abbastanza sbagliate che ho reso questo il primo capitolo del mio libro: SQL Antipatterns: evitare le insidie ​​della programmazione del database .

Ci sono momenti in cui è necessario utilizzare la denormalizzazione, ma come menziona @OMG Ponies , si tratta di casi di eccezione. Qualsiasi "ottimizzazione" non relazionale avvantaggia un tipo di query a scapito di altri usi dei dati, quindi assicurati di sapere quali delle tue query devono essere trattate in modo così speciale da meritare denormalizzazione.


* MySQL 8.0 non supporta più questa sintassi di espressione al limite di parole.


8
Un ARRAY (di qualsiasi tipo di dati) può risolvere l'eccezione, basta controllare PostgreSQL: postgresql.org/docs/current/static/arrays.html (@Bill: ottimo libro, un must da leggere per qualsiasi sviluppatore o dba)
Frank Heikens

4
+1 fattura Karwin Ottima risposta! Incantevoli punti elenco concisi. Anche quello sembra un grande libro. Adoro anche la copertina +1 NullUserException. Sto progettando lo schema per un database MySQL per sostituire un sistema basato su testo flat file. Finora ho incontrato diversi dilemmi. Quindi vale la pena comprare questo libro.
therobyouknow,

2
Anche il sito pragprog.com ha un bell'aspetto: stile, layout, pulizia intuitivi. Deve essere abbastanza nuovo, non sono stato in grado di acquistare i loro e-book in passato. PS. Non lavoro per loro hanno alcun legame con gli autori. Mi piace celebrare buoni prodotti, servizi e aiuto quando lo vedo.
therobyouknow,

2
Sul lato serio, aggiungerei alla tua lista: Difficile da cercare. Supponiamo che tu desideri tutti i record che includono "2". Ovviamente non puoi semplicemente cercare foobar = '2' perché perderebbe se ci fossero altri valori. Non puoi cercare foobar come '% 2%' perché ciò otterrebbe risultati falsi per 12 e 28 e così via. Non puoi cercare foobar come '%, 2,%' perché 2 potrebbe essere il primo o l'ultimo elemento dell'elenco e quindi avere solo una di quelle virgole.
Jay,

2
So che non è raccomandato, ma interpretare l'avvocato dei diavoli: la maggior parte di questi può essere tolta se esiste un'interfaccia utente che gestisce l'unicità e i tipi di dati (altrimenti si verificherebbe un errore o si comporterebbe male), scende e lo crea comunque, c'è una tabella dei driver in cui i valori vengono da per renderli unici, campo come '% P%' può essere usato, valori essendo P, R, S, T, il conteggio non ha importanza e l'ordinamento non ha importanza. A seconda dell'interfaccia utente, i valori possono essere divisi [] ad esempio per selezionare le caselle di controllo in un elenco dalla tabella dei driver nello scenario meno comune senza dover passare a un'altra tabella per ottenerli.
jmcclure,

44

"Una ragione era la pigrizia".

Questo suona un campanello d'allarme. L'unica ragione per cui dovresti fare qualcosa del genere è che sai come farlo "nel modo giusto" ma sei giunto alla conclusione che esiste un motivo tangibile per non farlo in quel modo.

Detto questo: se i dati che si sceglie di archiviare in questo modo sono dati per i quali non sarà mai necessario eseguire una query, potrebbe essere necessario archiviarli nel modo scelto.

(Alcuni utenti contesterebbero l'affermazione del mio paragrafo precedente, dicendo che "non si può mai sapere quali requisiti verranno aggiunti in futuro". Questi utenti sono o fuorviati o affermano una convinzione religiosa. A volte è vantaggioso lavorare secondo i requisiti ho davanti a te.)


Sento sempre alcune persone dire che "il mio design è più flessibile del tuo" quando li confronto su cose come non impostare vincoli di chiave esterna o memorizzare elenchi in un singolo campo. Per me, flessibilità (in questi casi) == nessuna disciplina == pigrizia.
foresightyj

41

Esistono numerose domande su SO:

  • come ottenere un conteggio di valori specifici dall'elenco separato da virgole
  • come ottenere record che hanno solo lo stesso valore specifico 2/3 / etc da quell'elenco separato da virgole

Un altro problema con l'elenco separato da virgole è garantire che i valori siano coerenti: la memorizzazione del testo significa la possibilità di errori di battitura ...

Questi sono tutti sintomi di dati denormalizzati ed evidenziano perché dovresti sempre modellare i dati normalizzati. La denormalizzazione può essere un'ottimizzazione della query, da applicare quando la necessità si presenta effettivamente .


19

In generale, qualsiasi cosa può essere difendibile se soddisfa i requisiti del progetto. Questo non significa che le persone saranno d'accordo o vorranno difendere la tua decisione ...

In generale, la memorizzazione di dati in questo modo non è ottimale (ad es. È più difficile eseguire query efficienti) e può causare problemi di manutenzione se si modificano gli articoli nel modulo. Forse avresti potuto trovare una via di mezzo e utilizzare un numero intero che rappresenta un insieme di bit flag?


10

Sì, direi che è davvero così male. È una scelta difendibile, ma ciò non lo rende corretto o buono.

Si rompe prima forma normale.

Una seconda critica è che l'inserimento di risultati di input non elaborati direttamente in un database, senza alcuna convalida o associazione, ti lascia aperto agli attacchi di iniezione SQL.

Ciò che chiami pigrizia e mancanza di conoscenza di SQL è la sostanza di cui sono fatti i neofiti. Consiglierei di prenderti il ​​tempo per farlo correttamente e di vederlo come un'opportunità per imparare.

O lascialo così com'è e impara la dolorosa lezione di un attacco di iniezione SQL.


19
Non vedo nulla in questa domanda che suggerisce che sia vulnerabile all'iniezione di SQL. L'iniezione di SQL e la normalizzazione del database sono argomenti ortogonali e la tua digressione sull'iniezione è irrilevante per la domanda.
Hammerite,

5
@Paul: E forse lo stesso atteggiamento lo porterà a essere investito da un autobus quando non riesce a guardare in entrambi i modi prima di attraversare la strada, ma non lo hai avvertito. Modifica: Pensavo fossi il poster di questa risposta, errore mio.
Hammerite,

1
@Hammerite: la tua estrapolazione agli autobus è ridicola.
duffymo,

4
Sì, doveva essere ridicolo. La sua ridicolità illustra il punto che sto sottolineando, ovvero che non ha senso metterlo in guardia contro qualcosa di cui non hai motivo di pensare che debba essere avvertito.
Hammerite,

1
Sì, vedo. Penso di avere molte più ragioni del tuo avvertimento sugli autobus.
duffymo,

7

Bene, sto usando un elenco separato da una coppia chiave / valore in una colonna NTEXT in SQL Server da oltre 4 anni e funziona. Si perde la flessibilità di fare query ma, d'altra parte, se si dispone di una libreria che persiste / derpersiste la coppia chiave-valore, non è una cattiva idea.


13
No, è un'idea orribile. Sei riuscito a cavartela, ma il costo dei tuoi pochi minuti di tempo di sviluppo ti è costato pessime prestazioni, flessibilità e manutenibilità del tuo codice.
Paul Tomblin,

5
Paul, sono d'accordo. Ma come ho detto, ho usato se per uno scopo specifico, e questo è per un'operazione di immissione dei dati in cui hai molti tipi di moduli. Sto rivedendo il progetto ora che ho imparato NHibernate ma allora avevo bisogno della flessibilità per progettare il modulo in ASP.NET e usare gli ID casella di testo come chiave nella coppia chiave / valore.
Raj,

28
+1 solo per contrastare i voti negativi. Dire a qualcuno che ha gestito l'app per 4 anni per problemi di manutenzione è un po 'presuntuoso. Ci sono pochissime idee "orribili" nello sviluppo di sw - per lo più sono solo idee con applicabilità molto limitata. È ragionevole avvertire la gente dei limiti, ma rimproverare coloro che l'hanno fatto e lo hanno vissuto mi sembra un atteggiamento più santo del quale non posso fare a meno.
Mark Brackett,

7

Avevo bisogno di una colonna multi-valore, che potrebbe essere implementata come un campo XML

Potrebbe essere convertito in una virgola delimitata, se necessario

interrogare un elenco XML nel server sql usando Xquery .

Essendo un campo XML, alcune delle preoccupazioni possono essere risolte.

Con CSV: non è possibile garantire che ogni valore sia il giusto tipo di dati: nessun modo per prevenire 1,2,3, banana, 5

Con XML: i valori in un tag possono essere forzati ad essere il tipo corretto


Con CSV: impossibile utilizzare vincoli di chiave esterna per collegare valori a una tabella di ricerca; nessun modo per applicare l'integrità referenziale.

Con XML: ancora un problema


Con CSV: impossibile applicare l'unicità: nessun modo per prevenire 1,2,3,3,3,5

Con XML: ancora un problema


Con CSV: impossibile eliminare un valore dall'elenco senza recuperare l'intero elenco.

Con XML: singoli elementi possono essere rimossi


Con CSV: è difficile cercare tutte le entità con un determinato valore nell'elenco; devi usare una scansione della tabella inefficiente.

Con XML: il campo xml può essere indicizzato


Con CSV: difficile contare gli elementi nell'elenco o eseguire altre query aggregate. **

Con XML: non particolarmente difficile


Con CSV: difficile unire i valori alla tabella di ricerca a cui fanno riferimento. **

Con XML: non particolarmente difficile


Con CSV: difficile recuperare l'elenco in ordine.

Con XML: non particolarmente difficile


Con CSV: la memorizzazione di numeri interi come stringhe occupa circa il doppio dello spazio rispetto alla memorizzazione di numeri interi binari.

Con XML: l' archiviazione è persino peggiore di un CSV


Con CSV: oltre a molti caratteri virgola.

Con XML: i tag vengono utilizzati al posto delle virgole


In breve, l'uso di XML risolve alcuni dei problemi con un elenco delimitato E può essere convertito in un elenco delimitato, se necessario


6

Sì, è così male. La mia opinione è che se non ti piace usare i database relazionali, cerca un'alternativa più adatta a te, ci sono molti progetti "NOSQL" interessanti con alcune funzionalità davvero avanzate.


0

Probabilmente prenderei la via di mezzo: trasformerei ogni campo nel CSV in una colonna separata nel database, ma non preoccuparti molto della normalizzazione (almeno per ora). Ad un certo punto, la normalizzazione potrebbe diventare interessante, ma con tutti i dati inseriti in una singola colonna non si ottiene praticamente alcun vantaggio dall'uso di un database. È necessario separare i dati in campi / colonne logici / come li si desidera chiamare prima di poterli manipolare in modo significativo.


Il modulo contiene alcuni altri campi, questa è solo una parte del modulo (che non ho spiegato bene nella domanda).
Scienziato pazzo,

0

Se hai un numero fisso di campi booleani, puoi usare un INT(1) NOT NULL(o BIT NOT NULLse esiste) o CHAR (0)(nullable) per ciascuno. Puoi anche usare un SET(ho dimenticato la sintassi esatta).


1
INT(1)richiede 4 byte; il (1)non ha senso.
Rick James,
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.