Perché dovresti archiviare un enum in DB?


69

Ho visto una serie di domande, come questa , che chiedono consigli su come archiviare gli enum in DB. Ma mi chiedo perché dovresti farlo. Quindi diciamo che ho un'entità Personcon un gendercampo e un Genderenum. Quindi, la mia tabella personale ha un genere di colonna.

Oltre all'ovvia ragione per imporre la correttezza, non vedo perché dovrei creare una tabella aggiuntiva genderper mappare ciò che già ho nella mia applicazione. E non mi piace molto avere quella duplicazione.



1
Dove altro potresti conservare i dati che possono cambiare regolarmente? Mentre potresti aver pensato a tutte le opzioni cosa succede se qualcuno arriva e vuole aggiungere una nuova opzione. Sei pronto a modificare quella lista codificata? Qualcuno potrebbe voler dare il proprio genere come qualcosa di diverso da maschio o femmina, ad esempio intersessuato per esempio.
JB King,

4
@JBKing ... basta guardare l'elenco dei sessi di Facebook.


3
Se i tuoi clienti sono "Tumblrite illuse", allora crei dannatamente uno schema di database che ti consenta di creare qualcosa che soddisfi le loro esigenze, almeno se intendi rimanere in attività.
Gort il robot il

Risposte:


74

Facciamo un altro esempio meno denso di concezioni e aspettative. Ho un enum qui, ed è l'insieme delle priorità per un bug.

Quale valore stai memorizzando nel database?

Quindi, potrei essere la memorizzazione 'C', 'H', 'M', e 'L'nel database. O 'HIGH'così via. Questo ha il problema dei dati digitati con stringhe . Esiste un set noto di valori validi e, se non lo memorizzi nel database, può essere difficile lavorarci.

Perché stai memorizzando i dati nel codice?

Hai List<String> priorities = {'CRITICAL', 'HIGH', 'MEDIUM', 'LOW'};qualcosa o qualcosa del genere nel codice. Significa che hai vari mapping di questi dati nel formato corretto (stai inserendo tutti i tappi nel database, ma li stai visualizzando come Critical). Il tuo codice ora è anche difficile da localizzare. La rappresentazione del database dell'idea è stata associata a una stringa memorizzata nel codice.

