È inutile creare una nuova tabella di database invece di utilizzare il tipo di dati enum?


38

Supponiamo che io abbia 4 tipi di servizi che offro (è improbabile che cambino spesso):

  • analisi
  • Design
  • Programmazione
  • Altro

Supponiamo che io abbia 60-80 di servizi effettivi che rientrano ciascuno in una delle categorie precedenti. Ad esempio, "un servizio" può essere "Programma di test utilizzando la tecnica A" ed è di tipo "Test".

Voglio codificarli in un database. Ho trovato alcune opzioni:

Opzione 0:

Utilizzare VARCHARdirettamente per codificare il tipo di servizio direttamente come stringa

Opzione 1:

Usa il database enum. Ma l' enum è malvagio

Opzione 2:

usa due tabelle:

service_line_item (id, service_type_id INT, description VARCHAR);
service_type (id, service_type VARCHAR);

Posso anche godere dell'integrità referenziale:

ALTER service_line_item 
    ADD FOREIGN KEY (service_type_id) REFERENCES service_type (id);

Suona bene, sì?

Ma devo ancora codificare le cose e gestire i numeri interi, vale a dire quando si popola la tabella. Oppure devo creare elaborati programmi o costrutti DB durante il popolamento o la gestione della tabella. Vale a dire, JOINs quando si ha a che fare direttamente con il database o quando si creano nuove entità orientate agli oggetti sul lato della programmazione e si assicura che le funzioni correttamente.

Opzione 3:

Non usare enum, non usare due tabelle, ma usa solo una colonna intera

service_line_item (
    id,
    service_type INT,        -- use 0, 1, 2, 3 (for service types)
    description VARCHAR
);

Questo è come un "falso enum" che richiede un maggior sovraccarico sul lato del codice delle cose, come ad esempio conoscerlo {2 == 'Programming'}e affrontarlo in modo appropriato.

Domanda:

Attualmente l'ho implementato usando l' opzione 2 , guidata sotto concetti

  1. non usare enum (opzione 1)
  2. evitare di utilizzare un database come foglio di calcolo (opzione 0)

Ma non posso fare a meno di sentirmi dispendioso in termini di programmazione e sovraccarico cognitivo: devo essere consapevole di due tabelle e occuparmi di due tabelle, anziché una.

Per un "modo meno dispendioso", sto guardando Option 3. L'IT è più leggero e richiede essenzialmente gli stessi costrutti di codice per funzionare (con lievi modifiche ma la complessità e la struttura sono sostanzialmente le stesse ma con una sola tabella)

Suppongo che idealmente non sia sempre dispendioso, e ci sono buoni casi per entrambe le opzioni, ma c'è una buona linea guida su quando si dovrebbe usare l'opzione 2 e quando l'opzione 3?

Quando ci sono solo due tipi (binari)

Per aggiungere un po 'di più a questa domanda ... nella stessa sede, ho un'opzione binaria di servizio "Standard" o "Eccezione", che può essere applicata all'elemento pubblicitario del servizio. L'ho codificato usando l' opzione 3 .

Ho scelto di non creare una nuova tabella solo per contenere i valori {"Standard", "Eccezione"}. Quindi la mia colonna contiene solo {0, 1} e il mio nome di colonna viene chiamato exception, e il mio codice sta facendo una traduzione {0, 1} => {STANDARD, EXCEPTION}(che ho codificato come costanti nel linguaggio di programmazione)

Finora non mi piaceva così ..... (non gradire l'opzione 2 né l'opzione 3). Trovo l'opzione 2 superiore a 3, ma con un overhead maggiore, e tuttavia non riesco a sfuggire alla codifica delle cose come numeri interi, indipendentemente dall'opzione che utilizzo tra 2 e 3.

ORM

Per aggiungere un po 'di contesto, dopo aver letto le risposte - ho appena iniziato a utilizzare di nuovo un ORM (di recente), nel mio caso Doctrine 2. Dopo aver definito lo schema DB tramite Annotazioni, volevo popolare il database. Poiché il mio intero set di dati è relativamente piccolo, volevo provare a usare costrutti di programmazione per vedere come funziona.

Ho prima popolato service_types, e poi service_line_items, poiché esisteva un elenco esistente da un foglio di calcolo effettivo. Quindi cose come "standard / exception" e "Testing" sono tutte stringhe sul foglio di calcolo e devono essere codificate in tipi appropriati prima di memorizzarle nel DB.

