¿Está bien alguna vez usar listas en una base de datos relacional?

88

He estado tratando de diseñar una base de datos que vaya con un concepto de proyecto y me encontré con lo que parece ser un tema muy debatido. He leído algunos artículos y algunas respuestas de desbordamiento de pila que dicen que nunca (o casi nunca) está bien almacenar una lista de ID o similares en un campo: todos los datos deben ser relacionales, etc.

El problema con el que me estoy topando, sin embargo, es que estoy tratando de hacer un asignador de tareas. La gente creará tareas, las asignará a varias personas y se guardará en la base de datos.

Por supuesto, si guardo estas tareas individualmente en "Persona", tendré que tener docenas de columnas ficticias de "ID de tarea" y las microgestionará porque puede haber de 0 a 100 tareas asignadas a una persona, por ejemplo.

Nuevamente, si guardo las tareas en una tabla de "Tareas", tendré que tener docenas de columnas de "PersonID" ficticias y las microgestionará, el mismo problema que antes.

Para un problema como este, ¿está bien guardar una lista de ID que toman una forma u otra o simplemente no estoy pensando en otra forma en que esto se pueda lograr sin romper los principios?

    
pregunta linus72982 14.11.2018 - 05:25

9 respuestas

242

La palabra clave y el concepto clave que necesita investigar es base de datos normalización .

Lo que haría, es que en lugar de agregar información sobre las asignaciones a las tablas de personas o tareas, es agregar una nueva tabla con esa información de asignación, con relaciones relevantes.

Ejemplo, tiene las siguientes tablas:

Personas:

+−−−−+−−−−−−−−−−−+
| ID |    Name   |
+====+===========+
|  1 |  Alfred   |
|  2 |  Jebediah |
|  3 |  Jacob    |
|  4 |  Ezekiel  |
+−−−−+−−−−−−−−−−−+

Tareas:

+−−−−+−−−−−−−−−−−−−−−−−−−−+
| ID |        Name        |
+====+====================+
|  1 |  Feed the Chickens |
|  2 |  Plow              |
|  3 |  Milking Cows      |
|  4 |  Raise a barn      |
+−−−−+−−−−−−−−−−−−−−−−−−−−+

Luego crearías una tercera tabla con Asignaciones. Esta tabla modelaría la relación entre las personas y las tareas:

+−−−−+−−−−−−−−−−−+−−−−−−−−−+
| ID |  PersonId |  TaskId |
+====+===========+=========+
|  1 |         1 |       3 |
|  2 |         3 |       2 |
|  3 |         2 |       1 |
|  4 |         1 |       4 |
+−−−−+−−−−−−−−−−−+−−−−−−−−−+

Entonces tendríamos una restricción de clave externa, tal que la base de datos impondrá que PersonId y TaskIds tienen que ser identificadores válidos para esos elementos extranjeros. En la primera fila, podemos ver PersonId is 1 , por lo que Alfred , se asigna a TaskId 3 , ordeñar vacas .

Lo que debería poder ver aquí es que podría tener tantas o tan pocas tareas por tarea o por persona como desee. En este ejemplo, a Ezekiel no se le asigna ninguna tarea, y a Alfred se le asigna 2. Si tiene una tarea con 100 personas, hacer SELECT PersonId from Assignments WHERE TaskId=<whatever>; producirá 100 filas, con una variedad de diferentes personas asignadas. Puede WHERE en el PersonId para encontrar todas las tareas asignadas a esa persona.

Si desea devolver las consultas reemplazando los identificadores con los nombres y las tareas, puede aprender a UNIRSE a las tablas.

    
respondido por el whatsisname 14.11.2018 - 05:47
35

Estás haciendo dos preguntas aquí.

Primero, pregunta si está bien almacenar listas serializadas en una columna. Si esta bien. Si su proyecto lo requiere. Un ejemplo podría ser ingredientes del producto para una página de catálogo, donde no desea intentar realizar un seguimiento de cada ingrediente individualmente.

Lamentablemente, su segunda pregunta describe un escenario en el que debería optar por un enfoque más relacional. Necesitarás 3 mesas. Uno para las personas, uno para las tareas y otro para mantener la lista de las tareas asignadas a cada persona. La última sería vertical, una fila por combinación de persona / tarea, con columnas para su clave principal, ID de tarea e ID de persona.

    
respondido por el GrandmasterB 14.11.2018 - 05:48
21

