Come memorizzare un elenco in una colonna di una tabella di database


115

Quindi, in base alla risposta di Mehrdad a una domanda correlata , ho capito che una colonna della tabella di database "corretta" non memorizza un elenco. Piuttosto, dovresti creare un'altra tabella che contenga efficacemente gli elementi di tale elenco e quindi collegarti ad essa direttamente o tramite una tabella di giunzione. Tuttavia, il tipo di elenco che voglio creare sarà composto da elementi unici (a differenza del frutto della domanda collegataesempio). Inoltre, gli elementi nel mio elenco sono ordinati in modo esplicito, il che significa che se memorizzassi gli elementi in un'altra tabella, dovrei ordinarli ogni volta che li accedo. Infine, l'elenco è fondamentalmente atomico in quanto ogni volta che desidero accedere all'elenco, voglio accedere all'intero elenco anziché solo a una parte di esso, quindi sembra sciocco dover emettere una query al database per raccogliere insieme pezzi di la lista.

La soluzione di AKX (collegata sopra) è serializzare l'elenco e memorizzarlo in una colonna binaria. Ma questo sembra anche scomodo perché significa che devo preoccuparmi della serializzazione e deserializzazione.

C'è una soluzione migliore? Se non v'è alcuna soluzione migliore, allora perché? Sembra che questo problema dovrebbe sorgere di tanto in tanto.

... solo qualche informazione in più per farti sapere da dove vengo. Non appena ho iniziato a capire SQL e i database in generale, sono passato a LINQ to SQL, quindi ora sono un po 'viziato perché mi aspetto di occuparmi del mio modello di oggetti di programmazione senza dover pensare a come gli oggetti vengono interrogati o archiviati nel database.

Ringrazia tutti!

John

AGGIORNAMENTO: Quindi nella prima raffica di risposte che ricevo, vedo "puoi seguire il percorso CSV / XML ... ma NON!". Quindi ora sto cercando spiegazioni sul perché. Indicami alcuni buoni riferimenti.

Inoltre, per darti un'idea migliore di ciò che sto facendo: Nel mio database ho una tabella delle funzioni che avrà un elenco di coppie (x, y). (La tabella conterrà anche altre informazioni che non hanno importanza per la nostra discussione.) Non avrò mai bisogno di vedere parte dell'elenco delle coppie (x, y). Piuttosto, li prenderò tutti e li traccerò sullo schermo. Consentirò all'utente di trascinare i nodi per modificare i valori occasionalmente o aggiungere più valori al grafico.

Risposte:


183

No, non esiste un modo "migliore" per memorizzare una sequenza di elementi in una singola colonna. I database relazionali sono progettati specificamente per memorizzare un valore per combinazione riga / colonna. Per memorizzare più di un valore, è necessario serializzare l'elenco in un singolo valore per l'archiviazione, quindi deserializzarlo al momento del recupero. Non c'è altro modo per fare quello di cui stai parlando (perché quello di cui stai parlando è una cattiva idea che, in generale, non dovrebbe mai essere fatta ).

Capisco che pensi sia sciocco creare un'altra tabella per memorizzare quell'elenco, ma questo è esattamente ciò che fanno i database relazionali. Stai combattendo una battaglia in salita e stai violando uno dei principi più basilari della progettazione di database relazionali senza una buona ragione. Dal momento che affermi che stai solo imparando SQL, ti consiglio vivamente di evitare questa idea e di attenersi alle pratiche consigliate dagli sviluppatori SQL più esperti.

Il principio che stai violando è chiamato prima forma normale , che è il primo passo nella normalizzazione del database.

Con il rischio di semplificare eccessivamente le cose, alla normalizzazione dei database è il processo di definizione del database in base a ciò che i dati è , in modo da poter scrivere sensibili, le query coerenti contro di esso ed essere in grado di mantenere facilmente. La normalizzazione è progettata per limitare le incongruenze logiche e il danneggiamento dei dati e ci sono molti livelli. L'articolo di Wikipedia sulla normalizzazione del database è in realtà piuttosto buono.