Ovunque sia necessario accedere a questo elenco, è necessario disporre della duplicazione del codice o di una classe con un gruppo di costanti. Nessuna delle due sono buone opzioni. Non bisogna inoltre dimenticare che esistono altre applicazioni che possono utilizzare questi dati (che possono essere scritti in altre lingue: l'applicazione Web Java ha un sistema di reportistica Crystal Reports utilizzato e un processo batch Perl che inserisce i dati in esso). Il motore di report dovrebbe conoscere l'elenco di dati valido (cosa succede se non c'è nulla contrassegnato in 'LOW'priorità e devi sapere che è una priorità valida per il report?) E il processo batch avrebbe le informazioni su ciò che è valido i valori sono.

Ipoteticamente, potresti dire "siamo un negozio in una sola lingua - tutto è scritto in Java" e abbiamo un singolo .jar che contiene queste informazioni - ma ora significa che le tue applicazioni sono strettamente collegate tra loro e che .jar contenente i dati. Dovrai rilasciare la parte di reportistica e la parte di aggiornamento batch insieme all'applicazione Web ogni volta che si verifica una modifica e sperare che tale versione si svolga senza problemi per tutte le parti.

Cosa succede quando il tuo capo vuole un'altra priorità?

Il tuo capo è venuto oggi. C'è una nuova priorità - CEO. Ora devi andare a cambiare tutto il codice e fare una ricompilazione e ridistribuire.

Con un approccio "enum-in-the-table", aggiorni l'elenco enum per avere una nuova priorità. Tutto il codice che ottiene l'elenco lo estrae dal database.

I dati raramente sono soli

Con le priorità, le chiavi dei dati in altre tabelle che potrebbero contenere informazioni sui flussi di lavoro o chi può impostare questa priorità o altro.

Tornando al genere come menzionato nella domanda per un po ': Il genere ha un link ai pronomi in uso: he/his/hime she/hers/her... e vuoi evitare di codificarlo nel codice stesso. E poi arriva il tuo capo e devi aggiungere che hai il 'OTHER'genere (per renderlo semplice) e devi mettere in relazione questo genere con they/their/them... e il tuo capo vede cosa ha Facebook e ... beh, sì.

Limitando te stesso a un bit di dati tipicamente stringa piuttosto che a una tabella enum, ora hai bisogno di replicare quella stringa in un gruppo di altre tabelle per mantenere questa relazione tra i dati e gli altri bit.

Che dire di altri archivi dati?

Non importa dove lo memorizzi, esiste lo stesso principio.

  • Potresti avere un file priorities.propcon l'elenco delle priorità. Hai letto questo elenco da un file di proprietà.
  • Potresti avere un database di archivio documenti (come CouchDB ) che ha una voce per enums(e quindi scrivere una funzione di validazione in JavaScript ):

    {
       "_id": "c18b0756c3c08d8fceb5bcddd60006f4",
       "_rev": "1-c89f76e36b740e9b899a4bffab44e1c2",
       "priorities": [ "critical", "high", "medium", "low" ],
       "severities": [ "blocker", "bad", "annoying", "cosmetic" ]
    }
    
  • Potresti avere un file XML con un po 'di uno schema:

    <xs:element name="priority" type="priorityType"/>
    
    <xs:simpleType name="priorityType">
      <xs:restriction base="xs:string">
        <xs:enumeration value="critical"/>
        <xs:enumeration value="high"/>
        <xs:enumeration value="medium"/>
        <xs:enumeration value="low"/>
      </xs:restriction>
    </xs:simpleType>
    

L'idea di base è la stessa. L'archivio dati stesso è dove è necessario archiviare e applicare l'elenco di valori validi. Inserendolo qui, è più facile ragionare sul codice e sui dati. Non devi preoccuparti di controllare in modo difensivo quello che hai ogni volta (è maiuscolo o inferiore? Perché c'è un chriticaltipo in questa colonna? Ecc ...) perché sai che cosa stai recuperando dal datastore è esattamente ciò che l'archivio dati si aspetta che invii altrimenti - e puoi interrogare l'archivio dati per un elenco di valori validi.

L'asporto

L'insieme di valori validi sono dati , non codice. È Non c'è bisogno di lottare per DRY codice - ma la questione della duplicazione è che si sta duplicando i dati nel codice, piuttosto che rispettando il suo posto come dati e la memorizzazione in un database.

Semplifica la scrittura di più applicazioni sull'archivio dati ed evita di avere istanze in cui è necessario distribuire tutto ciò che è strettamente accoppiato ai dati stessi, poiché non è stato accoppiato il codice ai dati.

Semplifica il test delle applicazioni perché non è necessario ripetere il test dell'intera applicazione quando CEOviene aggiunta la priorità, poiché non si dispone di alcun codice che si preoccupa del valore effettivo della priorità.

Essere in grado di ragionare sul codice e sui dati indipendentemente l'uno dall'altro semplifica la ricerca e la correzione di bug durante la manutenzione.


6
Se puoi aggiungere un valore enum al tuo codice senza dover cambiare alcuna logica (e per paura che sia la sua visualizzazione localizzata), dubito in primo luogo della necessità del valore enum aggiuntivo. E mentre sono abbastanza grande per valutare la capacità di eseguire facilmente query sui backup del database con semplici query SQL per analizzare un problema, con gli ORM in questi giorni puoi fare molto bene senza dover guardare affatto al database sottostante. Non capisco il punto sulla localizzazione (pronomi) qui - quella roba certamente non dovrebbe essere in un database, ma direi file di risorse di qualche tipo.
Voo

1
@Voo i pronomi è un esempio di altri dati relativi a questo valore enumesque. Senza i dati in una tabella, i valori digitati in modo stringente dovrebbero essere presenti senza vincoli FK adeguati. Se hai pronomi (come questo) in un file di risorse, hai un accoppiamento tra il database e il file (aggiorna il database e ridistribuisci il file). Considera gli enum di redmine che sono modificabili al volo tramite l'interfaccia di amministrazione senza dover eseguire una ridistribuzione.

1
... ricorda anche che i database sono un archivio dati poliglotta. Se stai richiedendo che la convalida sia eseguita come parte dell'ORM in una lingua, hai reso necessario duplicare quella convalida in qualsiasi altra lingua che usi (di recente ho lavorato con un front-end Java che aveva Python che spingeva i dati nel database - l'ORM di Java e i sistemi Python devono essere d'accordo sulle cose - e quell'accordo (i tipi validi) è stato più facilmente implementato facendo sì che il database lo imponesse con una tabella 'enum'.).

2
@Voo l'uso dell'enum di Redmine è lo stesso di bugzilla "la tabella più importante contiene tutti i bug del sistema. È composta da varie proprietà dei bug, inclusi tutti i valori di enum come gravità e priorità." - Non è un campo di testo in formato libero, è un valore che fa parte di questo insieme noto ed enumerabile. Non è un enum dei tempi di compilazione , ma è ancora enumico. Vedi anche Mantis .

1
Quindi per confermare: il punto è che le persone non dovrebbero mai usare Enums? Non era chiaro
Niico,

