Des bases de données relationnelles et multivaluées

Tout le monde connaît les systèmes de bases de données relationnelles, inutiles de présenter ici MySQL, Oracle et autres célèbres SGBDR. Ce que l'on connaît beaucoup moins ce sont les systèmes bases de données multivaluées, par exemple OpenQM. En effet, historiquement parlant les SGBDR se sont très largement imposées, laissant aux multivaluées une place plus que marginale et les entraînant dans l'oubli le plus profond. Sans vouloir remettre ces dernières à la mode, il est très intéressant de connaître les différences entre ces deux modèles, aussi bien par soucis de culture générale que parce que cet exposé répond à un grand problème que beaucoup d'utilisateurs débutants de SGBDR se sont posés un jour ou l'autre. Pour ma part je n'échappe pas à la règle, lorsque j'ai débuté avec MySQL j'ai fait des erreurs par méconnaissance du système.

L'avantage majeur du modèle multivalué

Afin de rendre plus compréhensible nos propos, nous prendront un exemple simple. Dans un système quelconque de gestion des utilisateurs, on souhaite mettre en place un système de liste d'amis, chaque utilisateur ayant un nombre indéterminé d'amis. Lorsque l'on utilise un système multivalué ceci est fort simple : un champ pouvant contenir plusieurs valeurs, il nous suffit d'avoir un champ dans la table des utilisateurs contenant l'identifiant des utilisateurs amis. Voici un exemple de ce que l'on obtient comme structure :

  • Un champ id contenant un identifiant unique pour l'utilisateur.
  • Un champ name contenant le nom ou pseudo.
  • Un champ password contenant un condensat du mot de passe (ici en md5).
  • Un champ friends contenant l'ide des amis de l'utilisateur.

Un exemple de contenu de la table :

+----+--------+----------------------------------+---------+
| id | name   | password                         | friends |
+----+--------+----------------------------------+---------+
|  1 | Paul   | 45723a2af3788c4ff17f8d1114760e62 |       2 |
|  2 | Robert | 5d933eef19aee7da192608de61b6c23d |         |
|  3 | Jean   | 5d933eef19aee7da192608de61b6c23d |       1 |
|                                                        2 |
+----+--------+----------------------------------+---------+

Ici, Paul à mis Robert dans sa liste d'ami, Jean à mis Paul et Robert dans la sienne tandis que Robert, qui n'utilise pas cette liste, n'a mis personne. Tout est simple, pas besoin de se poser plus de questions.

Que faire lorsque l'on utilise un modèle relationnel ?

Mise en place des tables

En général, lorsque l'on débute on est tenté de garder la même structure de table et d'utiliser des séparateurs personnels dans un champ a longueur variable. Il est évident que c'est extrêmement moche de faire ainsi, très peu pratiques pour l'exploitation de la base de donnée, rend compliquée la maintenance et est sujet à causer de jolis bugs dans les logiciels utilisant ce genre de fonctionnement. Afin de combler cette difficulté, il existe une fonctionnalité très puissante qui est la jointure. Une jointure sert à relier deux tables, il est possible d'effectuer plusieurs jointures afin de réaliser la jonction entre plusieurs tables. A noter qu'il existe plusieurs types de jointures, ici nous n'en présenteront qu'une seule. Comme vous l'avez certainement deviné, afin de mettre en place la liste d'amis nous allons simplement créer une nouvelle table et utiliser les jointures SQL dans nos requêtes afin de sortir le bon résultat. Gardons ainsi la même syntaxe que précédemment pour la table users à l'exception du champ friends que l'on retire et créons la table friend_list suivante :

  • Un champ id contenant un identifiant unique pour l'entrée.
  • Un champ user contenant l'identifiant de l'utilisateur.
  • Un champ friend contenant l'identifiant d'un seul ami de cet utilisateur.

Ces trois champs sont largement suffisant, lorsqu'un utilisateur souhaite ajouter un nouvel ami on a qu'a ajouter une nouvelle entrée contenant son id et celui de son ami. On se retrouve donc, pour conserver la même exemple, avec le cas suivant :

`users`
+----+--------+----------------------------------+
| id | name   | password                         |
+----+--------+----------------------------------+
|  1 | Paul   | 45723a2af3788c4ff17f8d1114760e62 | 
|  2 | Robert | 5d933eef19aee7da192608de61b6c23d | 
|  3 | Jean   | 5d933eef19aee7da192608de61b6c23d | 
+----+--------+----------------------------------+
`friends`
+----+------+--------+
| id | user | friend |
+----+------+--------+
|  1 |    1 |      2 | 
|  2 |    3 |      1 | 
|  3 |    3 |      2 | 
+----+------+--------+

