JeuWeb - Crée ton jeu par navigateur
Requête, boucle et jointure - Version imprimable

+- JeuWeb - Crée ton jeu par navigateur (https://jeuweb.org)
+-- Forum : Discussions, Aide, Ressources... (https://jeuweb.org/forumdisplay.php?fid=38)
+--- Forum : Programmation, infrastructure (https://jeuweb.org/forumdisplay.php?fid=51)
+--- Sujet : Requête, boucle et jointure (/showthread.php?tid=2255)

Pages : 1 2


Requête, boucle et jointure - phenix - 07-01-2008

Bonjour à tous,

Cela fait un moment que sa me turlipine, et soucieux d'optimise un peu mon site (enfin c'est surtout en regardant les graphique d'utilisation du serveur Cool) j'ai un peu regarder les topic de jeuPHP et je me suis renseigner.

J'ai vut qu'il était largement déconseiller de faire des requêtes dans des boucles (au final c'est largement conpréhensible :mauvaisSmile.

Et horreur pour moi, j'en fait beaucoup trop. Seulement je n'arrive pas a trouver un moyen de les enlever des boucles, je m'adresse donc a vous.

j'ai 2 tables: monstre et users. La première contient toute les donnée des monstres (niveau, caractéristique, etc...) et la seconde, toute les données des joueurs (Dur :goodSmile.

Voici leurs structures:

Users:
Code :
CREATE TABLE `users` (
  `id` int(11) NOT NULL auto_increment,
  `pseudo` text NOT NULL,
  `password` text NOT NULL,
  `forceu` int(11) NOT NULL,
  `dexterite` int(11) NOT NULL,
  `endurance` int(11) NOT NULL,
  `pouvoir` int(11) NOT NULL,
  `PO` int(11) NOT NULL,
  `PV` int(11) NOT NULL,
  `exp` int(11) NOT NULL,
  `niv` int(11) NOT NULL,
  `lieu` text NOT NULL,
  `quartier` text NOT NULL,
  `avatar` text NOT NULL,
  `arme` text NOT NULL,
  `armure` text NOT NULL,
  `jambe` text NOT NULL,
  `tete` text NOT NULL,
  `bouclier` text NOT NULL,
  `gant` text NOT NULL,
  `PA` int(11) NOT NULL,
  `tempsPA` int(11) NOT NULL,
  `mail` text NOT NULL,
  `orientation` text NOT NULL,
  `map` text NOT NULL,
  `guilde` text NOT NULL,
  `profil` text NOT NULL,
  `statut` smallint(6) NOT NULL,
  `suspent` text NOT NULL,
  `offrande` int(11) NOT NULL,
  `resurection` text NOT NULL,
  `reg` smallint(6) NOT NULL,
  `groupe` text NOT NULL,
  `messA` text NOT NULL,
  `position` int(11) NOT NULL,
  `PM` smallint(6) NOT NULL,
  `tempsPM` bigint(20) NOT NULL,
  PRIMARY KEY  (`id`),
  KEY `position` (`position`),
  KEY `statut` (`statut`),
  FULLTEXT KEY `pseudo` (`pseudo`),
  FULLTEXT KEY `lieu` (`lieu`),
  FULLTEXT KEY `quartier` (`quartier`),
  FULLTEXT KEY `arme` (`arme`),
  FULLTEXT KEY `armure` (`armure`,`jambe`,`tete`,`bouclier`,`gant`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=29 ;

Monstre:
Code :
CREATE TABLE `monstre` (
  `id` int(11) NOT NULL auto_increment,
  `lieu` text NOT NULL,
  `quartier` text NOT NULL,
  `nom` text NOT NULL,
  `niveau` int(11) NOT NULL,
  `forceu` int(11) NOT NULL,
  `endurance` int(11) NOT NULL,
  `dexterite` int(11) NOT NULL,
  `pouvoir` int(11) NOT NULL,
  `PV` int(11) NOT NULL,
  `armure` int(11) NOT NULL,
  `image` text NOT NULL,
  `reg` mediumint(9) NOT NULL,
  `degat_min` int(11) NOT NULL,
  `degat_max` int(11) NOT NULL,
  `position` int(11) NOT NULL,
  PRIMARY KEY  (`id`),
  KEY `position` (`position`),
  FULLTEXT KEY `lieu` (`lieu`,`quartier`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=20235 ;

Pour afficher les monstre et les joueurs sur la map, je récupère les positions:

Code PHP :
<?php 
//Tableau des joueurs
$tab_pos = array();
$sql = mysql_query('SELECT position FROM users WHERE lieu=\''.$lieu.'\' AND quartier=\''.$quartier.'\' AND statut=\'1\'') or die(mysql_error());
while (
$a = mysql_fetch_assoc($sql))
{
array_push($tab_pos,$a['position']); }

//Tableau des monstres
$sql = mysql_query('SELECT position FROM monstre WHERE lieu=\''.$lieu.'\' and quartier=\''.$quartier.'\'') or die(mysql_error());
while (
$monstre_info = mysql_fetch_assoc($sql))
{
array_push($tab_pos,$monstre_info['position']); }

Problème: je doit faire une requête suplémantaire dans ma boucle pour récupérer le reste des informations du joueur/monstre:

Code PHP :
<?php 
function create_profil_monstre($id_monstre)
{
$monstre = mysql_query('SELECT id,nom,niveau,lieu,PV,armure,image FROM monstre WHERE id=\''.$id_monstre.'\'') or die(mysql_error());

$donnees = mysql_fetch_assoc($monstre);

$idcible = $donnees['id'];
$nomcible = $donnees['nom'];
$niveaucible = $donnees['niveau'];
$lieucible = $donnees['lieu'];
$PVcible = $donnees['PV'];
$armurecible = $donnees['armure'];
$imagecible = $donnees['image'];

$sql = mysql_query('SELECT PV FROM listemonstre WHERE nom=\''.$nomcible.'\'') or die(mysql_error());
$recuppv = mysql_fetch_assoc($sql);
$PVrecup = $recuppv['PV'];

$profil = '['.$idcible.'] '.$donnees['nom'].'<br /><img src='.$imagecible.' width=50 height=50 /><br /> Niveau: '.$niveaucible.' <br /> PV: '.$PVcible.'/'.$PVrecup.' <br /> Etat: ';
$etatduperso = mysql_query("SELECT etat FROM etat WHERE joueur='$idcible'") or die(mysql_error());
while(
$etatt = mysql_fetch_assoc($etatduperso))
{
$profil .= '<br />'.addslashes($etatt['etat']);
}
return
$profil;
}

Comment pourrai-je faire pour envoier directement les info dans la fonction Sans devoir faire de requête (et bien sur en séléctionnant les bonne information ) ?

Merci d'avance de votre aide éclairé :wowowow:

Phenix


RE: Requête, boucle et jointure - Sephi-Chan - 07-01-2008

Il faudrait que tu nous montre ce à quoi ressemble la table état. Je suppose que tu veux avoir une relation de type 0, n, c'est à dire un monstre peut avoir zéro ou plusieurs états (empoisonnés, paralysée, etc.). Ça nous permettra de t'indiquer comment les sortir de ta boucle.

Il faudrait aussi savoir comment fonctionne ta table listemonstre.

En attendant, un petit commentaire :

Utilise plutôt des identifiants numériques, plus rapides : le traitement de clés FULLTEXT est bien plus lent (et plus lourd en base).

Par exemple, dans la colonne bouclier, je suppose que tu indiques le nom du bouclier dont les statistiques sont stockées dans une table objets. Si ce n'est pas le cas, je te conseille de le faire et de faire références aux objets par leur identifiant. Car tu imagines si tu dois écrire "Égide du Dieu sanglant" dans la case bouclier de chaque monstre, ça fait une table très lourde !?


Sephi-Chan


RE: Requête, boucle et jointure - phenix - 07-01-2008

Citation :Il faudrait que tu nous montre ce à quoi ressemble la table état. Je suppose que tu veux avoir une relation de type 0, n, c'est à dire un monstre peut avoir zéro ou plusieurs états (empoisonnés, paralysée, etc.). Ça nous permettra de t'indiquer comment les sortir de ta boucle.

Voila

Code :
CREATE TABLE `etat` (
  `id` int(11) NOT NULL auto_increment,
  `joueur` text NOT NULL,
  `etat` text NOT NULL,
  `forceu` mediumint(9) NOT NULL,
  `dexterite` mediumint(9) NOT NULL,
  `endurance` mediumint(9) NOT NULL,
  `pouvoir` mediumint(9) NOT NULL,
  `bonusdegat` mediumint(9) NOT NULL,
  `reschoc` mediumint(9) NOT NULL,
  `bonusmagic` mediumint(9) NOT NULL,
  `resmagic` mediumint(9) NOT NULL,
  `reg` mediumint(9) NOT NULL,
  `time` bigint(20) NOT NULL,
  PRIMARY KEY  (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;
Citation :Il faudrait aussi savoir comment fonctionne ta table listemonstre.

Cette table contient les profile de base des monstres, cela me permet d'automatiser leur création et de faire des comparaisons avec leur PV de base.

Voici sa structure:

Code :
CREATE TABLE `listemonstre` (
  `id` int(11) NOT NULL auto_increment,
  `nom` text NOT NULL,
  `niveau` int(11) NOT NULL,
  `forceu` int(11) NOT NULL,
  `endurance` int(11) NOT NULL,
  `dexterite` int(11) NOT NULL,
  `pouvoir` int(11) NOT NULL,
  `PV` int(11) NOT NULL,
  `armure` int(11) NOT NULL,
  `image` text NOT NULL,
  `reg` mediumint(9) NOT NULL,
  `quete` smallint(6) NOT NULL,
  `degat_min` int(11) NOT NULL,
  `degat_max` int(11) NOT NULL,
  PRIMARY KEY  (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=35 ;

Citation :Par exemple, dans la colonne bouclier, je suppose que tu indiques le nom du bouclier dont les statistiques sont stockées dans une table objets. Si ce n'est pas le cas, je te conseille de le faire et de faire références aux objets par leur identifiant. Car tu imagines si tu dois écrire "Égide du Dieu sanglant" dans la case bouclier de chaque monstre, ça fait une table très lourde !?

Certes, mais lorsque je veux afficher le nom du bouclier, je dois faire une requête SQL en plus pour récupérer le nom de l'objet :heuuu:.

Mon "système d'inventaire ce décline en 3 tables: Objet, qui contient les caractéristique de chaque objet. Inventaire, qui contient le pseudo des joueur et le nom de l'objet dans l'inventaire et enfin la table users qui contient dans les champs arme,armure,etc. le nom des objet équipé. Je pourrais transformé tout sa en identifiant numérique (les ID de la table objet) mais alors, cela voudrais dire que pour afficher les noms des objets je doit faire un requête sur celle-ci. En stockant le nom, je peux l'afficher directement. :heuuu: A moins que je me trompe ?

Merci de votre aide,

Phenix


RE: Requête, boucle et jointure - Anthor - 07-01-2008

Citation :Certes, mais lorsque je veux afficher le nom du bouclier, je dois faire une requête SQL en plus pour récupérer le nom de l'objet 6.

Non, c'est la que tu utilise les jointures, par exemple :
Code :
SELECT user.id, bouclier.nom AS bouclier_nom
    FROM users AS user
    INNER JOIN boucliers AS bouclier
        ON bouclier.id = user.bouclier
    WHERE users.id = identifiant_utilisateur

A adapter en fonction des tables que tu as evidemment ^^


RE: Requête, boucle et jointure - Sephi-Chan - 07-01-2008

Voilà ce que je te propose comme structure pour la table état (et la manière de découper les tables s'applique dans de nombreux cas) :

Table Etats, qui contient les bonus, malus sur les différentes caractéristiques (celles données sont là à titre d'exemple Wink) :
  • Id : TINYINT (UNSIGNED)
  • Nom : VARCHAR(30)
  • Force : SMALLINT
  • Endurance : SMALLINT
Si les joueurs ont les mêmes caractéristiques que les monstres, cette table peut servir à décrire des altérations d'état pour les deux.

Ensuite, ta table Monstres. Alors là, si tu veux que chaque monstre soit identique (d'un point de vue caractéristiques et compétences), tu peux créer des gabarits de monstres et les instancier dans une autre table. Mais à la limite on pourra voir ça plus tard, pour ne pas tout compliquer d'un coup. Smile
  • Id : SMALLINT (UNSIGNED)
  • Nom : VARCHAR(30)
  • Pv : SMALLINT (UNSIGNED)
  • Pv_max : SMALLINT (UNSIGNED)
  • Force : SMALLINT (UNSIGNED)
  • Endurance : SMALLINT (UNSIGNED)
Cette fois-ci on interdit les valeurs non signées

Ensuite, on aura une table Monstre_a_Etat lie un monstre à une altération d'état :
  • Id_monstre : SMALLINT (UNSIGNED) (selon le choix de champ que tu as choisi pour l'Id du monstre)
  • Id_etat : SMALLINT (UNSIGNED) (selon le choix de champ que tu as choisi pour l'Id de l'altération d'état)

Si tu veux utiliser les mêmes altérations d'état pour les joueurs, il faut que tu crées une table de relation Joueur_a_Etat sur le même modèle.

Petite note à propos des types de champs numériques :
  • La valeur d'un TINYINT vaut entre -127 et 127. Et vaut entre 0 et 255 si on lui donne l'attribut UNSIGNED.
  • La valeur d'un SMALLINT vaut entre -32767 et 32767. Et vaut entre 0 et 65535 si on lui donne l'attribut UNSIGNED.

Ensuite, grâce aux jointures, tout devient plus simple (et c'est pas marketting !).

(J'édite au fur et à mesure, sauf si l'explication de Anthor t'as suffit. Smile)


Sephi-Chan


RE: Requête, boucle et jointure - phenix - 07-01-2008

Citation :Si les joueurs ont les mêmes caractéristiques que les monstres, cette table peut servir à décrire des altérations d'état pour les deux.

oui, les joueur et les monstres on les même caractéristique, en fait un monstre c'est un joueur (sauf pour le calcule des PV).

Citation :Ensuite, ta table Monstres. Alors là, si tu veux que chaque monstre soit identique (d'un point de vue caractéristiques et compétences), tu peux créer des gabarits de monstres et les instancier dans une autre table. Mais à la limite on pourra voir ça plus tard, pour ne pas tout compliquer d'un coup. 2

Je n'avait jamais penser a stocker tout simplement le nombre de PV_max :respect:. C'est vrai que rien qu'avec sa, on économise une requête :good:

Les "gabarit" existe déjà, c'est la table liste monstre, par contre je ne sais pas ce que veux dire instancier :heuuu:. Les monstres sont crée pour le moment en 2 requêtes: Une qui choisi au hasard un monstre sur la table listemonstre (avec ORDER BY rand()) et une autre qui fait un INSERT dans la table monstre.

Par contre un truc que je comprend pas. Le nombre d'état que peu subir un joueur/monstre est illimité (enfin en théorie, en pratique c'est différent) dès lors tu utilise: Id : TINYINT (UNSIGNED) et puis tu me dis:

Citation :La valeur d'un TINYINT vaut entre -127 et 127. Et vaut entre 0 et 255 si on lui donne l'attribut UNSIGNED.

Cela ne va t'il pas limité le nombre d'état possible dans la table ? (mes champ id sont en auto_increment clé primaire)
Citation :Ensuite, grâce aux jointures, tout devient plus simple (et c'est pas marketting !).

(J'édite au fur et à mesure, sauf si l'explication de Anthor t'as suffit. 2)

C'est la que j'ai le plus de mal, je me rend bien compte que a quel point les jointures doivent économisé des ressources, seulement j'ai beau lire la doc, et les tuto, quand je veux appliqué le principe sur 2 tables ben sa marche jamais comme prévu :pleure2:.

Code :
SELECT user.id, bouclier.nom AS bouclier_nom
    FROM users AS user
    INNER JOIN boucliers AS bouclier
        ON bouclier.id = user.bouclier
    WHERE users.id = identifiant_utilisateur

Bon je vais détaillé ce que je sais et ce que je sais pas.

Code :
SELECT user.id,

La on séléctionne l'ID de la table user

Code :
, bouclier.nom AS bouclier_nom

Si je me souviens bien AS sert a "renommer" le champs. Quel est l'intérêt d'un telle pratique ? :heuuu:

Code :
FROM users AS user

Même question, pourquoi renommé la table ?

Code :
INNER JOIN boucliers AS bouclier
        ON bouclier.id = user.bouclier

j'ai pas de table boucliers mais une table objet m'enfin, je suppose qu'il faudrais juste renommer ?

Phenix


RE: Requête, boucle et jointure - Anthor - 07-01-2008

phenix a écrit :
Code :
, bouclier.nom AS bouclier_nom

Si je me souviens bien AS sert a "renommer" le champs. Quel est l'intérêt d'un telle pratique ? :heuuu:

Ca t'evite de te tromper avec un autre champs qui pourrait s'appeler nom, de cette façon, tu peux séparer user.nom et bouclier.nom

phenix a écrit :
Code :
FROM users AS user

Même question, pourquoi renommé la table ?
Ca me permet de séparer la table users qui contient tous les utilisateurs et user qui est la ligne de l'utilisateur que je sélectionne

phenix a écrit :
Code :
INNER JOIN boucliers AS bouclier
        ON bouclier.id = user.bouclier

j'ai pas de table boucliers mais une table objet m'enfin, je suppose qu'il faudrais juste renommer ?

Exactement, tu peux l'appliquer sur n'importes quelles tables en relations. Le nombre de jointure n'est pas limité.


RE: Requête, boucle et jointure - phenix - 07-01-2008

Ben alors, il faut que l'on m'explique pourquoi ce code:
Code PHP :
<?php 
$sql
= mysql_query('SELECT item.entrave AS poid FROM objet AS item INNER JOIN inventaire ON item.objet = inventaire.objet WHERE inventaire.joueur = \''.$pseudodb.'\'') or die(mysql_error());

while (
$a = mysql_fetch_assoc($sql))

{
$poid = $poid + $a['poid'];

}

Ne renvoie pas la même chose que:

Code PHP :
<?php 
$sql
= mysql_query('SELECT objet FROM inventaire WHERE joueur=\''.$pseudodb.'\'') or die(mysql_error());

while (
$a = mysql_fetch_assoc($sql))

{

$sql2 = mysql_query('SELECT entrave FROM objet WHERE objet=\''.$a['objet'].'\'') or die(mysql_error());

$s = mysql_fetch_assoc($sql2);

$poid = $poid + $s['entrave'];

}


Amicalement,

Phenix


RE: Requête, boucle et jointure - Sephi-Chan - 07-01-2008

Je vais faire un exemple de jointure pour les altérations, le principe peut être appliqué à plein de choses après. Le tout, c'est d'avoir une structure en 3 tables (ou plus, parfois).

Je te conseille par contre, dans tes noms de champs, de rappeler la table. Par exemple dans la table Monstres, tu appelle l'id id_monstre, ça te permet d'éviter de devoir aliaser chaque champ !

Remplissons les tables :
Etat (id_etat, nom_etat) (on les effets ne nous intéressent pas pour le moment), qui contient la liste des différents altérations d'état possibles. Je doute qu'il y en ai plus que 255. Smile
Citation :1, Stupidité
2, Fureur
3, Paralysie
4, Maladie

Ensuite, remplissons, la table listeMonstre (id_monstre, nom_monstre, pv_max), qui instancie les modèles de monstres :
Citation :1, Chevalier de sang, 60000
2, Anaconda, 3200
3, Chocobo noir, 18000

Imaginons pendant le jeu qu'un personnage jette un sortilège qui paralyse sa cible sur un Chocobo noir. Alors tu créeras dans la table Monstre_a_Etat (id_monstre, id_etat, duree_effet) une relation :
3 (l'id du monstre (id_monstre de la table Monstres) affecté), 3 (l'id de l'altération d'état (id_etat de la table Etats) qui affecte le monstre), 5 (le nombre de tours de combat durant laquelle l'altération fait effet. Tu le réduit de 1 à chaque tour, quand il atteint 0).

Ainsi, il devient possible d'affecter plusieurs altérations d'état à un même monstre !

Imaginons donc qu'un joueur rende malade ce même Chocobo noir, le pauvre ! Alors là, rebelotte, on insère une nouvelle entrée dans la table Monstre_a_Etat :
3 (l'id du monstre Chocobo noir), 4 (l'id de l'altération Maladie), 2 (la durée de l'effet)

En pratique, quand tu vas résoudre ton combat, pour récupérer les informations sur les caractéristiques et les altérations d'état du monstre, tu feras un script comme ça :

Code :
SELECT M.*, E.*, ME.duree_effet
FROM Monstres M
LEFT JOIN Monstre_a_Etat ME ON ME.id_monstre = M.id_monstre
LEFT JOIN Etats E ON E.id_etat = ME.id_etat
WHERE M.id_monstre = 3;

J'explique ma requête, on sélectionne toutes les informations de la table Monstres, on va chercher dans la table de relation Monstre_a_Etat si l'id du monstre s'y trouve (le LEFT devant le JOIN sert à sélectionner quand même les informations du monstres s'il n'a aucune altération, mais c'est un détail), si oui, alors on va chercher dans la table Etats ceux qui ont l'id de (ou des) altérations trouvées précédemment dans la table Monstre_a_Etat.

Comme tu le vois, on utilise à chaque fois l'id du monstre actuellement parcouru par SQL quand il parcourt les tables à l'exécution de la requête. On spécifie en l'occurrence la bête d'id 3 à la fin, mais si on ne met pas de WHERE, ça sort toutes les bêtes et leurs altérations d'état !

Ça nous sort une ressource genre (je n'écris pas tous les champs dans mon exemple car ce serait trop long pour moi de les lister) :
Citation :id_monstre, nom_monstre, …, id_maladie, nom_maladie, …, duree_effet
3, Chocobo noir, …, 3, Paralysie, …, 5
3, Chocobo noir, …, 4, Maladie, …, 2

Ce résultat est un peu particulier à exploiter vu qu'il y a des doublons, mais c'est en fait très facile à faire avec PHP (on pourra t'expliquer au besoin). Smile

Voilà, j'espère que ça t'aura aidé à comprendre le fonctionnement !


Sephi-Chan


RE: Requête, boucle et jointure - phenix - 08-01-2008

Merci Sephi, je commence a comprendre comment cela fonctionne Cool.

Déjà j'ai réussi a faire fonctionné ma première jointure, c'étais des erreurs dans la base de donnée :mauvais:

Sinon, au lieu d'avoir 2 tables pour les etats, ce ne serais pas plus facile de n'en avoir qu'une seul et de donner de très grand ID au monstre (ici j'ai mit 20000) ?