18

Quale di questi pensi sia più probabile che produca errori durante la lettura della query?

select * 
from Person 
where Gender = 1

O

select * 
from Person join Gender on Person.Gender = Gender.GenderId
where Gender.Label = "Female" 

Le persone creano tabelle enum in SQL perché trovano quest'ultima più leggibile, portando a un minor numero di errori nella scrittura e nella gestione di SQL.

Potresti trasformare il genere in una stringa direttamente Person, ma poi dovresti provare ad applicare il caso. È inoltre possibile aumentare l'hit di archiviazione per la tabella e il tempo di query a causa della differenza tra stringhe e numeri interi a seconda di quanto sia fantastico il tuo DB nell'ottimizzare le cose.


5
Ma poi ci uniamo ai tavoli. Se la mia entità ha due enumerazioni, unirò tre tabelle solo per una semplice query.
user3748908

11
@ user3748908 - quindi? I join sono i punti di forza dei DB e le alternative sono peggiori, almeno agli occhi delle persone che hanno scelto questa strada.
Telastyn,

8
@ user3748908: Non solo i database sono davvero bravi a fare join, ma sono anche molto bravi a far rispettare la coerenza. L'applicazione della coerenza funziona davvero molto bene quando è possibile puntare una colonna in una tabella alla riga identificativa di un'altra e dire "il valore per questa colonna deve essere uno degli identificatori in quella tabella".
Blrfl,

2
Questo è vero, ma ci sono molti casi in cui è necessario sacrificare i join per motivi di prestazioni. Non fraintendetemi, mi occupo di questo tipo di design e di unione, ma sto lanciando che il mondo non finirà se ti accorgi che a volte non hai bisogno dei join a causa delle prestazioni.
JonH,

3
Se devi abbandonare l'unione alle tabelle di riferimento per motivi di prestazioni @JonH, devi acquistare un server più grande o smettere di provare a inviare predicati attraverso un gran numero di sottoquery (suppongo che tu sappia cosa stai facendo). Le tabelle dei riferimenti sono le cose che dovrebbero essere nella cache entro pochi secondi dall'avvio del DB.
Ben

10

Non riesco a credere che la gente non l'abbia ancora menzionato.

Chiavi straniere

Mantenendo l'enum nel database e aggiungendo una chiave esterna nella tabella che contiene un valore enum, si garantisce che nessun codice inserisca mai valori errati per quella colonna. Questo aiuta l'integrità dei tuoi dati ed è il motivo più ovvio per cui IMO dovresti avere tabelle per enumerazioni.


La domanda è lunga solo 5 righe e indica chiaramente "Oltre all'ovvia ragione per far rispettare la correttezza". Quindi nessuno lo ha menzionato perché l'OP afferma che è ovvio e sta cercando altre giustificazioni - PS: sono d'accordo con te, questa è una ragione sufficiente.
user1007074

6

Sono nel campo che è d'accordo con te. Se si mantiene un enumerazione di genere nel codice e un tblGender nel database, è possibile che si verifichino problemi in tempi di manutenzione. Dovrai documentare che queste due entità dovrebbero avere gli stessi valori e quindi qualsiasi modifica apportata a una devi apportare anche all'altra.

Sarà quindi necessario passare i valori enum alle procedure memorizzate in questo modo:

create stored procedure InsertPerson @name varchar, @gender int
    insert into tblPeople (name, gender)
    values (@name, @gender)

Ma pensa come lo faresti se conservassi questi valori in una tabella del database:

create stored procedure InsertPerson @name varchar, @genderName varchar
    insert into tblPeople (name, gender)
    select @name, fkGender
    from tblGender
    where genderName = @genderName --I hope these are the same

Sicuramente i database relazionali sono creati tenendo conto dei join, ma quale query è più facile da leggere?


Ecco un'altra query di esempio:

create stored procedure SpGetGenderCounts
    select count(*) as count, gender
    from tblPeople
    group by gender

Confrontalo con questo:

create stored procedure SpGetGenderCounts
    select count(*) as count, genderName
    from tblPeople
    inner join tblGender on pkGender = fkGender
    group by genderName --assuming no two genders have the same name

Ecco ancora un'altra query di esempio:

create stored procedure GetAllPeople
    select name, gender
    from tblPeople

Nota che in questo esempio, dovresti convertire la cella di genere nei tuoi risultati da un int a un enum. Queste conversioni sono comunque facili. Confrontalo con questo:

create stored procedure GetAllPeople
    select name, genderName
    from tblPeople
    inner join tblGender on pkGender = fkGender

Tutte queste query sono più piccole e più gestibili quando si considera l'idea di mantenere le definizioni enum fuori dal database.


