¿Está mal el uso de varias claves externas separadas por comas? De ser así, ¿por qué?

31

Hay dos tablas: Dealy DealCategories. Un acuerdo puede tener muchas categorías de acuerdos.

Entonces, la forma correcta debería ser hacer una tabla llamada DealCategoriescon la siguiente estructura:

DealCategoryId (PK)
DealId (FK)
DealCategoryId (FK)

Sin embargo, nuestro equipo de subcontratación almacenó las múltiples categorías en la Dealtabla de esta manera:

DealId (PK)
DealCategory -- In here they store multiple deal ids separated by commas like this: 18,25,32.

Siento que lo que hicieron está mal, pero no sé cómo explicar claramente por qué esto no está bien.

¿Cómo debería explicarles que esto está mal? ¿O tal vez soy yo quien está equivocado y esto es aceptable?

Sarawut Positwinyu
fuente
20
Tienes razón. ¿Es realmente tan malo almacenar una lista separada por comas en una columna de la base de datos? . Respuesta corta: Sí, es tan malo.
ypercubeᵀᴹ
77
despedir al equipo subcontratado de inmediato antes de que hagan más daño ... (-_-)
Rafa

Respuestas:

49

Sí, es una idea terrible.

En lugar de ir:

SELECT Deal.Name, DealCategory.Name
FROM Deal
  INNER JOIN
     DealCategories ON Deal.DealID = DealCategories.DealID
  INNER JOIN
     DealCategory ON DealCategories.DealCategoryID = DealCategory.DealCategoryID
WHERE Deal.DealID = 1234

Ahora tienes que ir:

SELECT Deal.ID, Deal.Name, DealCategories
FROM Deal
WHERE Deal.DealID = 1234

Luego debe hacer cosas en el código de su aplicación para dividir esa lista de comas en números individuales, luego consultar la base de datos por separado:

SELECT DealCategory.Name
FROM DealCategory
WHERE DealCategory.DealCategoryID IN (<<that list from before>>)

Este diseño antipatrón se debe a un malentendido completo del modelado relacional (no tiene que tener miedo a las tablas. Las tablas son sus amigos. Úselos), o una creencia extrañamente equivocada de que es más rápido tomar una lista separada por comas y dividirla en el código de la aplicación de lo que es agregar una tabla de enlaces ( nunca lo es). La tercera opción es que no son lo suficientemente seguros / competentes con SQL para poder configurar claves externas, pero si ese es el caso, no deberían tener nada que ver con el diseño de un modelo relacional.

SQL Antipatterns (Karwin, 2010) dedica un capítulo entero a este antipatrón (que él llama 'Jaywalking'), páginas 15-23. Además, el autor ha publicado una pregunta similar en SO . Los puntos clave que observa (como se aplican a este ejemplo) son:

  • La consulta de todas las ofertas en una categoría específica es bastante complicada (la forma más fácil de resolver ese problema es una expresión regular, pero una expresión regular es un problema en sí misma).
  • No puede imponer integridad referencial sin relaciones de claves externas. Si elimina DealCategory nr. # 26, entonces, en el código de su aplicación, tiene que pasar por cada acuerdo buscando referencias a la categoría # 26 y eliminarlas. Esto es algo que debe manejarse en la capa de datos, y tener que manejarlo en su aplicación es algo muy malo .
  • Las consultas agregadas ( COUNT, SUMetc.), nuevamente, varían de 'complicadas' a 'casi imposibles'. Pregúnteles a sus desarrolladores cómo podrían obtener una lista de todas las categorías con un recuento del número de ofertas en esa categoría. Con un diseño adecuado, son cuatro líneas de SQL.
  • Las actualizaciones se vuelven mucho más difíciles (es decir, tiene un acuerdo que está en cinco categorías, pero desea eliminar dos y agregar otras tres). Son tres líneas de SQL con un diseño adecuado.
  • Eventualmente te toparás con VARCHARlimitaciones de longitud de lista. Aunque si tienes una lista separada por comas que tiene más de 4000 caracteres, lo más probable es que el monstruo sea lento como el infierno de todos modos.
  • Sacar una lista de la base de datos, dividirla y luego volver a la base de datos para otra consulta es intrínsecamente más lenta que una consulta.