Ho trovato questa risposta SO: che cosa usi invece di ENUM in doctrine2? , che suggeriva di non usare il costrutto enum di DB, ma di usare un INTcampo e codificare i tipi usando il costrutto "const" del linguaggio di programmazione.

Ma come sottolineato nella precedente domanda SO, posso evitare di usare direttamente numeri interi e usare costrutti di linguaggio - costanti - una volta definiti ...

Ma comunque ... non importa come lo giri, se inizio con stringun tipo, devo prima convertirlo in un tipo corretto, anche quando utilizzo un ORM.

Quindi, se dico $str = 'Testing';, ho ancora bisogno di avere un blocco da qualche parte che fa qualcosa di simile:

switch($str):
{ 
    case 'Testing':  $type = MyEntity::TESTING; break;
    case 'Other':    $type = MyEntity::OTHER; break;
}

La cosa buona è che non hai a che fare con numeri interi / magici [invece, con quantità costanti codificate], ma la cosa brutta è che non puoi estrarre e rimuovere automaticamente le cose dal database senza questo passaggio di conversione, nel mio conoscenza.

E questo è ciò che intendevo, in parte, dicendo cose come "devono ancora codificare le cose e gestire gli interi". (Concesso, ora, dopo il commento di Ocramius, non dovrò occuparmi direttamente degli interi, ma delle costanti nominate e della conversione da / verso costanti, se necessario).


9
Qualunque cosa tu faccia, non fare # 3. Lo psicopatico che lo mantiene dovrà costantemente capire cosa significano quei numeri magici. Se lo fai, speri che non sappiano dove vivi. blog.codinghorror.com/coding-for-violent-psychopaths
RubberDuck

7
Mi piace l'opzione 2. Se non ti piace la proliferazione delle tabelle di ricerca, usa una tabella e aggiungi una colonna "tipo di ricerca". Ma sì, creare una tabella di ricerca è il modo "standard" per farlo, in quanto ti consente di fare cose divertenti come popolare facilmente un menu a discesa nell'interfaccia utente.
Robert Harvey,

Non utilizzare "EDIT" nei tuoi post qui; non siamo un forum. Ogni post di Stack Exchange contiene già una cronologia delle modifiche dettagliata che chiunque può visualizzare.
Robert Harvey,

se non posso usare EDIT, cosa devo usare?
Dennis,

Modifica il post e rendilo naturale, come ho già fatto. Vedi la cronologia delle modifiche per rivedere le modifiche.
Robert Harvey,

Risposte:


35

L'opzione n. 2, utilizzando le tabelle di riferimento, è il modo standard di farlo. È stato utilizzato da milioni di programmatori ed è noto per funzionare. È uno schema , quindi chiunque guarderà le tue cose saprà immediatamente cosa sta succedendo. Esistono librerie e strumenti che funzionano su database, salvandoti da un sacco di lavoro, che li gestirà correttamente. I vantaggi dell'utilizzo sono innumerevoli.

È dispendioso? Sì, ma solo leggermente. Qualsiasi database decente manterrà sempre nella cache tabelle così piccole unite di frequente, quindi i rifiuti sono generalmente impercettibili.

Tutte le altre opzioni che hai descritto sono ad hoc e confuse, comprese quelle di MySQL enum, perché non fanno parte dello standard SQL. (A parte questo, ciò che fa schifo enumè l'implementazione di MySQL, non l'idea stessa. Non mi dispiacerebbe vederlo un giorno come parte dello standard.)

La tua ultima opzione n. 3 con l'utilizzo di un intero semplice è particolarmente confusa. Ottieni il peggio di tutti i mondi: nessuna integrità referenziale, nessun valore con nome, nessuna conoscenza definitiva nel database di ciò che rappresenta un valore, solo interi arbitrari gettati dappertutto. Con questo token, potresti anche smettere di usare le costanti nel tuo codice e iniziare invece a usare valori hardcoded. circumference = radius * 6.28318530718;. Che ne dici di quello?

Penso che dovresti riesaminare perché trovi le tabelle di riferimento onerose. Nessun altro li trova onerosi, per quanto ne so. Potrebbe essere perché non stai usando gli strumenti giusti per il lavoro?