1
E se non fosse il genere però. Penso che stiamo diventando troppo bloccati sul fatto che il genere sia il campo. E se l'OP avesse detto "Quindi diciamo che ho un'entità Bug con un campo prioritario": la tua risposta cambierebbe?

4
@MichaelT L'elenco dei possibili valori di "priorità" fa parte del codice almeno nella stessa misura in cui fa parte dei dati. Vedi icone grafiche per varie priorità? Non ti aspetti che vengano estratti dal database? E cose del genere potrebbero essere a tema e in stile e comunque rappresentare lo stesso intervallo di valori memorizzati nel DB. Non puoi semplicemente cambiarlo nel database comunque; hai un codice di presentazione da sincronizzare.
Eugene Ryabtsev il

1

Vorrei creare una tabella Genders per il motivo che può essere utilizzata nell'analisi dei dati. Potrei cercare tutte le persone di sesso maschile o femminile nel database per generare un rapporto. Più modi puoi visualizzare i tuoi dati, più facile sarà scoprire informazioni di tendenza. Ovviamente, questa è un'enumerazione molto semplice, ma per enumerazioni complesse (come i paesi del mondo o gli stati), rende più semplice generare report specializzati.


1

Innanzitutto è necessario decidere se il database verrà utilizzato solo da un'applicazione o se è possibile che vengano utilizzate da più applicazioni. In alcuni casi un database non è altro che un formato di file per un'applicazione (i database SQLite possono spesso essere utilizzati in questo senso). In questo caso, duplicare un po 'la definizione di enum come tabella può spesso andare bene e può avere più senso.

Tuttavia, non appena si desidera considerare la possibilità di avere più applicazioni che accedono al database, una tabella per l'enum ha molto senso (le altre risposte spiegano perché in modo più dettagliato). L'altra cosa da considerare è che tu o un altro sviluppatore desideriate esaminare i dati del database non elaborati. In tal caso, questo può essere considerato un altro utilizzo dell'applicazione (solo uno in cui l'indicatore di laboratorio è SQL non elaborato).

Se hai l'enum definito nel codice (per un codice più pulito e il controllo del tempo di compilazione) e una tabella nel database, consiglierei di aggiungere test unitari per verificare che i due siano sincronizzati.


1

Quando si dispone di un'enumerazione di codice utilizzata per guidare la logica aziendale nel codice, è comunque necessario creare una tabella per rappresentare i dati nel DB per i numerosi motivi descritti sopra / sotto. Ecco alcuni suggerimenti per assicurare che i valori del tuo DB rimangano sincronizzati con i valori del codice:

  1. Non rendere il campo ID sulla tabella una colonna Identità. Includi ID e descrizione come campi.

  2. Fare qualcosa di diverso nella tabella per aiutare gli sviluppatori a sapere che i valori sono semi-statici / legati a un'enumerazione di codice. In tutte le altre tabelle di ricerca (di solito dove gli utenti possono aggiungere valori) Di solito ho un LastChangedDateTime e un LastChangedBy, ma non averli nelle tabelle relative all'enum mi aiuta a ricordare che sono modificabili solo dagli sviluppatori. Documentalo.

  3. Creare un codice di verifica che controlli che ogni valore nell'enumerazione sia nella tabella corrispondente e che solo quei valori siano nella tabella corrispondente. Se disponi di "test di integrità" per applicazioni automatiche che eseguono post-build, lì. In caso contrario, eseguire automaticamente il codice all'avvio dell'applicazione ogni volta che l'applicazione è in esecuzione nell'IDE.

  4. La produzione genera script SQL che fanno lo stesso, ma dall'interno del DB. Se creati correttamente, aiuteranno anche le migrazioni dell'ambiente.


0

Dipende anche da chi accede ai dati. Se hai solo un'applicazione che potrebbe andare bene. Se si aggiunge un data warehouse o un sistema di reporting. Dovranno sapere cosa significa quel codice, qual è la versione redable umana del codice.

Di solito, la tabella dei tipi non viene duplicata come enum nel codice. È possibile caricare la tabella dei tipi in un elenco memorizzato nella cache.

Class GenderList

   Public Shared Property UnfilteredList
   Public Shared Property Male = GetItem("M")
   Public Shared Property Female = GetItem("F")

End Class

Spesso, il tipo va e viene. Avresti bisogno di una data per quando è stato aggiunto il nuovo tipo. Sapere quando è stato rimosso un tipo specifico. Visualizzalo solo quando necessario. Cosa succede se un cliente vuole "transgender" come genere, ma altri clienti no? Tutte queste informazioni sono meglio archiviate nel database.

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.