¿Es realmente tan malo almacenar una lista delimitada en una columna de base de datos?

363

Imagine un formulario web con un conjunto de casillas de verificación (se puede seleccionar cualquiera o todas ellas). Elegí guardarlos en una lista de valores separados por comas almacenados en una columna de la tabla de la base de datos.

Ahora, sé que la solución correcta sería crear una segunda tabla y normalizar adecuadamente la base de datos. Implementar la solución fácil fue más rápido, y quería tener una prueba de concepto de esa aplicación rápidamente y sin tener que dedicar demasiado tiempo a ella.

Pensé que el tiempo ahorrado y el código más simple valían la pena en mi situación, ¿es esta una opción de diseño defendible, o debería haberlo normalizado desde el principio?

Un poco más de contexto, esta es una pequeña aplicación interna que esencialmente reemplaza un archivo de Excel que se almacenó en una carpeta compartida. También pregunto porque estoy pensando en limpiar el programa y hacerlo más fácil de mantener. Hay algunas cosas allí con las que no estoy completamente satisfecho, una de ellas es el tema de esta pregunta.

Científico loco
fuente
21
en ese caso, ¿por qué molestar a la base de datos ?, guardar en un archivo será suficiente.
thavan
66
De acuerdo con @thavan. ¿Por qué incluso guardar los datos como prueba de concepto? Una vez que tenga la prueba completa, agregue una base de datos correctamente. Su multa es liviana como prueba de concepto, simplemente no haga cosas que tenga que deshacer más tarde.
Jeff Davis
1
En Postgres, se debe preferir una columna de matriz sobre una lista separada por comas. Eso al menos asegura el tipo de datos adecuado, no tiene problemas para distinguir el delimitador de los datos reales y puede indexarse ​​de manera eficiente.
a_horse_with_no_name

Respuestas:

568

Además de violar la primera forma normal debido al grupo repetitivo de valores almacenados en una sola columna, las listas separadas por comas tienen muchos otros problemas más prácticos:

  • No puedo asegurar que cada valor sea el tipo de datos correcto: no hay forma de evitar 1,2,3, banana, 5
  • No se pueden usar restricciones de clave externa para vincular valores a una tabla de búsqueda; No hay forma de hacer cumplir la integridad referencial.
  • No se puede hacer cumplir la unicidad: no hay forma de prevenir 1,2,3,3,3,5
  • No se puede eliminar un valor de la lista sin recuperar toda la lista.
  • No se puede almacenar una lista más larga de lo que cabe en la columna de cadena.
  • Difícil de buscar todas las entidades con un valor dado en la lista; tienes que usar un escaneo de tabla ineficiente. Puede tener que recurrir a expresiones regulares, por ejemplo en MySQL:
    idlist REGEXP '[[:<:]]2[[:>:]]'*
  • Elementos difíciles de contar en la lista, o hacer otras consultas agregadas.
  • Es difícil unir los valores a la tabla de búsqueda a la que hacen referencia.
  • Es difícil obtener la lista en orden ordenado.

Para resolver estos problemas, debe escribir toneladas de código de aplicación, reinventando la funcionalidad que el RDBMS ya proporciona de manera mucho más eficiente .

Las listas separadas por comas son lo suficientemente erróneas como para que este sea el primer capítulo de mi libro: Antipatterns SQL: evitar las trampas de la programación de bases de datos .

Hay momentos en los que necesita emplear la desnormalización, pero como menciona @OMG Ponies , estos son casos excepcionales. Cualquier "optimización" no relacional beneficia un tipo de consulta a expensas de otros usos de los datos, así que asegúrese de saber cuáles de sus consultas deben tratarse de manera tan especial que merezcan la desnormalización.


* MySQL 8.0 ya no admite esta sintaxis de expresión de límite de palabra.

