Bon, pour commencer, optimiser la base de données :
Je suis un peu maniaque sur les bords, donc j'ai supprimé la clé inutile sur bati_user pour la remplacer par une clé double (user / bati), les clés, sont en bigint et non-singé (vous n'aurez jamais de clé négative), enfin j'ai renommé les champs pour facilité les jointures (vous verrez plus bas).
Bon, j'aurais pu aussi rajouter les contraintes de clé étrangère, mais ça n'optimise en rien les select, ça sécurise juste les insert et update, donc je vous les laisse.
La nouvelle base de test :
Bon ensuite, deux petites choses :
1. le SELECT * c'est bien quand on est sûr de vouloir vraiment tous les champs, mais avec un *, on ne sait jamais ce qu'on remonte, et s'il y a trop de champs, ça ralenti le résultat, il vaut donc mieux préciser à chaque fois les champs que l'on souhaite récupérer.
2. Les requêtes imbriqués c'est lent, terriblement lent, car selon comment elles sont mise en place, elle peuvent être chacune appelée pour chaque ligne du premier select... donc à supprimer.
Ca donne ceci :
Les bâtiments déjà construit pour l'utilisateur 1 :
Les bâtiments qui restent à construire :
Enfin les bâtiments dont les prérequis (s'il y en a) peuvent être construit :
Un dernier point, le OR dans la clause WHERE, c'est aussi un truc qui a tendance à ralentir, alors on va essayer de l'enlever en faisant l'Union de deux requêtes, les bâtiments sans prérequis et ceux dont les prérequis sont valide :
Voilà, on pourra difficilement faire plus rapide…
Ensuite, histoire d'essayer d'utiliser le cache de la base de données, je conseillerai de créer une vue avec cette requête (en ajoutant l'utilisateur dans le résultat)
Reste un cas qui n'est pas pris en compte dans cette requête : le cas où deux bâtiments sont requis pour la construction d'un troisième !
Après relecture, le seul point de ralentissement qui reste dans la requête c'est ce morceau là :
Je suis un peu maniaque sur les bords, donc j'ai supprimé la clé inutile sur bati_user pour la remplacer par une clé double (user / bati), les clés, sont en bigint et non-singé (vous n'aurez jamais de clé négative), enfin j'ai renommé les champs pour facilité les jointures (vous verrez plus bas).
Bon, j'aurais pu aussi rajouter les contraintes de clé étrangère, mais ça n'optimise en rien les select, ça sécurise juste les insert et update, donc je vous les laisse.
La nouvelle base de test :
--
-- Structure de la table `bati`
--
CREATE TABLE `bati` (
`bati_id` bigint(20) unsigned NOT NULL auto_increment,
`nom` varchar(100) collate utf8_unicode_ci NOT NULL,
PRIMARY KEY (`bati_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=7 ;
--
-- Contenu de la table `bati`
--
INSERT INTO `bati` VALUES(1, 'bati 1');
INSERT INTO `bati` VALUES(2, 'bati 2');
INSERT INTO `bati` VALUES(3, 'bati 3');
INSERT INTO `bati` VALUES(4, 'bati 4');
INSERT INTO `bati` VALUES(5, 'bati 5');
INSERT INTO `bati` VALUES(6, 'bati 6');
-- --------------------------------------------------------
--
-- Structure de la table `bati_user`
--
CREATE TABLE `bati_user` (
`bati_id` bigint(20) unsigned NOT NULL,
`user_id` bigint(20) unsigned NOT NULL,
`niveau` int(11) NOT NULL,
PRIMARY KEY (`bati_id`,`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
--
-- Contenu de la table `bati_user`
--
INSERT INTO `bati_user` VALUES(1, 1, 5);
INSERT INTO `bati_user` VALUES(4, 1, 2);
-- --------------------------------------------------------
--
-- Structure de la table `prerequis`
--
CREATE TABLE `prerequis` (
`prerequis_id` bigint(20) unsigned NOT NULL auto_increment,
`bati_id` bigint(20) unsigned NOT NULL,
`bati_requis_id` bigint(20) unsigned NOT NULL,
`niveau_prerequis` int(11) NOT NULL,
UNIQUE KEY `id` (`prerequis_id`),
UNIQUE KEY `bati_id_2` (`bati_id`,`bati_requis_id`),
KEY `bati_id` (`bati_id`),
KEY `bati_requis_id` (`bati_requis_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=6 ;
--
-- Contenu de la table `prerequis`
--
INSERT INTO `prerequis` VALUES(1, 2, 1, 1);
INSERT INTO `prerequis` VALUES(2, 3, 1, 10);
INSERT INTO `prerequis` VALUES(3, 4, 1, 5);
INSERT INTO `prerequis` VALUES(4, 5, 1, 1);
INSERT INTO `prerequis` VALUES(5, 5, 2, 1);
Bon ensuite, deux petites choses :
1. le SELECT * c'est bien quand on est sûr de vouloir vraiment tous les champs, mais avec un *, on ne sait jamais ce qu'on remonte, et s'il y a trop de champs, ça ralenti le résultat, il vaut donc mieux préciser à chaque fois les champs que l'on souhaite récupérer.
2. Les requêtes imbriqués c'est lent, terriblement lent, car selon comment elles sont mise en place, elle peuvent être chacune appelée pour chaque ligne du premier select... donc à supprimer.
Ca donne ceci :
Les bâtiments déjà construit pour l'utilisateur 1 :
SELECT bati.bati_id, bati.nom, niveau
FROM bati
INNER JOIN bati_user USING (bati_id)
WHERE user_id = 1
Les bâtiments qui restent à construire :
SELECT bati.bati_id, bati.nom
FROM bati
LEFT JOIN bati_user ON (bati.bati_id = bati_user.bati_id AND user_id = 1)
WHERE bati_user.bati_id IS NULL
La requête se traduisant par "les bâtiments qui n'existent pas chez l'utilisateur", plutôt que le NOT IN(SELECT …) utilisez le LEFT JOIN … WHERE id IS NULLEnfin les bâtiments dont les prérequis (s'il y en a) peuvent être construit :
SELECT bati.bati_id, nom
FROM bati
LEFT JOIN bati_user
ON (bati.bati_id = bati_user.bati_id AND user_id = 1)
LEFT JOIN prerequis
ON (bati.bati_id = prerequis.bati_id)
LEFT JOIN bati_user AS bati_user_exist
ON (prerequis.bati_requis_id = bati_user_exist.bati_id AND bati_user_exist.user_id = 1 AND bati_user_exist.niveau >= prerequis.niveau_prerequis)
WHERE bati_user.bati_id IS NULL AND (prerequis_id IS NULL OR prerequis_id IS NOT NULL AND bati_user_exist.bati_id IS NOT NULL)
Un dernier point, le OR dans la clause WHERE, c'est aussi un truc qui a tendance à ralentir, alors on va essayer de l'enlever en faisant l'Union de deux requêtes, les bâtiments sans prérequis et ceux dont les prérequis sont valide :
SELECT bati.bati_id, nom
FROM bati
LEFT JOIN bati_user
ON (bati.bati_id = bati_user.bati_id AND user_id = 1)
LEFT JOIN prerequis
ON (bati.bati_id = prerequis.bati_id)
WHERE bati_user.bati_id IS NULL AND prerequis_id IS NULL
UNION
SELECT bati.bati_id, nom
FROM bati
LEFT JOIN bati_user
ON (bati.bati_id = bati_user.bati_id AND user_id = 1)
LEFT JOIN prerequis
ON (bati.bati_id = prerequis.bati_id)
LEFT JOIN bati_user AS bati_user_exist
ON (prerequis.bati_requis_id = bati_user_exist.bati_id AND bati_user_exist.user_id = 1 AND bati_user_exist.niveau >= prerequis.niveau_prerequis)
WHERE bati_user.bati_id IS NULL AND prerequis_id IS NOT NULL AND bati_user_exist.bati_id IS NOT NULL
Voilà, on pourra difficilement faire plus rapide…
Ensuite, histoire d'essayer d'utiliser le cache de la base de données, je conseillerai de créer une vue avec cette requête (en ajoutant l'utilisateur dans le résultat)
Reste un cas qui n'est pas pris en compte dans cette requête : le cas où deux bâtiments sont requis pour la construction d'un troisième !
Après relecture, le seul point de ralentissement qui reste dans la requête c'est ce morceau là :
bati_user_exist.niveau >= prerequis.niveau_prerequis