Lo que estás describiendo se conoce como una relación de "muchos a muchos", en tu caso entre Person y Task . Normalmente se implementa utilizando una tercera tabla, a veces llamada tabla de "enlace" o "referencia cruzada". Por ejemplo:

create table person (
    person_id integer primary key,
    ...
);

create table task (
    task_id integer primary key,
    ...
);

create table person_task_xref (
    person_id integer not null,
    task_id integer not null,
    primary key (person_id, task_id),
    foreign key (person_id) references person (person_id),
    foreign key (task_id) references task (task_id)
);
    
respondido por el Mike Partridge 14.11.2018 - 05:46
12
  

... nunca (o casi nunca) está bien almacenar una lista de ID o similares en un campo

La única vez que podría almacenar más de un elemento de datos en un solo campo es cuando ese campo es solo alguna vez utilizado como una sola entidad y es < em> nunca se considera que está formado por esos elementos más pequeños. Un ejemplo podría ser una imagen, almacenada en un campo BLOB. Se compone de muchos y más pequeños elementos (bytes), pero estos no significan nada para la base de datos y solo se pueden usar todos juntos (y son bastante bonitos para un usuario final).

Dado que una "lista" está, por definición, formada por elementos más pequeños (elementos), este no es el caso aquí y debe normalizar los datos.

  

... si guardo estas tareas individualmente en "Persona", tendré que tener docenas de columnas ficticias de "ID de tarea" ...

No. Tendrá unas pocas filas en una tabla de intersección (a.k.a. Entidad débil) entre Persona y Tarea. Las bases de datos son realmente buenas para trabajar con muchas filas; En realidad son bastante basura al trabajar con muchas columnas [repetidas].

Buen ejemplo claro dado por whatsisname.

    
respondido por el Phill W. 14.11.2018 - 13:02
4

Puede ser legítimo en ciertos campos precalculados.

Si algunas de sus consultas son caras y usted decide ir con campos precalculados que se actualizan automáticamente utilizando los activadores de la base de datos, puede ser legítimo mantener las listas dentro de una columna.

Por ejemplo, en la interfaz de usuario desea mostrar esta lista mediante la vista de cuadrícula, donde cada fila puede abrir todos los detalles (con listas completas) después de hacer doble clic:

REGISTERED USER LIST
+------------------+----------------------------------------------------+
|Name              |Top 3 most visited tags                             |
+==================+====================================================+
|Peter             |Design, Fitness, Gifts                              |
+------------------+----------------------------------------------------+
|Lucy              |Fashion, Gifts, Lifestyle                           |
+------------------+----------------------------------------------------+

Mantiene la segunda columna actualizada por activador cuando el cliente visita un nuevo artículo o por una tarea programada.

Puede hacer que este campo esté disponible incluso para búsquedas (como texto normal).

Para tales casos, mantener listas es legítimo. Solo debe considerar el caso de que posiblemente exceda la longitud máxima del campo.

Además, si está utilizando Microsoft Access, ofrezca campos multivalor son otro caso de uso especial. Manejan tus listas en un campo automáticamente.

Pero siempre puedes volver a la forma normalizada estándar que se muestra en otras respuestas.

Resumen: las formas normales de la base de datos son modelos teóricos necesarios para comprender aspectos importantes del modelado de datos. Pero, por supuesto, la normalización no tiene en cuenta el rendimiento u otro costo de recuperar los datos. Está fuera del alcance de ese modelo teórico. Pero la implementación práctica a menudo requiere el almacenamiento de listas u otros duplicados precalculados (y controlados).

A la luz de lo anterior, en la implementación práctica, ¿preferiríamos una consulta basada en la forma normal perfecta y ejecutar 20 segundos o una consulta equivalente basándose en valores precalculados que requieren 0.08 s? A nadie le gusta que su producto de software sea acusado de lentitud.

    
respondido por el miroxlav 15.11.2018 - 01:21
1

Estás tomando lo que debería ser otra mesa, girándola 90 grados y colocándola en otra mesa.

Es como tener una tabla de orden en la que tienes itemProdcode1, itemQuantity1, itemPrice1 ... itemProdcode37, itemQuantity37, itemPrice37. Además de ser incómodo de manejar mediante programación, puede garantizar que mañana alguien querrá pedir 38 cosas.