Fondamentalmente, la prima regola (o forma) di normalizzazione afferma che la tua tabella deve rappresentare una relazione. Ciò significa che:

  • Devi essere in grado di differenziare una riga da qualsiasi altra riga (in altre parole, la tabella deve avere qualcosa che possa fungere da chiave primaria. Ciò significa anche che nessuna riga deve essere duplicata.
  • Qualsiasi ordinamento dei dati deve essere definito dai dati, non dall'ordinamento fisico delle righe (SQL si basa sull'idea di un insieme, il che significa che l' unico ordine su cui dovresti fare affidamento è quello che definisci esplicitamente nella tua query)
  • Ogni intersezione riga / colonna deve contenere uno e un solo valore

L'ultimo punto è ovviamente il punto saliente qui. SQL è progettato per memorizzare i tuoi set per te, non per fornirti un "secchio" per te stesso per memorizzare un set. Sì, è possibile farlo. No, il mondo non finirà. Tuttavia, ti sei già paralizzato nella comprensione di SQL e delle migliori pratiche che lo accompagnano saltando immediatamente nell'uso di un ORM. LINQ to SQL è fantastico, proprio come lo sono le calcolatrici grafiche. Allo stesso modo, tuttavia, dovrebbero non essere utilizzati come un sostituto per sapere come i processi che impiegano in realtà di lavoro.

La tua lista potrebbe essere completamente "atomica" ora, e questo potrebbe non cambiare per questo progetto. Tuttavia, ti abituerai a fare cose simili in altri progetti e alla fine (probabilmente rapidamente) ti imbatterai in uno scenario in cui ora stai inserendo il tuo elenco in una colonna facile e veloce approccio dove è del tutto inappropriato. Non c'è molto lavoro aggiuntivo nella creazione della tabella corretta per ciò che stai cercando di memorizzare e non sarai deriso dagli altri sviluppatori SQL quando vedranno la progettazione del tuo database. Inoltre, LINQ to SQL è andare a vedere il vostro rapporto e vi darà l'interfaccia orientata agli oggetti adeguata alla vostra lista automaticamente . Perché rinunciare alla comodità offerta dall'ORM in modo da poter eseguire hacker di database non standard e sconsiderati?


17
Quindi credi fermamente che memorizzare un elenco in una colonna sia una cattiva idea, ma non dici perché. Dato che ho appena iniziato con SQL, un po 'del "perché" sarebbe davvero molto utile. Ad esempio, dici che sto "combattendo una battaglia in salita e violando uno dei principi più basilari della progettazione di database relazionali senza una buona ragione" ... quindi qual è il principio? Perché i motivi che ho citato "non vanno bene"? (in particolare, la natura ordinata e atomica delle mie liste)
JnBrymn

6
Fondamentalmente, si tratta di anni di esperienza condensati in migliori pratiche. Il principale di base in questione è noto come 1a forma normale .
Toby

1
Grazie Adam. Molto informativo. Buon punto con la tua ultima domanda.
JnBrymn

8
"[...] e non sarai deriso dagli altri sviluppatori SQL quando vedranno il design del tuo database." Ci sono ottime ragioni per rispettare la Prima Forma Normale (e la tua risposta le menziona), ma la pressione dei pari / "è così che si fanno le cose qui" non è tra queste.
Lynn

5
Memorizziamo già mazzi di elenchi nelle colonne del database ogni giorno. Si chiamano "char" e "varchar". Ovviamente in Postgres si chiamano anche testo. Ciò che l'1NF dice davvero è che non dovresti mai voler suddividere le informazioni in alcun campo in campi più piccoli, e se lo fai, sei un pasticcio. Quindi non memorizzi il nome, memorizzi il nome personale, il secondo nome e il cognome (a seconda della localizzazione) e li unisci insieme. Altrimenti non memorizzeremo affatto stringhe di testo. D'altra parte, tutto ciò che vuole è una stringa di stringhe. E ci sono modi per farlo.
Haakon Løtveit

15

Puoi semplicemente dimenticare SQL tutti insieme e andare con un approccio "NoSQL". RavenDB , MongoDB e CouchDB saltano alla mente come possibili soluzioni. Con un approccio NoSQL, non stai usando il modello relazionale ... non sei nemmeno vincolato agli schemi.


11

Quello che ho visto fare a molte persone è questo (potrebbe non essere l'approccio migliore, correggimi se sbaglio):

La tabella che sto usando nell'esempio è riportata di seguito (la tabella include i soprannomi che hai dato alle tue amiche specifiche. Ogni ragazza ha un ID univoco):

nicknames(id,seq_no,names)

Supponi di voler memorizzare molti soprannomi sotto un ID. Questo è il motivo per cui abbiamo incluso un seq_nocampo.

Ora, inserisci questi valori nella tua tabella:

(1,1,'sweetheart'), (1,2,'pumpkin'), (2,1,'cutie'), (2,2,'cherry pie')

Se vuoi trovare tutti i nomi che hai dato alla tua ragazza id 1, puoi usare:

select names from nicknames where id = 1;

5

Risposta semplice: se, e solo se, sei certo che l'elenco verrà sempre utilizzato come elenco, unisciti alla lista alla tua estremità con un carattere (come "\ 0") che non verrà utilizzato nel testo mai e memorizzalo. Quindi, quando lo recuperi, puoi dividerlo per "\ 0". Ci sono ovviamente altri modi per affrontare queste cose, ma questi dipendono dal tuo specifico fornitore di database.

Ad esempio, puoi memorizzare JSON in un database Postgres. Se la tua lista è di testo e vuoi solo la lista senza ulteriori problemi, è un ragionevole compromesso.

Altri hanno azzardato suggerimenti per la serializzazione, ma non credo proprio che la serializzazione sia una buona idea: parte della cosa bella dei database è che diversi programmi scritti in lingue diverse possono parlare tra loro. E i programmi serializzati utilizzando il formato Java non funzionerebbero molto bene se un programma Lisp volesse caricarlo.

Se vuoi un buon modo per fare questo genere di cose, di solito sono disponibili tipi di array o simili. Postgres, ad esempio, offre un array come tipo e ti consente di memorizzare un array di testo, se è quello che vuoi , e ci sono trucchi simili per MySql e MS SQL che utilizzano JSON, e DB2 di IBM offre anche un tipo di array (nel loro propria documentazione utile ). Questo non sarebbe così comune se non ce ne fosse bisogno.

Quello che perdi andando su quella strada è l'idea dell'elenco come un insieme di cose in sequenza. Almeno nominalmente, i database trattano i campi come valori singoli. Ma se è tutto ciò che vuoi, allora dovresti provarci. È un giudizio di valore che devi fare per te stesso.


3

Oltre a quanto hanno detto tutti gli altri, ti suggerirei di analizzare il tuo approccio in termini più lunghi rispetto a adesso. E ' attualmente il caso che gli elementi sono unici. E ' attualmente il caso che il ricorso gli elementi richiederebbe una nuova lista. È quasi necessario che l'elenco sia attualmente breve. Anche se non ho le specifiche del dominio, non è un granché pensare che tali requisiti possano cambiare. Se serializzi la tua lista, stai cuocendo con un'inflessibilità che non è necessaria in un design più normalizzato. A proposito, questo non significa necessariamente un rapporto Molti: Molti completo. Potresti avere solo una singola tabella figlio con una chiave esterna per il genitore e una colonna di caratteri per l'elemento.

Se vuoi ancora seguire questa strada per serializzare l'elenco, potresti considerare di archiviare l'elenco in XML. Alcuni database come SQL Server hanno anche un tipo di dati XML. L'unico motivo per cui suggerirei XML è che, quasi per definizione, questo elenco deve essere breve. Se l'elenco è lungo, serializzarlo in generale è un approccio orribile. Se segui il percorso CSV, devi tenere conto dei valori contenenti il ​​delimitatore, il che significa che sei obbligato a utilizzare identificatori tra virgolette. Supponendo che gli elenchi siano brevi, probabilmente non farà molta differenza se si utilizza CSV o XML.


+1 per anticipare i cambiamenti futuri: progetta sempre il tuo modello di dati in modo che sia estensibile.
coolgeek

2

Lo memorizzerei come CSV, se si tratta di valori semplici, dovrebbe essere tutto ciò di cui hai bisogno (XML è molto dettagliato e la serializzazione da / verso sarebbe probabilmente eccessiva, ma sarebbe anche un'opzione).

Ecco una buona risposta su come estrarre CSV con LINQ.


Ci ho pensato. Significa ancora che dovrei serializzare e deserializzare ... ma sospetto che sia fattibile. Vorrei che ci fosse un modo condonato per fare quello che voglio, ma sospetto che non ci sia.
JnBrymn

capnproto.org è un modo per non dover serializzare e deserializzare, altrettanto veloce (rispetto a csv o xml) nel caso in cui capnproto non sia supportato nella lingua scelta msgpack.org/index.html
VoronoiPotato

2

Se è necessario eseguire una query nell'elenco, archiviarlo in una tabella.

Se vuoi sempre l'elenco, puoi memorizzarlo come elenco delimitato in una colonna. Anche in questo caso, a meno che tu non abbia ragioni MOLTO specifiche per non farlo, memorizzalo in una tabella di ricerca.


1

Solo un'opzione non è menzionata nelle risposte. Puoi denormalizzare il tuo design DB. Quindi hai bisogno di due tavoli. Una tabella contiene l'elenco corretto, un elemento per riga, un'altra tabella contiene l'intero elenco in una colonna (separata da virgola, ad esempio).

Ecco il design DB "tradizionale":

List(ListID, ListName) 
Item(ItemID,ItemName) 
List_Item(ListID, ItemID, SortOrder)

Ecco la tabella denormalizzata:

Lists(ListID, ListContent)

L'idea qui: mantenere la tabella degli elenchi utilizzando trigger o codice dell'applicazione. Ogni volta che modifichi il contenuto List_Item, le righe appropriate in Lists vengono aggiornate automaticamente. Se leggi principalmente elenchi, potrebbe funzionare abbastanza bene. Pro: puoi leggere gli elenchi in una dichiarazione. Contro: gli aggiornamenti richiedono più tempo e sforzi.


0

Se si desidera davvero memorizzarlo in una colonna e renderlo interrogabile, molti database ora supportano XML. Se non esegui query, puoi memorizzarli come valori separati da virgole e analizzarli con una funzione quando ne hai bisogno. Sono d'accordo con tutti gli altri, anche se se stai cercando di utilizzare un database relazionale, una parte importante della normalizzazione è la separazione dei dati in questo modo. Tuttavia, non sto dicendo che tutti i dati si adattino a un database relazionale. Puoi sempre esaminare altri tipi di database se molti dei tuoi dati non si adattano al modello.


0

Penso che in alcuni casi si possa creare una FALSA "lista" di articoli nel database, ad esempio, la merce ha alcune immagini per mostrarne i dettagli, è possibile concatenare tutti gli ID delle immagini divisi da virgola e memorizzare la stringa in il DB, quindi devi solo analizzare la stringa quando ne hai bisogno. Sto lavorando a un sito web adesso e ho intenzione di usarlo in questo modo.


0

Ero molto riluttante a scegliere la strada che alla fine ho deciso di intraprendere a causa delle tante risposte. Mentre aggiungono più comprensione a ciò che è SQL e ai suoi principi, ho deciso di diventare un fuorilegge. Ero anche riluttante a pubblicare le mie scoperte poiché per alcuni è più importante sfogare la frustrazione su qualcuno che infrange le regole piuttosto che capire che ci sono pochissime verità universali.

L'ho testato ampiamente e, nel mio caso specifico, è stato molto più efficiente sia dell'utilizzo del tipo di array (generosamente offerto da PostgreSQL) sia dell'interrogazione di un'altra tabella.

Ecco la mia risposta: ho implementato con successo un elenco in un singolo campo in PostgreSQL, utilizzando la lunghezza fissa di ogni elemento dell'elenco. Supponiamo che ogni elemento sia un colore come valore esadecimale ARGB, significa 8 caratteri. Quindi puoi creare il tuo array di massimo 10 elementi moltiplicando per la lunghezza di ogni elemento:

ALTER product ADD color varchar(80)

Nel caso in cui la lunghezza degli elementi dell'elenco sia diversa, puoi sempre riempire il riempimento con \ 0

NB: Ovviamente questo non è necessariamente l'approccio migliore per il numero esadecimale poiché un elenco di interi consumerebbe meno spazio di archiviazione, ma questo è solo allo scopo di illustrare questa idea di array facendo uso di una lunghezza fissa assegnata a ciascun elemento.

Il motivo per cui: 1 / Molto conveniente: recupera l'elemento i nella sottostringa i * n, (i +1) * n. 2 / Nessun overhead delle query incrociate. 3 / Più efficiente e conveniente sul lato server. L'elenco è come un mini blob che il client dovrà dividere.

Anche se rispetto le persone che seguono le regole, molte spiegazioni sono molto teoriche e spesso non riescono a riconoscere che, in alcuni casi specifici, specialmente quando si punta a costi ottimali con soluzioni a bassa latenza, alcune piccole modifiche sono più che benvenute.

"Dio non voglia che stia violando qualche sacro principio sacro di SQL": Adottare un approccio più aperto e pragmatico prima di recitare le regole è sempre la strada da percorrere. Altrimenti potresti finire come un fanatico candido che recita le Tre Leggi della Robotica prima di essere cancellato da Skynet

Non pretendo che questa soluzione sia una svolta, né che sia ideale in termini di leggibilità e flessibilità del database, ma può certamente darti un vantaggio quando si tratta di latenza.


Ma questo è un caso molto specifico: un numero fisso di articoli a lunghezza fissa. Anche allora, rende una ricerca semplice come "tutti i prodotti che hanno almeno il colore x" più difficile di quanto farebbe SQL standard.
Gert Arnold

Come ho affermato più volte, non lo uso per il colore, il campo in cui lo uso non dovrebbe essere indicizzato né usato come condizione, eppure è fondamentale
Antonin GAVREL

Lo so, sto cercando di indicare che questo è altamente specifico. Se qualche piccolo requisito aggiuntivo si insinua, diventa rapidamente più scomodo rispetto alle soluzioni standard. La stragrande maggioranza delle persone che sono tentate di memorizzare elenchi in un campo db probabilmente è meglio che non lo faccia.
Gert Arnold

0

Molti database SQL consentono a una tabella di contenere una sottotabella come componente. Il metodo usuale consiste nel consentire al dominio di una delle colonne di essere una tabella. Questo è in aggiunta all'utilizzo di alcune convenzioni come CSV per codificare la sottostruttura in modi sconosciuti al DBMS.

Quando Ed Codd stava sviluppando il modello relazionale nel 1969-1970, definì specificamente una forma normale che non consentisse questo tipo di annidamento delle tabelle. La forma normale fu successivamente chiamata Prima forma normale. Ha poi continuato dimostrando che per ogni database esiste un database nella prima forma normale che esprime le stesse informazioni.

Perché preoccuparsi di questo? Ebbene, i database nella prima forma normale consentono l'accesso con chiave a tutti i dati. Se fornisci un nome di tabella, un valore chiave in quella tabella e un nome di colonna, il database conterrà al massimo una cella contenente un elemento di dati.

Se consenti a una cella di contenere un elenco o una tabella o qualsiasi altra raccolta, ora non puoi fornire l'accesso con chiave agli elementi secondari, senza rielaborare completamente l'idea di una chiave.

L'accesso con chiave a tutti i dati è fondamentale per il modello relazionale. Senza questo concetto, il modello non è relazionale. Per quanto riguarda il motivo per cui il modello relazionale è una buona idea e quali potrebbero essere i limiti di tale buona idea, è necessario considerare i 50 anni di esperienza accumulata con il modello relazionale.


-1

puoi memorizzarlo come testo che assomiglia a un elenco e creare una funzione che possa restituire i suoi dati come un elenco effettivo. esempio:

Banca dati:

 _____________________
|  word  | letters    |
|   me   | '[m, e]'   |
|  you   |'[y, o, u]' |  note that the letters column is of type 'TEXT'
|  for   |'[f, o, r]' |
|___in___|_'[i, n]'___|

E la funzione del compilatore di elenchi (scritta in python, ma dovrebbe essere facilmente traducibile nella maggior parte degli altri linguaggi di programmazione). TEXT rappresenta il testo caricato dalla tabella sql. restituisce un elenco di stringhe dalla stringa contenente l'elenco. se vuoi che restituisca int invece di stringhe, rendi la modalità uguale a 'int'. Allo stesso modo con "string", "bool" o "float".

def string_to_list(string, mode):
    items = []
    item = ""
    itemExpected = True
    for char in string[1:]:
        if itemExpected and char not in [']', ',', '[']:
            item += char
        elif char in [',', '[', ']']:
            itemExpected = True
            items.append(item)
            item = ""
    newItems = []
    if mode == "int":
        for i in items:
            newItems.append(int(i))

    elif mode == "float":
        for i in items:
            newItems.append(float(i))

    elif mode == "boolean":
        for i in items:
            if i in ["true", "True"]:
                newItems.append(True)
            elif i in ["false", "False"]:
                newItems.append(False)
            else:
                newItems.append(None)
    elif mode == "string":
        return items
    else:
        raise Exception("the 'mode'/second parameter of string_to_list() must be one of: 'int', 'string', 'bool', or 'float'")
    return newItems

Anche qui c'è una funzione da lista a stringa nel caso ne abbiate bisogno.

def list_to_string(lst):
    string = "["
    for i in lst:
        string += str(i) + ","
    if string[-1] == ',':
        string = string[:-1] + "]"
    else:
        string += "]"
    return string
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.