Requêtes complexes et sous-requêtes

Article écrit par kazhar

Il arrive assez régulièrement, lorsque l'on cherche à développer une application web utilisant plusieurs bases de données, que l'on ait besoin de construire des requêtes SQL qui vont paraitre au premier abord plutot complexes.

Prenons un exemple de forum.
Nous avons notre base de données qui est construite de la manière suivante :

Une base topics, avec les champs :
- IdTopic : identifiant unique du topic, clé primaire
- TitreTopic : titre du topic

Une base messages, avec les champs :
- IdMessage : identifiant unique du message, clé primaire
- IdUser : identifiant du posteur du message, clé étrangère
- IdTopic : identifiant du topic, clé étrangère
- DateMessage : date du message, au format TimeStamp
- Message : le contenu du message

Une base utilisateurs avec les champs :
- IdUser : identifiant de l'utilisateur, clé primaire
- Pseudo : pseudonyme de l'utilisateur

Maintenant, nous voulons afficher la liste de tous les topics.
Mais pour ne pas faire simple, nous désirons connaitre le pseudonyme du posteur du topic, et celui du posteur du dernier message.

Il va donc nous falloir utiliser deux fois la table utilisateurs dans notre requête.
Nous allons donc devoir la renommer. Nous aurions un conflit au niveau des noms de tables sinon.
La solution pour renommer une table dans une requete SQL est de mettre le nouveau nom de la table après son véritable nom dans le from :

Code :
FROM `topics`, `users` u, `users` v


Ici, nous n'avons plus de table s'appellant users, mais nous avons deux tables u et v basées sur cette table users.

Maintenant, il nous faut également signaler que pour une table users, nous voulons le premier enregistrement.
Et que pour l'autre, nous voulons le dernier.
Pour avoir le premier, la solution est très simple. Par défaut, la contrainte de jointure nous mettra le premier message.

Mais pour le dernier, c'est plus compliqué, et c'est la qu'interviennent les sous-requêtes.
Une sous-requêtes, comme son nom l'indique est une requête à l'intérieur d'une requête.
Voici pour notre exemple :

Code :
WHERE n.Id = (SELECT Id
FROM forum_messages
WHERE Topic = forum_topics.Id
ORDER BY `Date`
LIMIT 1)


Ici, nous récupérons le message qui a pour identifiant le plus récent. Donc, notre dernier message. Le problème est résolu !

Pour ceux qui n'y verraient pas encore forcément très clair, voici notre requête telle qu'elle pourrait l'être :

Code :
SELECT forum_topics.Id, `Titre`, `Epingle`, `Ferme`, u.Pseudo AS Pseudo, v.Pseudo AS LastPoster, DAY(m.Date) AS Day, MONTH(m.Date) AS Month, YEAR(m.Date) AS Year, DAY(n.Date) AS Day2, MONTH(n.Date) AS Month2, YEAR(n.Date) AS Year2, count(m.Id)-1 AS NbMes
FROM `forum_topics`, `forum_messages` m, `forum_messages` n, `core_users` u, `core_users` v
WHERE forum_topics.Forum = ".$id."
AND forum_topics.Id = m.Topic
AND m.Auteur = u.Id
AND forum_topics.Id = n.Topic
AND n.Auteur = v.Id
AND n.Id = (SELECT Id
FROM forum_messages
WHERE Topic = forum_topics.Id
ORDER BY `Date`
LIMIT 1)
GROUP BY forum_topics.Id
ORDER BY `Epingle` DESC, n.Date DESC
LIMIT 50;



/!\ Il pourrait parraitre plus simple de faire une première requête, que l'on parcourerait, et dans cette requête, d'en faire une autre récupérant les données supplémentaires.
C'est une solution en effet, mais dans notre cas, nous avons tenté d'optimiser notre code au maximum, et avons donc préféré ne faire appel qu'une seule fois à notre base de données.

Source : http://www.phportail.net