web-dev-qa-db-fra.com

Est-il jamais acceptable d'utiliser des listes dans une base de données relationnelle?

J'ai essayé de concevoir une base de données pour aller avec un concept de projet et suis tombé sur ce qui semble être un problème très débattu. J'ai lu quelques articles et quelques réponses Stack Overflow qui indiquent qu'il n'est jamais (ou presque jamais) correct de stocker une liste d'ID ou similaires dans un champ - toutes les données doivent être relationnelles, etc.

Le problème que je rencontre, cependant, est que j'essaie de créer un attribut de tâche. Les utilisateurs créeront des tâches, les assigneront à plusieurs personnes et les enregistreront dans la base de données.

Bien sûr, si j'enregistre ces tâches individuellement dans "Personne", je devrai avoir des dizaines de colonnes "TaskID" factices et les micro-gérer car il peut y avoir 0 à 100 tâches assignées à une personne, disons.

Là encore, si j'enregistre les tâches dans un tableau "Tâches", je devrai avoir des dizaines de colonnes "PersonID" factices et les micro-gérer - même problème qu'avant.

Pour un problème comme celui-ci, est-il acceptable d'enregistrer une liste d'ID sous une forme ou une autre ou est-ce que je ne pense tout simplement pas à une autre manière de réaliser cela sans enfreindre les principes?

95
linus72982

Le mot clé et le concept clé que vous devez rechercher sont normalisation de la base de données .

Ce que vous feriez, plutôt que d'ajouter des informations sur les affectations aux tables de personnes ou de tâches, c'est d'ajouter une nouvelle table avec ces informations d'affectation, avec les relations pertinentes.

Exemple, vous disposez des tableaux suivants:

Personnes:

 + −−−− + −−−−−−−−−−− + 
 | ID | Nom | 
 + ==== + =========== + 
 | 1 | Alfred | 
 | 2 | Jebediah | 
 | 3 | Jacob | 
 | 4 | Ézéchiel | 
 + −−−− + −−−−−−−−−−− + 

Tâches:

 + −−−− + −−−−−−−−−−−−−−−−−−−− + 
 | ID | Nom | 
 + ==== + ==================== + 
 | 1 | Nourrir les poulets | 
 | 2 | Charrue | 
 | 3 | Vaches laitières | 
 | 4 | Élever une étable | 
 + −−−− + −−−−−−−−−−−−−−−−−−−− + 

Vous créeriez alors une troisième table avec des affectations. Ce tableau modéliserait la relation entre les personnes et les tâches:

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

Nous aurions alors une contrainte de clé étrangère, telle que la base de données imposera que les PersonId et TaskIds doivent être des ID valides pour ces éléments étrangers. Pour la première ligne, nous pouvons voir PersonId is 1, donc Alfred , est affecté à TaskId 3, Vaches laitières .

Ce que vous devriez pouvoir voir ici, c'est que vous pourriez avoir aussi peu ou autant de tâches par tâche ou par personne que vous le souhaitez. Dans cet exemple, Ezekiel n'est assigné à aucune tâche, et Alfred est attribué 2. Si vous avez une tâche avec 100 personnes, faire SELECT PersonId from Assignments WHERE TaskId=<whatever>; donnera 100 lignes, avec une variété de personnes différentes affectées. Vous pouvez WHERE sur le PersonId pour trouver toutes les tâches assignées à cette personne.

Si vous souhaitez renvoyer des requêtes remplaçant les identifiants par les noms et les tâches, vous apprendrez comment joindre des tables.

249
whatsisname

Vous posez deux questions ici.

Tout d'abord, vous demandez si son ok pour stocker des listes sérialisées dans une colonne. Oui ça ira. Si votre projet l'exige. Un exemple pourrait être les ingrédients du produit pour une page de catalogue, où vous ne souhaitez pas essayer de suivre chaque ingrédient individuellement.

Malheureusement, votre deuxième question décrit un scénario dans lequel vous devriez opter pour une approche plus relationnelle. Vous aurez besoin de 3 tables. Un pour les personnes, un pour les tâches et un qui conserve la liste des tâches affectées à quelles personnes. Ce dernier serait vertical, une ligne par combinaison personne/tâche, avec des colonnes pour votre clé primaire, votre ID de tâche et votre ID de personne.

35
GrandmasterB

Ce que vous décrivez est connu sous le nom de relation "plusieurs à plusieurs", dans votre cas entre Person et Task. Il est généralement implémenté à l'aide d'une troisième table, parfois appelée table "lien" ou "référence croisée". Par exemple:

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)
);
21
Mike Partridge

... il n'est jamais (ou presque jamais) acceptable de stocker une liste d'ID ou similaires dans un champ

La seule fois où vous pouvez stocker plus d'un élément de données dans un seul champ, c'est lorsque ce champ est uniquement jamais utilisé en tant qu'entité unique et jamais considéré comme étant composé de ces éléments plus petits . Un exemple pourrait être une image, stockée dans un champ BLOB. Il est composé de beaucoup, beaucoup d'éléments plus petits (octets), mais ceux-ci ne signifient rien à la base de données et ne peuvent être utilisés que tous ensemble (et sont jolis) à un utilisateur final).

Puisqu'une "liste" est, par définition, composée d'éléments plus petits (éléments), ce n'est pas le cas ici et vous devez normaliser les données.

... si j'enregistre ces tâches individuellement dans "Personne", je devrai avoir des dizaines de colonnes "TaskID" factices ...

Non. Vous aurez quelques lignes dans une table d'intersection (a.k.a. entité faible) entre la personne et la tâche. Les bases de données sont vraiment bonnes pour travailler avec beaucoup de lignes; ils sont en fait assez mal à l'aise avec beaucoup de colonnes [répétées].