TLDR: es un diseño fundamentalmente defectuoso, no se escalará bien, introduce una complejidad adicional incluso para las consultas más simples, y de forma inmediata ralentiza su aplicación.

Simon Righarts
fuente
1
Simon, alguien hizo la misma pregunta ( dba.stackexchange.com/questions/17824/… ), pero no tengo claro por qué los mismos FK y PK están en la misma tabla, eso frena el 3FN.
jcho360
2
No estaba del todo seguro de si querían tener una relación de muchos a muchos entre ofertas y categorías, o algún tipo de jerarquía de categorías. De cualquier manera, era una línea lateral al punto principal, que ser campos delimitados por comas en lugar de una tabla de enlaces es una mala idea.
Simon Righarts
4

Sin embargo, nuestro equipo de outsourcing almacenó las múltiples categorías en la tabla Deal de esta manera:

DealId (PK) DealCategory: aquí almacenan múltiples identificadores de acuerdo separados por comas como esta: 18,25,32.

En realidad, ese es un buen diseño si solo necesita consultar las categorías para un acuerdo determinado.

Pero es terrible si quieres conocer todas las ofertas en una categoría determinada.

Y también hace que sea realmente difícil y propenso a errores hacer cualquier otra cosa, como actualizaciones, recuentos, uniones, etc.

La desnormalización tiene su lugar, pero debe tener en cuenta que se optimiza para un tipo de consulta a expensas de todas las demás que pueda realizar con los mismos datos. Si sabe que siempre estará realizando consultas en un patrón, entonces podría darle una ventaja usar el diseño desnormalizado. Pero si hay alguna posibilidad de que pueda necesitar más flexibilidad en los tipos de consultas, quédese con un diseño normalizado.

Al igual que cualquier otra forma de optimización, necesita saber qué consultas va a ejecutar antes de poder decidir si la desnormalización está justificada.

Bill Karwin
fuente
1
¿Realmente crees que una cadena con ID de niño separados por comas es útil? Quiero decir, la aplicación tenía que leer primero, luego analizar los ID y consultar a todos los niños, como select * from DealCategories where DealId in (1,2,3,4,...). Tiene más experiencia en el diseño de bases de datos que yo, por lo que tal vez tenga una buena razón en algunos casos para tal "ajuste extremo" en casos muy específicos. Mi única idea para justificar esto es una selectcarga muy alta en Deal / DealCategory. Esto se parece mucho a un equipo externo sin ningún conocimiento de diseño de base de datos, más allá de crear tablas, lo creó.
Erik Hart
1
@ErikHart, esto es desnormalización, y puede ser útil, pero mi punto es que depende completamente de las consultas que necesita ejecutar. Tiene razón en que la desnormalización hace que todas las consultas funcionen peor, excepto la consulta para la que se optimiza. Si solo necesita ejecutar esa consulta y no le importan las otras consultas, es una victoria. Pero estos son casos raros, porque generalmente queremos flexibilidad para consultar los datos de varias maneras.
Bill Karwin el
1
@ErikHart, si ese equipo de subcontratación recibió especificaciones del proyecto que incluían solo una consulta con estos datos, podrían haber diseñado una optimización solo para esa consulta específica. En otras palabras, "lo pediste, lo conseguiste". Pero el proveedor de outsourcing no tiene ninguna razón para planificar usos futuros de los datos: implementan la aplicación al pie de la letra de lo que está escrito en la especificación.
Bill Karwin
1

Múltiples valores en una columna están en contra de la primera forma normal.

Tampoco es absolutamente ninguna ganancia de velocidad, ya que las tablas deben vincularse en la base de datos. Primero debe leer y analizar una cadena, luego seleccionar todas las categorías para el "Acuerdo".

La implementación correcta sería una tabla de unión como "DealDealCategories", con DealId y DealCategoryId.

Mala implementación de la jerarquía?

Además, un FK en DealCategories a otra DealCategory parece una mala implementación de una jerarquía / árbol de DealCategories. ¡Trabajar con árboles a través de una relación de ID de padre (llamada lista de adyacencia) es un dolor!

Verifique los conjuntos anidados (buenos para leer, pero difíciles de modificar) y las tablas de cierre (el mejor rendimiento general, pero posiblemente un alto uso de memoria, ¡probablemente no demasiado para sus DealCategories) al implementar jerarquías!

Erik Hart
fuente