Avantage(s) d'une jointure

Petit nouveau ! | 5 Messages

09 janv. 2013, 15:49

Bonjour.

J'ai deux tables liées entre elles :

TABLE 1 'elements' :
id (PK) ; type ; nom_original ; nom_occidental ; etc.

TABLE 2 'elements_liens'
id (PK) ; type_l ; id_lié (FK) ; nom (> c'est un "nom_occidental" de la table 1) ; id_el (> c'est l'id correspondant au "nom_occidental", je sais, ça fait doublon mais ça évite de refaire une toute petite requête, enfin, je crois)

La clef étrangère est la colonne id_lié et l'id_el renvoie à l'id de la table 1 puisque les éléments liés à chaque élément de la table 1 proviennent de cette table et on donc leur propres informations.

Bref, j'ai besoin d'afficher toutes ces infos et je me suis dit, forcément, tu vas faire une jointure puisque ça sert à ça, et que c'est bien les jointures, il paraît que ça prend moins de temps, ça envoie moins de requête, toussa. Et en avançant dans mon code, j'ai fini par utiliser les deux solutions, avec jointures ou avec deux requêtes séparées. Et tant qu'à faire, j'ai calculé le temps que donnent les deux codes pour me rendre compte après une moyenne de 100 essais que l'utilisation de la jointure met presque deux fois plus de temps qu'avec deux requêtes séparées. C'est aussi super chiant à mettre en place.
Donc, après vous avoir passé mes deux codes, je voudrais savoir ce qu'il en était : est-ce que c'est mon code qui est mauvais, qui peut être amélioré de manière significative (en sachant qu'il s'agit d'une version épurée pour les besoins de ce sujet, je ne compte pas l'utiliser tel quel mais chaque élément dispensé me servira à un moment où à un autre même si ça ne ressort que dans un echo), ou est-ce que c'est mon usage spécifique qui invalide l'intérêt d'une jointure ?

J'ai essayé de commenter au possible mon code.

Ha oui, je vous le contenu des deux tables pour $id=2;

TABLE 1 'elements'
id=2 ; type='gamm'; nom_original='fruit_eng' ; nom_occidental='fruit' ; etc.

TABLE 2 'elements_liens'
id=1 ; type_l='asc' ; id_lié=2 ; nom='chou' ; id_el=1 ;
id=2 ; type_l='des' ; id_lié=2 ; nom='carotte ' ; id_el=4 ;
id=3 ; type_l='des' ; id_lié=2 ; nom='patate' ; id_el=6 ;
id=4 ; type_l='des' ; id_lié=2 ; nom='tomate ' ; id_el=5 ;
//code piqué quelque part pour calculer l'exécution du script
//temps au début du script
$temps1 = microtime(); 
$temps1 = explode(' ', $temps1);
$debut1 = $temps1[1] + $temps1[0];

//PREMIER SCRIPT AVEC JOINTURE

if(isset($_GET['id']) AND $_GET['id']!='')
{
	$id=filtre_d_recues($_GET['id']); //filtre_d_recues = fonction intval pour ce cas là
	$v=mysqli_query($db,"SELECT * FROM elements_liens el_l RIGHT JOIN elements el ON el.id=el_l.id_lié WHERE el.id=".$id."");
	$count_r=mysqli_num_rows($v);
	echo 'NBR> '.$count_r.'<br/>';
	$d=mysqli_fetch_assoc($v);
	
	//simple dispensation de données de la table 1
	echo $d['type'].'<br/>';
	echo $d['nom_occidental'].'<br/>';
	echo $d['nom_original'].'<br/>';
	
	//les colonnes "type" et "type_l" ne renvoient que deux string : "asc" ou "des" qui me servent de filtre mais j'ai besoin de savoir dans un premier temps si la table "elements_liens" me donnent des résultats en fonction du "type_l" (asc ou des) avant de lancer des boucles pour afficher les "nom" et "id_el" qui vont avec.
	$req_asc=mysqli_query($db,"SELECT COUNT(id) AS nbr_asc FROM elements_liens WHERE type_l='asc' AND id_lié=".$id."");
	$req_des=mysqli_query($db,"SELECT COUNT(id) AS nbr_des FROM elements_liens WHERE type_l='des' AND id_lié=".$id."");
	$fetch_asc=mysqli_fetch_assoc($req_asc);
	$fetch_des=mysqli_fetch_assoc($req_des);
	
	if($fetch_asc['nbr_asc']>0)
	{
		mysqli_data_seek($v,0);
		while($d=mysqli_fetch_assoc($v)) if($d['type_l']=='asc') echo 'ASC> '.$d['nom'].' id> '.$d['id_el'].'<br/>';
	}

	if($fetch_des['nbr_des']>0)
	{
		mysqli_data_seek($v,0);
		while($d=mysqli_fetch_assoc($v))if($d['type_l']=='des') echo 'DES> '.$d['nom'].' id> '.$d['id_el'].'<br/>';
	}
}

//calcul du temps à la fin du script
$temps1 = microtime();
$temps1 = explode(' ', $temps1);
$fin1 = $temps1[1] + $temps1[0];
 
echo '_________________<br/>';	

//nouveau calcul du temps au début du second script
$temps2 = microtime();      // Timestamp actuel avec microsecondes
$temps2 = explode(' ', $temps2);      // Segmente le timestamp
$debut2 = $temps2[1] + $temps2[0];

//SECOND SCRIPT SANS JOINTURE

if(isset($_GET['id']) AND $_GET['id']!='')
{
	$id2=filtre_d_recues($_GET['id']);
	$req=mysqli_query($db,"SELECT * FROM elements WHERE id=".$id2."");
	$count_r2=mysqli_num_rows($req);
	echo 'NBR> '.$count_r2.'<br/>';
	$d2=mysqli_fetch_assoc($req);
	
	echo $d2['type'].'<br/>';
	echo $d2['nom_occidental'].'<br/>';
	echo $d2['nom_original'].'<br/>';

	$req_asc2=mysqli_query($db,"SELECT * FROM elements_liens WHERE type_l='asc' AND id_lié=".$id2."");
	$req_des2=mysqli_query($db,"SELECT * FROM elements_liens WHERE type_l='des' AND id_lié=".$id2."");
	
	if(mysqli_num_rows($req_asc2)>0) while($d2=mysqli_fetch_assoc($req_asc2)) echo 'ASC> '.$d2['nom'].' id> '.$d2['id_el'].'<br/>';
	if(mysqli_num_rows($req_des2)>0) while($d3=mysqli_fetch_assoc($req_des2)) echo 'DES> '.$d3['nom'].' id> '.$d3['id_el'].'<br/>';
}

//nouveau calcul du temps à la fin du second script
$temps2 = microtime();
$temps2 = explode(' ', $temps2);
$fin2 = $temps2[1] + $temps2[0];
$calcul1=round(($fin1 - $debut1),6);
$calcul2=round(($fin2 - $debut2),6);

 echo '_________________<br/>';

echo "<span style='color:red;'>PREMIER SCRIPT (JOINture) script exécuté en ".$calcul1." secondes.</span><br/>";
$_SESSION['res1'][]=$calcul1;
$_SESSION['res2'][]=$calcul2;
echo "<span style='color:red;'>SECOND SCRIPT (SANS jointure) exécutée en ".$calcul2." secondes.</span><br/>";

echo 'Moyenne du 1er script> '.array_sum($_SESSION['res1'])/count($_SESSION['res1']).'<br/>';
echo 'Moyenne du 2nd script '.array_sum($_SESSION['res2'])/count($_SESSION['res2']).'<br/>';

echo count($_SESSION['res1']).'<br/>';
echo count($_SESSION['res2']);
exit;
Résultats du script :
NBR> 4
gamm
fruit
fruit_eng
ASC> chou id> 1
DES> carotte id> 4
DES> patate id> 6
DES> tomate id> 5
_________________
NBR> 1
gamm
fruit
fruit_eng
ASC> chou id> 1
DES> carotte id> 4
DES> patate id> 6
DES> tomate id> 5
_________________
PREMIER SCRIPT (JOINture) script exécuté en 0.003178 secondes.
SECOND SCRIPT (SANS jointure) exécutée en 0.001295 secondes.
Moyenne du 1er script> 0.0025596
Moyenne du 2nd script> 0.00147112
100
100
Personnellement, je ne pense pas avoir fait un usage abusif de mes deux tables liées mais je compte sur vous pour me dire ce qu'il en est. En espérant avoir été assez clair.
Merci d'avance.
Modifié en dernier par Blop le 09 janv. 2013, 21:24, modifié 2 fois.

ViPHP
xTG
ViPHP | 7331 Messages

09 janv. 2013, 15:56

Tout simplement que ta jointure n'est pas optimisée...
La clause WHERE est appliquée après la jointure, donc tu fais toutes les correspondances puis ensuite tu filtres avec ta requête.
$v=mysqli_query($db,"SELECT * FROM elements_liens el_l RIGHT JOIN elements el ON (el.id=".$id." AND el.id=el_l.id_lié)");
Avec celle-ci tu filtres tout en faisant la jointure.

Une requête avec jointure face à deux requêtes montre tout son potentiel lorsque tu ne filtres pas sur l'élément de jointure.

Eléphant du PHP | 267 Messages

09 janv. 2013, 16:15

on ne fait pas une jointure pour gagner du temps
on fait une jointure parce qu'on en a besoin

effectivement, une jointure, c'est coûteux, voire très coûteux mais y a des moyens d'optimisation :
- en écrivant correctement les requêtes
- en utilisant des index

@+
dix2

ViPHP
xTG
ViPHP | 7331 Messages

09 janv. 2013, 16:35

on ne fait pas une jointure pour gagner du temps
on fait une jointure parce qu'on en a besoin
Ce n'est pas forcement vrai.
$res = mysqli_query('SELECT idCat, name FROM category ORDER BY idCat');
while( $categories = mysqli_fetch_array($res) ){
  echo '<h2>' . $categories['name'] . '</h2>';
  $res2 = mysqli_query('SELECT idArt, title FROM news WHERE idCat = ' . $categories['idCat']);
  while( $news = mysqli_fetch_array($res2) ){
    echo $news['title'] . '<br />';
  }
}
A ton avis est-ce qu'une jointure n'irai pas plus vite que ça ? :mrgreen:

Petit nouveau ! | 5 Messages

09 janv. 2013, 16:43

Tout simplement que ta jointure n'est pas optimisée...
La clause WHERE est appliquée après la jointure, donc tu fais toutes les correspondances puis ensuite tu filtres avec ta requête.
$v=mysqli_query($db,"SELECT * FROM elements_liens el_l RIGHT JOIN elements el ON (el.id=".$id." AND el.id=el_l.id_lié)");
Avec celle-ci tu filtres tout en faisant la jointure.
J'ai remplacé cette portion de code, le problème c'est que cela me donne de mauvais résultats. Au lieu de 4 lignes, j'en obtiens 11, soit toutes les lignes de ma table + 3 (4-1) lignes obtenues grâce à la jointure de l'autre côté. C'est normal puisque c'est une jointure externe mais le el.id=".$id." ne fait plus son travail en évacuant tous les autres résultats inutiles. Bref, pas de filtrage...
on fait une jointure parce qu'on en a besoin
Je tiens à préciser que c'est la première fois que j'utilise une jointure.
Donc, par défaut, une jointure n'est pas la solution miracle pour lier deux tables, c'est même plutôt encombrant ?
Par 'utiliser des index', que veux-tu dire par là ?

Si tu n'as pas le temps de me répondre en détail, de la doc me convient très bien pour un peu que ce ne soit pas un bloc sur les jointures en général.


Par contre, pouvez-vous confirmer que dans mon cas, je n'ai pas d'autre choix que de faire une nouvelle requête SELECT COUNT à chaque fois que je veux connaître la présence de résultats selon certains critères ? Ici s'il existe un résultat dans la table 2 'elements_liens' lorsque le type_l est soit 'asc' ou 'des' ? Ca ne me paraît guère propre alors je me demandais s'il était possible d'inclure ça dans la jointure, tant qu'à faire.

La recherche de performance est vraiment une obsession mais lorsqu'on n'a pas les clefs pour, je vous avoue que c'est vraiment une horreur. Et là, je crois que les jointures et moi, ça va bien faire 2. En fait, c'est plus pour éviter une requête dans une boucle, si j'ai bien comprite.

Eléphant du PHP | 267 Messages

09 janv. 2013, 16:55

on ne fait pas une jointure pour gagner du temps
on fait une jointure parce qu'on en a besoin
Ce n'est pas forcement vrai.
Le fait de gagner du temps n'est pas l'objectif d'une jointure, ça peut éventuellement être une conséquence

Mammouth du PHP | 571 Messages

09 janv. 2013, 17:14

@Blop,

je trouve ta conclusion un peu trop sommaire pourquoi? parce que ta BD contient peut être une dizaine voire une centaines d'enregistrements ce qui n'a pas trop d'influence sur la comparaison. Or dans la vraie vie une table contient souvent des milliers voire des millions d'enregistrements(ce qui est normal) on ne va pas s'amuser à faire un "select * from table1"(où table1 contient 100 mille lignes) ça prend des minutes pour afficher une telle requête.

Il faut utiliser le profiler de MySQL pour optimiser une requête(temps d'exécution par exemple...) et faire un teste sur des milliers de données.

ViPHP
AB
ViPHP | 5818 Messages

09 janv. 2013, 19:47

Peut-être aussi y aurait-il moyen d'améliorer l'organisation de tes données... et donc de tes tables.

Sinon comme dit plus haut on ne fait pas des jointures pour faire des jointures mais quand on en a besoin. Je ne suis pas rentré dans le détail de ton code (que je trouve un peu compliqué, j'ai du mal saisir la logique) mais grossièrement le code sans jointure comporte 3 requêtes et idem pour le code avec jointure. A priori, j'ai envie de dire que ce n'est pas dans ces conditions que les jointures sont utiles.