Utilisation des ces tables

Grâce aux jointures il va nous être possible de récupérer toutes les informations que l'on souhaite. On utilisera ici une jointure interne (INNER JOIN). La syntaxe simplifiée est la suivante :

SELECT champs FROM table1 INNER JOIN table2 ON condition;

Ici on sélectionne les champs spécifiées depuis la jointure entre table1 et table2 sur une condition spécifique. Attention, si l'on ne spécifie pas de condition alors le produit cartésien sera utilisé, ce qui sur quelques milliers de données peut mettre un serveur modeste sur les rotules. Il est également possible de multiplier les jointures entre tables comme nous le verrons, il faut alors penser à mettre des parenthèse là où il faut afin d'éviter les erreurs.
On remarquera que pour préciser de quelle table provient un champ on utilise la notation table.champ. Ce n'est pas obligatoire lorsqu'un champ est unique mais je trouve cette manière de faire bien plus claire lors des jointures.

Sélection de l'id de tous les amis d'un utilisateur particulier

Ici on sélectionne tout à tour les amis de Paul, Robert en enfin Jean, une simple jointure suffit.

mysql> SELECT `friends`.`friend` FROM `friends` INNER JOIN `user` ON `friends`.`user` = `users`.`id` WHERE `users`.`id` = 1;
+--------+
| friend |
+--------+
|      2 | 
+--------+
1 row in set (0.00 sec)

mysql> SELECT `friends`.`friend` FROM `friends` INNER JOIN `user` ON `friends`.`user` = `users`.`id` WHERE `users`.`id` = 2;
Empty set (0.00 sec)

mysql> SELECT `friends`.`friend` FROM `friends` INNER JOIN `user` ON `friends`.`user` = `users`.`id` WHERE `users`.`id` = 3;
+--------+
| friend |
+--------+
|      1 | 
|      2 | 
+--------+
2 rows in set (0.00 sec)

Sélection du nom de tous les amis d'un utilisateur particulier

En reprenant le cas précédent, il nous faut faire une seconde jointure afin de relier ce résultat à la table des utilisateurs afin d'obtenir le nom de cet utilisateur.

mysql> SELECT `users`.`name` FROM ((`friends` INNER JOIN `users` AS `fusers` ON `friends`.`user` = `fusers`.`id`) INNER JOIN `users` ON `users`.`id` = `friends`.`friend`) WHERE `fusers`.`id` = 1;
+--------+
| name   |
+--------+
| Robert | 
+--------+
1 row in set (0.00 sec)

mysql> SELECT `users`.`name` FROM ((`friends` INNER JOIN `users` AS `fusers` ON `friends`.`user` = `fusers`.`id`) INNER JOIN `users` ON `users`.`id` = `friends`.`friend`) WHERE `fusers`.`id` = 2;
Empty set (0.01 sec)

mysql> SELECT `users`.`name` FROM ((`friends` INNER JOIN `users` AS `fusers` ON `friends`.`user` = `fusers`.`id`) INNER JOIN `users` ON `users`.`id` = `friends`.`friend`) WHERE `fusers`.`id` = 3;
+--------+
| name   |
+--------+
| Paul   | 
| Robert | 
+--------+
2 rows in set (0.00 sec)

On remarquera qu'il a ici été nécessaire de faire un alias de users car sinon il y aurait eu confusion lors de la seconde jointure.

Conclusion

Le modèle multivalué est peut être le plus intuitif et le plus simple à mettre en place, cependant une bonne habitude des jointures permet de faire en une seule requête des choses extrêmement intéressantes qui auraient certainement demandé plusieurs requêtes sur un système multivalué. Les deux modèles sont intéressant et pour ma part j'apprécie chacun d'entre eux, cependant dans la pratique je me sert presque exclusivement de gestionnaires de basses de données relationnelle. En effet, contrairement aux systèmes relationnels qui ont le SQL en commun avec une syntaxe globalement identique (à quelques exceptions près), les systèmes multivalués ont chacun leur manière de faire. Ainsi OpenQM n'est disponible que sous la forme d'une sorte de machine virtuelle comprenant son propre langage dérivé du BASIC et difficile à interfacer avec des programmes externes, ce qui rend son utilisation vraiment trop spécifique et complexe.