¿Es un desperdicio crear una nueva tabla de base de datos en lugar de usar el tipo de datos enum?


38

Supongamos que tengo 4 tipos de servicios que ofrezco (es poco probable que cambien a menudo):

  • Pruebas
  • Diseño
  • Programación
  • Otro

Supongamos que tengo 60-80 de servicios reales que cada uno cae en una de las categorías anteriores. Por ejemplo, 'un servicio' puede ser "Programa de prueba utilizando la técnica A" y es del tipo "Prueba".

Quiero codificarlos en una base de datos. Se me ocurrieron algunas opciones:

Opción 0:

Use VARCHARdirectamente para codificar el tipo de servicio directamente como una cadena

Opción 1:

Usar base de datos enum. Pero, enum es malvado

Opcion 2:

usa dos tablas:

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

Incluso puedo disfrutar de integridad referencial:

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

Suena bien, si?

Pero todavía tengo que codificar cosas y tratar con números enteros, es decir, al llenar la tabla. O tengo que crear programación elaborada o construcciones de base de datos al completar o manejar la tabla. Es decir, SE UNE al tratar con la base de datos directamente, o al crear nuevas entidades orientadas a objetos en el lado de la programación, y asegurándose de que las opere correctamente.

Opcion 3:

No use enum, no use dos tablas, solo use una columna entera

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

Esto es como una 'enumeración falsa' que requiere más sobrecarga en el lado del código de las cosas, por ejemplo, saber eso {2 == 'Programming'}y tratarlo adecuadamente.

Pregunta:

Actualmente lo he implementado usando la Opción 2 , guiada bajo conceptos

  1. no use enum (opción 1)
  2. evite usar una base de datos como hoja de cálculo (opción 0)

Pero no puedo evitar sentir que me parece un desperdicio en términos de programación y sobrecarga cognitiva: tengo que tener en cuenta dos tablas y tratar con dos tablas, frente a una.

Para una "manera menos derrochadora", estoy mirando Option 3. Es más liviano y requiere esencialmente las mismas construcciones de código para operar (con ligeras modificaciones, pero la complejidad y la estructura son básicamente las mismas pero con una sola tabla)

Supongo que idealmente no siempre es un desperdicio, y hay buenos casos para cualquiera de las opciones, pero ¿hay alguna buena pauta sobre cuándo se debe usar la Opción 2 y cuándo la Opción 3?

Cuando solo hay dos tipos (binario)

Para agregar un poco más a esta pregunta ... en el mismo lugar, tengo una opción binaria de Servicio "Estándar" o "Excepción", que se puede aplicar a la línea de servicio. Lo he codificado usando la Opción 3 .

Elegí no crear una nueva tabla solo para contener los valores {"Estándar", "Excepción"}. Entonces, mi columna solo contiene {0, 1} y se llama al nombre de mi columna exception, y mi código está haciendo una traducción {0, 1} => {STANDARD, EXCEPTION}(que codifiqué como constantes en el lenguaje de programación)

Hasta ahora no me ha gustado de esa manera ... (no me gusta la opción 2 ni la opción 3). Encuentro la opción 2 superior a 3, pero con más sobrecarga, y aún así no puedo escapar de codificar cosas como enteros, sin importar qué opción use de 2 y 3.

ORM

Para agregar algo de contexto, después de leer las respuestas, acabo de comenzar a usar un ORM nuevamente (recientemente), en mi caso Doctrine 2. Después de definir el esquema de DB a través de Anotaciones, quería llenar la base de datos. Como todo mi conjunto de datos es relativamente pequeño, quería intentar usar construcciones de programación para ver cómo funciona.

Primero rellené service_types, y luego service_line_items, ya que había una lista existente de una hoja de cálculo real. Así que cosas como 'estándar / excepción' y 'Pruebas' son todas cadenas en la hoja de cálculo, y deben codificarse en tipos adecuados antes de almacenarlas en DB.

Encontré esta respuesta SO: ¿Qué utilizas en lugar de ENUM en doctrine2? , que sugirió no usar la construcción enum de DB, sino usar un INTcampo y codificar los tipos usando la construcción 'const' del lenguaje de programación.

Pero como se señaló en la pregunta SO anterior, puedo evitar usar enteros directamente y usar construcciones de lenguaje, constantes, una vez que se definen ...

Pero aún así ... no importa cómo lo gire, si empiezo con stringun tipo, primero tengo que convertirlo a un tipo adecuado, incluso cuando uso un ORM.

Entonces, si digo $str = 'Testing';, todavía necesito tener un bloque en algún lugar que haga algo como:

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

Lo bueno es que no se trata de números enteros / mágicos [en cambio, se trata de cantidades constantes codificadas], pero lo malo es que no se pueden extraer y sacar cosas automáticamente de la base de datos sin este paso de conversión. conocimiento.

Y eso es lo que quise decir, en parte, al decir cosas como "todavía tengo que codificar cosas y tratar con números enteros". (De acuerdo, ahora, después del comentario de Ocramius, no tendré que tratar directamente con enteros, sino tratar con constantes con nombre y alguna conversión a / desde constantes, según sea necesario).


99
Hagas lo que hagas, no hagas el # 3. El psicópata que lo mantiene tendrá que averiguar constantemente qué significan esos números mágicos. Si haces eso, es mejor que esperen que no sepan dónde vives. blog.codinghorror.com/coding-for-violent-psychopaths
RubberDuck

77
Me gusta la opción 2. Si no le gusta la proliferación de tablas de búsqueda, use una tabla y agregue una columna de "tipo de búsqueda". Pero sí, crear una tabla de búsqueda es la forma "estándar" de hacerlo, ya que le permite hacer cosas divertidas como llenar fácilmente un menú desplegable en la interfaz de usuario.
Robert Harvey