La tua frase sul dovere di "codificare le cose e trattare con numeri interi", o di "creare elaborati costrutti di programmazione", o "creare nuove entità orientate agli oggetti sul lato della programmazione", mi dice che forse potresti tentare di fare oggetti relazionali mappatura (ORM) al volo sparsa in tutto il codice dell'applicazione o, nel migliore dei casi, potresti provare a rotolare il tuo meccanismo di mappatura relazionale ad oggetti, invece di utilizzare uno strumento ORM esistente per il lavoro, come Hibernate. Tutte queste cose sono un gioco da ragazzi con Hibernate. Ci vuole un po 'di tempo per impararlo, ma una volta appreso, puoi davvero concentrarti sullo sviluppo della tua applicazione e dimenticare la meccanica grintosa di come rappresentare elementi nel database.

Infine, se vuoi semplificarti la vita quando lavori direttamente con il database, ci sono almeno due cose che puoi fare, a cui riesco a pensare in questo momento:

  1. Crea viste che uniscono le tue tabelle principali con qualsiasi tabella di riferimento a cui fanno riferimento, in modo che ogni riga contenga non solo gli ID di riferimento, ma anche i nomi corrispondenti.

  2. Invece di utilizzare un ID intero per la tabella di riferimento, utilizzare una colonna CHAR (4), con abbreviazioni di 4 lettere. Quindi, gli ID delle tue categorie diventerebbero "TEST", "DSGN", "PROG", "OTHR". (Le loro descrizioni rimarrebbero le parole inglesi appropriate, ovviamente.) Sarà un po 'più lento, ma fidati di me, nessuno se ne accorgerà.

Infine, quando ci sono solo due tipi, la maggior parte delle persone usa solo una colonna booleana. Quindi, quella colonna "standard / exception" verrebbe implementata come booleana e si chiamerebbe "IsException".


3
A parte questo, Postgres ha anche tipi di enum . Sono semplici e niente di speciale, ti consentono di utilizzare una stringa leggibile come valore, ma hanno un intero più efficiente da utilizzare sotto il cofano.
Kat

Che dire del caso in cui i dati vengono di conseguenza ripetuti, ma non ridondanti (ad es. Non comporteranno anomalie di aggiornamento / inserimento / cancellazione)? Ad esempio, il genere di una persona (è improbabile che introducano nuovi tipi di dati, non sarà mai necessario cambiare il nome di un genere, ecc.)
Adam Thompson,

Questo: perché alla fine scoprirai che hai bisogno di un "ambiente di accettazione" e che i tuoi enum non cambianti debbano essere cambiati.
Pieter B,

3

Opzione 2 con costanti o enumerazioni sull'estremità della programmazione.
Anche se duplica la conoscenza, violando il principio della singola fonte di verità, puoi affrontarla usando la tecnica Fail-fast . Quando il sistema viene caricato, verificherebbe che nel database siano presenti gli enum o i valori const. In caso contrario, il sistema dovrebbe generare un errore e rifiutare il caricamento. In genere sarà più economico correggere questo bug in questo momento rispetto a quando in seguito potrebbe essere successo qualcosa di più serio.


0

Non c'è nulla che ti impedisca di usare stringhe [corte] come chiavi, quindi potresti comunque avere la leggibilità dei nomi nelle tue tabelle e non ricorrere alla codifica numerica surrogata insignificante. Dovresti comunque avere una tabella separata per descrivere i tipi di servizio, proprio per caso, per esempio, la tua applicazione diventa internazionale!

Gli utenti possono vedere i tuoi quattro categorie nella propria lingua, ma le tabelle del database ancora contenere valori che si possono leggere - e nessuno di esso richiede qualsiasi struttura o codice database di cambiamenti!

table service_type 
( id VARCHAR 
, name VARCHAR 
  primary key ( id ) 
);
table service_line_item 
( id 
, service_type VARCHAR 
, description VARCHAR
  foreign key ( service_type ) references service_type ( id )
);

select * from service_type ; 

+-------------+----------------+
| id          | name           |
+-------------+----------------+
| Testing     | Testen         |
| Design      | Design         | 
| Programming | Programmierung |
| Other       | Andere         |
+-------------+----------------+

o, per i tuoi clienti francesi ...

update services_types set name = 'Essai'         where id = 'Testing'; 
update services_types set name = 'Conception'    where id = 'Design'; 
update services_types set name = 'Programmation' where id = 'Programming'; 
update services_types set name = 'Autre'         where id = 'Other'; 
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.