Solo lo haría a su manera si la 'lista' no es realmente una lista, es decir, dónde se ubica como un todo y cada línea individual no se refiere a alguna entidad clara e independiente. En ese caso, simplemente rellene todo en un tipo de datos que sea lo suficientemente grande.

Entonces, un pedido es una lista, una lista de materiales es una lista (o una lista de listas, lo que sería aún más una pesadilla de implementar "de lado"). Pero una nota / comentario y un poema no lo son.

    
respondido por el Bloke Down The Pub 15.11.2018 - 00:06
0

Dadas dos tablas; Los llamaremos Persona y Tarea, cada uno con su propia ID (PersonID, TaskID) ... la idea básica es crear una tercera tabla para unirlos. Llamaremos a esta tabla PersonToTask. Como mínimo, debe tener su propia identificación, así como las otras dos. Entonces, cuando se trata de asignar a alguien a una tarea; ya no tendrá que ACTUALIZAR la tabla de personas, solo debe INSERTAR una nueva línea en la Tabla de Tareas Personales. Y el mantenimiento se vuelve más fácil: la necesidad de eliminar una tarea solo se convierte en DELETE en función de TaskID, ya no es necesario actualizar la tabla de persona y su análisis asociado

CREATE TABLE dbo.PersonToTask (
    pttID INT IDENTITY(1,1) NOT NULL,
    PersonID INT NULL,
    TaskID   INT NULL
)

CREATE PROCEDURE dbo.Task_Assigned (@PersonID INT, @TaskID INT)
AS
BEGIN
    INSERT PersonToTask (PersonID, TaskID)
    VALUES (@PersonID, @TaskID)
END

CREATE PROCEDURE dbo.Task_Deleted (@TaskID INT)
AS
BEGIN
    DELETE PersonToTask  WHERE TaskID = @TaskID
    DELETE Task          WHERE TaskID = @TaskID
END

¿Qué tal un informe simple o quién está asignado a una tarea?

CREATE PROCEDURE dbo.Task_CurrentAssigned (@TaskID INT)
AS
BEGIN
    SELECT PersonName
    FROM   dbo.Person
    WHERE  PersonID IN (SELECT PersonID FROM dbo.PersonToTask WHERE TaskID = @TaskID)
END

Por supuesto que podrías hacer mucho más; se podría hacer un TimeReport si agregaba campos de DateTime para TaskAssigned y TaskCompleted. Todo depende de ti

    
respondido por el Mad Myche 14.11.2018 - 20:26
0

Puede funcionar si usted dice que tiene claves primarias legibles por humanos y desea una lista de # tareas sin tener que lidiar con la naturaleza vertical de la estructura de una tabla. es decir, mucho más fácil de leer la primera tabla.

------------------------  
Employee Name | Task 
Jack          |  1,2,5
Jill          |  4,6,7
------------------------

------------------------  
Employee Name | Task 
Jack          |  1
Jack          |  2
Jack          |  5
Jill          |  4
Jill          |  6
Jill          |  7
------------------------

La pregunta entonces sería: si la lista de tareas se almacenara o generara a pedido, lo que dependería en gran medida de requisitos tales como: la frecuencia con la que se necesita la lista, la precisión de cuántas filas de datos existen, cómo se utilizarán los datos. , etc. luego de lo cual se deben analizar las ventajas y desventajas de la experiencia del usuario y cumplir los requisitos.

Por ejemplo, comparando el tiempo que llevaría recuperar las 2 filas frente a ejecutar una consulta que generaría las 2 filas. Si tarda mucho y el usuario no necesita la lista más actualizada (* esperando menos de 1 cambio por día), entonces podría almacenarse.

O si el usuario necesita un registro histórico de las tareas asignadas, también tendría sentido si se almacenara la lista. Así que realmente depende de lo que estés haciendo, nunca digas nunca.

    
respondido por el Double E CPU 14.11.2018 - 20:46
0

Si "no está bien", entonces es bastante malo que cada sitio de Wordpress tenga una lista en wp_usermeta con wp_capabilities en una fila, la lista dismissed_wp_pointers en una fila, y otras ...

De hecho, en casos como este, podría ser mejor para la velocidad, ya que casi siempre querrá la lista . Pero no se sabe que Wordpress sea el ejemplo perfecto de las mejores prácticas.

    
respondido por el NoBugs 18.11.2018 - 07:27

Lea otras preguntas en las etiquetas