Bill Karwin
fuente
8
Un ARRAY (de cualquier tipo de datos) puede corregir la excepción, simplemente verifique PostgreSQL: postgresql.org/docs/current/static/arrays.html (@Bill: Gran libro, una lectura obligada para cualquier desarrollador o dba)
Frank Heikens
44
+1 bill Karwin ¡Excelente respuesta! Encantadores puntos concisos. Ese también parece un gran libro. También me encanta la portada +1 NullUserException. Estoy en el proceso de diseñar el esquema para una base de datos MySQL para reemplazar un sistema basado en texto de archivo plano. Me he encontrado con varios dilemas hasta ahora. Entonces valdrá la pena comprar este libro.
therobyouknow
2
El sitio pragprog.com también se ve bien: buen estilo, diseño, limpieza fácil de usar. Esto debe ser bastante nuevo, no he podido comprar sus libros electrónicos en el pasado. PD. No trabajo para ellos tienen ninguna conexión con los autores. Me gusta celebrar buenos productos, servicios y ayuda cuando lo veo.
therobyouknow
2
En el lado serio, agregaría a su lista: Difícil de buscar. Digamos que quiere todos los registros que incluyen "2". Por supuesto, no puede simplemente buscar foobar = '2' porque eso lo perdería si hubiera otros valores. No puede buscar foobar como '% 2%' porque obtendría resultados falsos para 12 y 28 y así sucesivamente. No puede buscar foobar como '%, 2,%' porque 2 podría ser el primer o el último elemento de la lista y, por lo tanto, solo tiene una de esas comas.
Jay
2
Sé que no es recomendable, pero el juego de Devils advocate: la mayoría de estos pueden eliminarse si hay una interfaz de usuario que maneja la unicidad y los tipos de datos (de lo contrario, se produciría un error o se comportaría mal), la interfaz de usuario se cae y la crea de todos modos, hay una tabla de controladores donde los valores provienen para hacerlos únicos, se puede usar un campo como '% P%', los valores son P, R, S, T, el conteo no importa y la clasificación no importa. Dependiendo de la interfaz de usuario, los valores se pueden dividir [], por ejemplo, para marcar casillas de verificación en una lista de la tabla de controladores en el escenario menos común sin tener que ir a otra tabla para obtenerlos.
jmcclure
44

"Una razón fue la pereza".

Esto suena las alarmas. La única razón por la que debe hacer algo como esto es porque sabe cómo hacerlo "de la manera correcta", pero ha llegado a la conclusión de que hay una razón tangible para no hacerlo de esa manera.

Dicho esto: si los datos que está eligiendo almacenar de esta manera son datos que nunca necesitará consultar, entonces puede haber un caso para almacenarlos de la manera que haya elegido.

(Algunos usuarios disputarían la declaración en mi párrafo anterior, diciendo que "nunca se puede saber qué requisitos se agregarán en el futuro". Estos usuarios están equivocados o declaran una convicción religiosa. A veces es ventajoso cumplir con los requisitos que usted tener delante de ti.)

Hammerita
fuente
Siempre escucho a algunas personas decir que "mi diseño es más flexible que el tuyo" cuando los confronto sobre cosas como no establecer restricciones de claves externas o almacenar listas en un solo campo. Para mí, flexibilidad (en tales casos) == no disciplina == pereza.
foresightyj
41

Hay numerosas preguntas sobre SO preguntando:

  • cómo obtener un recuento de valores específicos de la lista separada por comas
  • cómo obtener registros que solo tengan el mismo valor específico de 2/3 / etc. de esa lista separada por comas

Otro problema con la lista separada por comas es garantizar que los valores sean consistentes: almacenar texto significa la posibilidad de errores tipográficos ...

Todos estos son síntomas de datos desnormalizados y destacan por qué siempre debe modelar para datos normalizados. La desnormalización puede ser una optimización de consulta, que se aplica cuando la necesidad realmente se presenta .

Ponis OMG
fuente
19

En general, cualquier cosa puede ser defendible si cumple con los requisitos de su proyecto. Esto no significa que las personas estén de acuerdo o quieran defender su decisión ...

En general, el almacenamiento de datos de esta manera no es óptimo (por ejemplo, es más difícil hacer consultas eficientes) y puede causar problemas de mantenimiento si modifica los elementos en su formulario. ¿Quizás podría haber encontrado un término medio y utilizado un número entero que representa un conjunto de banderas de bits en su lugar?

bobbymcr
fuente
10

Sí, diría que realmente es tan malo. Es una opción defendible, pero eso no lo hace correcto o bueno.

Se rompe la primera forma normal.

Una segunda crítica es que poner los resultados de entrada sin procesar directamente en una base de datos, sin ninguna validación o enlace, lo deja abierto a ataques de inyección SQL.

Lo que usted llama pereza y falta de conocimiento de SQL es el material del que están hechos los neófitos. Recomiendo tomarse el tiempo para hacerlo correctamente y verlo como una oportunidad para aprender.

O déjelo como está y aprenda la dolorosa lección de un ataque de inyección SQL.

duffymo
fuente
19
No veo nada en esta pregunta que sugiera que es vulnerable a la inyección de SQL. La inyección de SQL y la normalización de la base de datos son temas ortogonales, y su digresión sobre la inyección es irrelevante para la pregunta.
Hammerite
55
@Paul: Y tal vez la misma actitud lo lleve a ser atropellado por un autobús cuando no mire a ambos lados antes de cruzar la calle, pero no lo ha advertido al respecto. Editar: pensé que eras el cartel de esta respuesta, mi error.
Hammerite
1
@Hammerite: su extrapolación a los autobuses es ridícula.
duffymo
44
Sí, estaba destinado a ser ridículo. Su ridiculez ilustra el punto que estoy haciendo, que es que no tiene sentido advertirlo contra algo de lo que no tienes razón para pensar que necesita ser advertido.
Hammerite
1
Si lo veo. Creo que tenía muchas más razones que tu advertencia sobre los autobuses.
duffymo
7