Bel exemple clair donné par whatsisname.

13
Phill W.

Il peut être légitime dans certains champs pré-calculés.

Si certaines de vos requêtes sont coûteuses et que vous décidez d'utiliser des champs pré-calculés mis à jour automatiquement à l'aide de déclencheurs de base de données, il peut être légitime de conserver les listes dans une colonne.

Par exemple, dans l'interface utilisateur, vous souhaitez afficher cette liste à l'aide de la vue grille, où chaque ligne peut ouvrir tous les détails (avec des listes complètes) après un double-clic:

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

Vous gardez la deuxième colonne mise à jour par déclencheur lorsque le client visite un nouvel article ou par tâche planifiée.

Vous pouvez rendre un tel champ disponible même pour la recherche (en tant que texte normal).

Dans de tels cas, la tenue de listes est légitime. Il vous suffit de considérer le cas d'un dépassement possible de la longueur maximale du champ.


De plus, si vous utilisez Microsoft Access, les champs à valeurs multiples sont un autre cas d'utilisation spécial. Ils gèrent automatiquement vos listes dans un champ.

Mais vous pouvez toujours revenir à la forme normalisée standard indiquée dans d'autres réponses.


Résumé: Les formes normales de base de données sont un modèle théorique nécessaire pour comprendre les aspects importants de la modélisation des données. Mais bien sûr, la normalisation ne prend pas en compte les performances ou les autres coûts de récupération des données. Cela sort du cadre de ce modèle théorique. Mais le stockage de listes ou d'autres doublons pré-calculés (et contrôlés) est souvent requis par la mise en œuvre pratique.

À la lumière de ce qui précède, dans la mise en œuvre pratique, préférerions-nous une requête reposant sur une forme normale parfaite et exécutant 20 secondes ou une requête équivalente reposant sur des valeurs pré-calculées qui prend 0,08 s? Personne n'aime que son logiciel soit accusé de lenteur.

4
miroxlav

Étant donné deux tableaux; nous les appellerons Personne et Tâche, chacune avec son propre ID (PersonID, TaskID) ... l'idée de base est de créer une troisième table pour les lier ensemble. Nous appellerons cette table PersonToTask. Au minimum, il devrait avoir sa propre ID, ainsi que les deux autres. Donc, quand il s'agit d'affecter quelqu'un à une tâche; vous n'aurez plus besoin de METTRE À JOUR la table Person, il vous suffit d'insérer une nouvelle ligne dans le PersonToTaskTable. Et la maintenance devient plus facile - le besoin de supprimer une tâche devient simplement un DELETE basé sur TaskID, plus de mise à jour de la table Person et son analyse associée

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

Que diriez-vous d'un simple rapport ou qui est tout affecté à une tâche?

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

Vous pourriez bien sûr faire beaucoup plus; un TimeReport peut être effectué si vous ajoutez des champs DateTime pour TaskAssigned et TaskCompleted. Cela ne tient qu'à toi

0
Mad Myche

Cela peut fonctionner si, par exemple, vous disposez de clés primaires lisibles par l'homme et souhaitez une liste de tâches sans avoir à gérer la nature verticale d'une structure de table. c'est-à-dire beaucoup plus facile à lire le premier tableau.

------------------------  
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 question serait alors: si la liste des tâches devait être stockée ou générée à la demande, ce qui dépendrait en grande partie d'exigences telles que: la fréquence à laquelle la liste est nécessaire, la précision du nombre de lignes de données existantes, la façon dont les données seront utilisées, etc. .. après quoi l'analyse des compromis entre l'expérience utilisateur et les exigences doit être effectuée.

Par exemple, comparer le temps qu'il faudrait pour rappeler les 2 lignes par rapport à l'exécution d'une requête qui générerait les 2 lignes. Si cela prend du temps et que l'utilisateur n'a pas besoin de la liste la plus à jour (* en attendant moins de 1 changement par jour), elle peut être stockée.

Ou si l'utilisateur a besoin d'un historique des tâches qui lui sont assignées, il serait également judicieux que la liste soit stockée. Cela dépend donc vraiment de ce que vous faites, ne dites jamais jamais.

0
Double E CPU

Vous prenez ce qui devrait être une autre table, vous la tournez à 90 degrés et vous la transformez en une autre table.

C'est comme avoir une table de commande où vous avez itemProdcode1, itemQuantity1, itemPrice1 ... itemProdcode37, itemQuantity37, itemPrice37. En plus d'être maladroit à gérer par programme, vous pouvez garantir que demain quelqu'un voudra commander 38 choses.

Je ne le ferais qu'à votre façon si la `` liste '' n'est pas vraiment une liste, c'est-à-dire où elle se situe dans son ensemble et chaque élément de ligne individuel ne fait pas référence à une entité claire et indépendante. Dans ce cas, il suffit de tout remplir dans un type de données suffisamment grand.

Ainsi, une commande est une liste, une nomenclature est une liste (ou une liste de listes, ce qui serait encore plus un cauchemar à mettre en œuvre "latéralement"). Mais une note/commentaire et un poème ne le sont pas.

0
Bloke Down The Pub

Si ce n'est "pas ok", alors il est assez mauvais que chaque Wordpress ait une liste dans wp_usermeta avec wp_capabilities sur une ligne, la liste rejeté_wp_pointers sur une ligne, et d'autres ...

En fait, dans des cas comme celui-ci, il pourrait être meilleur pour la vitesse car vous voudrez presque toujours la liste. Mais Wordpress n'est pas connu pour être l'exemple parfait des meilleures pratiques.

0
NoBugs