No uses "EDITAR" en tus publicaciones aquí; No somos un foro. Cada publicación de Stack Exchange ya contiene un historial de edición detallado que cualquiera puede ver.
Robert Harvey

si no puedo usar EDIT, ¿qué debo usar?
Dennis

Solo edita la publicación y haz que se vea natural, como ya lo hice. Vea el historial de edición para revisar los cambios.
Robert Harvey

Respuestas:


35

La opción # 2, usando tablas de referencia, es la forma estándar de hacerlo. Ha sido utilizado por millones de programadores y se sabe que funciona. Es un patrón , por lo que cualquier persona que mire sus cosas sabrá de inmediato lo que está sucediendo. Existen bibliotecas y herramientas que funcionan en bases de datos, lo que le ahorra mucho trabajo, que lo manejará correctamente. Los beneficios de usarlo son innumerables.

¿Es un desperdicio? Sí, pero solo un poco. Cualquier base de datos medio decente siempre mantendrá en caché las tablas pequeñas unidas con frecuencia, por lo que el desperdicio es generalmente imperceptible.

Todas las demás opciones que describió son ad hoc y hacky, incluidas las MySQL enum, porque no es parte del estándar SQL. (Aparte de eso, lo que apesta enumes la implementación de MySQL, no la idea en sí. No me importaría verlo algún día como parte del estándar).

Su última opción # 3 con el uso de un entero simple es especialmente hacky. Obtiene lo peor de todos los mundos: sin integridad referencial, sin valores con nombre, sin conocimiento definitivo dentro de la base de datos de lo que representa un valor, solo enteros arbitrarios arrojados por todo el lugar. Con este token, también podría dejar de usar constantes en su código y comenzar a usar valores codificados en su lugar. circumference = radius * 6.28318530718;. ¿Qué hay sobre eso?

Creo que debería volver a examinar por qué encuentra las tablas de referencia onerosas. Nadie más los encuentra onerosos, que yo sepa. ¿Podría ser porque no está utilizando las herramientas adecuadas para el trabajo?

Su oración acerca de tener que "codificar cosas y tratar con números enteros", o tener que "crear construcciones de programación elaboradas", o "crear nuevas entidades orientadas a objetos en el lado de la programación", me dice que tal vez esté intentando hacer relaciones de objeto mapeo (ORM) sobre la marcha disperso en todo el código de su aplicación, o en el mejor de los casos, puede estar intentando rodar su propio mecanismo de mapeo relacional de objetos, en lugar de utilizar una herramienta ORM existente para el trabajo, como Hibernate. Todas estas cosas son muy fáciles con Hibernate. Toma un poco de tiempo aprenderlo, pero una vez que lo ha aprendido, realmente puede concentrarse en desarrollar su aplicación y olvidarse de la mecánica esencial de cómo representar cosas en la base de datos.

Finalmente, si desea facilitarle la vida al trabajar directamente con la base de datos, hay al menos dos cosas que puede hacer, que puedo pensar en este momento:

  1. Cree vistas que unan sus tablas principales con las tablas de referencia a las que hacen referencia, de modo que cada fila contenga no solo los identificadores de referencia, sino también los nombres correspondientes.

  2. En lugar de usar una identificación entera para la tabla de referencia, use una columna CHAR (4), con abreviaturas de 4 letras. Por lo tanto, los identificadores de sus categorías se convertirían en "TEST", "DSGN", "PROG", "OTHR". (Sus descripciones seguirían siendo palabras inglesas apropiadas, por supuesto). Será un poco más lento, pero créanme, nadie lo notará.

Finalmente, cuando solo hay dos tipos, la mayoría de las personas solo usan una columna booleana. Entonces, esa columna "estándar / excepción" se implementaría como un booleano y se llamaría "IsException".


3
Por otro lado, Postgres también tiene tipos de enumeración , también. Son simples y nada especial, lo que le permite usar una cadena legible como valor, pero tiene un número entero más eficiente que se puede usar debajo del capó.
Kat

¿Qué pasa con el caso cuando los datos se repiten consecuentemente, pero no son redundantes (por ejemplo, no darán lugar a anomalías de actualización / inserción / eliminación)? Por ejemplo, el género de una persona (es poco probable que introduzca nuevos tipos de datos, nunca necesitará cambiar el nombre de un género, etc.)
Adam Thompson,

Esto: porque eventualmente descubrirá que necesita un "entorno de aceptación" y que sus enumeraciones que no cambian deben cambiarse.
Pieter B

3

Opción 2 con constantes o enumeraciones en el final de la programación.
Aunque duplica el conocimiento, violando el principio de la Fuente Única de la Verdad, puede lidiar con él utilizando la técnica Fail-fast . Cuando su sistema se carga, verificará que los valores enumerados o constantes existan en la base de datos. De lo contrario, el sistema debería arrojar un error y negarse a cargar. Por lo general, será más barato corregir este error en este momento que más tarde, cuando puede haber sucedido algo más grave.


0

No hay nada que le impida usar cadenas [cortas] como teclas, por lo que aún podría tener la legibilidad de los nombres en sus tablas y no recurrir a la codificación de números sustitutos sin sentido. Aún debe tener una tabla separada para describir los Tipos de servicio, ¡solo en el caso de que, por ejemplo, su aplicación se internacionalice!

Sus usuarios pueden ver las cuatro categorías en su propio idioma, pero sus tablas de la base todavía contienen valores que se puede leer - y nada de esto requiere ninguna estructura de base de datos o cambios en el código!

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, para sus clientes franceses ...

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'; 
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.