Bueno, he estado usando una lista separada por pestañas par de clave / valor en una columna NTEXT en SQL Server durante más de 4 años y funciona. Pierdes la flexibilidad de hacer consultas, pero por otro lado, si tienes una biblioteca que persiste / derive el par de valores clave, entonces no es una mala idea.

Raj
fuente
13
No, es una idea horrible. Se las arregló para salirse con la suya, pero el costo de sus pocos minutos de tiempo de desarrollo le ha costado un rendimiento de consulta, flexibilidad y facilidad de mantenimiento de su código.
Paul Tomblin
55
Paul, estoy de acuerdo. Pero como dije, lo usé para un propósito específico, y eso es para una operación de entrada de datos donde tiene muchos tipos de formularios. Estoy revisando el diseño ahora que aprendí NHibernate, pero en ese entonces necesitaba la flexibilidad para diseñar el formulario en ASP.NET y usar los identificadores del cuadro de texto como clave en el par clave / valor.
Raj
28
+1 solo para contrarrestar los votos negativos. Decirle a alguien que ha mantenido la aplicación durante 4 años sobre problemas de mantenimiento es un poco presuntuoso. Hay muy pocas ideas "horribles" en el desarrollo de software, principalmente son ideas con una aplicabilidad muy limitada. Es razonable advertir a la gente sobre las limitaciones, pero castigar a quienes lo han hecho y lo han vivido me parece una actitud más santa que la que puedo prescindir.
Mark Brackett
7

Necesitaba una columna de valores múltiples, podría implementarse como un campo xml

Podría convertirse a una coma delimitada según sea necesario

consultar una lista XML en el servidor sql usando Xquery .

Al ser un campo xml, se pueden abordar algunas de las preocupaciones.

Con CSV: no se puede garantizar que cada valor sea el tipo de datos correcto: no hay forma de evitar 1,2,3, banana, 5

Con XML: los valores en una etiqueta pueden ser forzados a ser del tipo correcto


Con CSV: no se pueden usar restricciones de clave externa para vincular valores a una tabla de búsqueda; No hay forma de hacer cumplir la integridad referencial.

Con XML: sigue siendo un problema


Con CSV: No se puede hacer cumplir la unicidad: no hay forma de prevenir 1,2,3,3,3,5

Con XML: sigue siendo un problema


Con CSV: no se puede eliminar un valor de la lista sin obtener toda la lista.

Con XML: los elementos individuales se pueden eliminar


Con CSV: Difícil de buscar todas las entidades con un valor dado en la lista; tienes que usar un escaneo de tabla ineficiente.

Con XML: el campo xml se puede indexar


Con CSV: elementos difíciles de contar en la lista, o hacer otras consultas agregadas. **

Con XML: no particularmente difícil


Con CSV: Difícil unir los valores a la tabla de búsqueda a la que hacen referencia. **

Con XML: no particularmente difícil


Con CSV: es difícil obtener la lista en orden ordenado.

Con XML: no particularmente difícil


Con CSV: almacenar enteros como cadenas ocupa aproximadamente el doble de espacio que almacenar enteros binarios.

Con XML: el almacenamiento es incluso peor que un csv


Con CSV: Además de muchos caracteres de coma.

Con XML: se usan etiquetas en lugar de comas


En resumen, el uso de XML soluciona algunos de los problemas con la lista delimitada Y se puede convertir a una lista delimitada según sea necesario

James A Mohler
fuente
6

Sí, es así de malo. Mi opinión es que si no le gusta usar bases de datos relacionales, busque una alternativa que se adapte mejor a usted, hay muchos proyectos interesantes "NOSQL" con algunas características realmente avanzadas.

Robin
fuente
0

Probablemente tomaría el término medio: convierta cada campo en el CSV en una columna separada en la base de datos, pero no se preocupe mucho por la normalización (al menos por ahora). En algún momento, la normalización puede volverse interesante, pero con todos los datos en una sola columna, prácticamente no obtendrá ningún beneficio al usar una base de datos. Debe separar los datos en campos lógicos / columnas / como quiera llamarlos antes de poder manipularlos de manera significativa.

Jerry Coffin
fuente
El formulario contiene algunos campos más, esta es solo una parte del formulario (que no expliqué bien en la pregunta).
Mad Scientist
0

Si tiene un número fijo de campos booleanos, puede usar un INT(1) NOT NULL(o BIT NOT NULLsi existe) o CHAR (0)(anulable) para cada uno. También podría usar un SET(se me olvida la sintaxis exacta).

Solomon Ucko
fuente
1
INT(1)toma 4 bytes; El no (1)tiene sentido.
Rick James