Partage
  • Partager sur Facebook
  • Partager sur Twitter

SQLSTATE[HY000] dans certains cas

    11 novembre 2019 à 0:03:33

    Je suis en train d'essayer d'améliorer un code qui marchait très bien avant. Mais maintenant j'ai une erreur et je ne comprends pas pourquoi elle se déclenche dans un cas et pas dans l'autre.

    J'ai cette erreur :
    Fatal error: Uncaught PDOException: SQLSTATE[HY000]: General error in C:\wamp64\www\Site\Tour\Test.php on line 35

    PDOException: SQLSTATE[HY000]: General error in C:\wamp64\www\Site\Tour\Test.php on line 35

    J'ai un peu cherché, et il semble que cela est dû au fait que je fasse un fetch/while et que j'ai un update dans la boucle sur la même table.

    Voici le code qui plante lorsque je passe dans le dernier else à la fin :

    $joueur = $bdd->query('SELECT
    						v.idjoueur idj ,
    						v.chantier chantier ,
    						u.biens	biens ,
    						u.titane ti
    						FROM variationstour v
    						INNER JOIN utilisateurs u
    						ON u.id = v.idjoueur
    						ORDER BY idj');
    	while ($repjoueur = $joueur->fetch())
    	{
    	$chantier =  $repjoueur['chantier'] ;
    	$biens = $repjoueur['biens'] ;
    	$titane = $repjoueur['ti'] ;
    
    	$construction = $bdd->query(
    	"SELECT nombre, avancementbiens, avancementtitane, idjoueurconst, idconst, trucaconstruire
    	FROM construction WHERE idjoueurconst = {$repjoueur['idj']} ORDER BY idconst"); 
    	while ($repconstruction = $construction->fetch())
    		{
    		$nb = $repconstruction['nombre'];
    		$avancementbiens = $repconstruction['avancementbiens'] ; 
    		$avancementtitane = $repconstruction['avancementtitane'] ;
    	
    		a: // Revenir ici si prod se finie et qu'il y a un round 2.
    		$message = $bdd->prepare("INSERT INTO messagetour (idjoumess , message , domainemess , numspemessage) VALUES (? , ?, ? , ?)") ;
    
    		if ($avancementbiens > 1) // S'il reste des biens à investir, faire cette partie.
    			{
    		$minbiens = min($chantier, $avancementbiens, $biens) ;
    		$nouvavbien = $avancementbiens - $minbiens ; 
    		echo $chantier .' chantier.</br>'; 
    		echo $avancementbiens .' avancement.</br>'; 
    		echo $biens .' biens.</br>'; 
    		echo $minbiens .' minimum entre biens, chantier et avancement.</br>';
    		echo $nouvavbien . ' nouvel avancement des biens.</br>' ; 		
    		$bdd->query("UPDATE construction SET avancementbiens = $nouvavbien WHERE idconst = {$repconstruction['idconst']}");
    		$bdd->query("UPDATE utilisateurs SET biens = biens - $minbiens WHERE id = {$repjoueur['idj']}");
    		$chantier = $chantier - $minbiens ;
    		$biens = $biens - $minbiens ;
    
    			if ($biens == 0)
    				{$message ->execute(array($repjoueur['idj']  , 'Manque de biens !' , 'Construction' , $repconstruction['idconst'])) ;}
    			}
    		else {$nouvavbien = 0;}
    
    		if ($avancementtitane > 1)  // S'il reste du titane à investir, faire cette partie.
    			{
    		$mintitane = min($chantier/5, $avancementtitane, $titane) ;
    		$nouvavtitane = $avancementtitane - $mintitane ; 
    		echo $chantier .' chantier.</br>'; 
    		echo $avancementtitane .' avancement.</br>'; 
    		echo $titane .' titane.</br>'; 
    		echo $mintitane .' minimum entre titane, chantier et avancement.</br>'; 			
    		$bdd->query("UPDATE construction SET avancementtitane = $nouvavtitane WHERE idconst = {$repconstruction['idconst']}");
    		$bdd->query("UPDATE utilisateurs SET titane = titane - $mintitane WHERE id = {$repjoueur['idj']}");
    		$chantier = $chantier - $mintitane * 5 ;
    		$titane = $titane - $mintitane ;
    			}
    		else {$nouvavtitane = 0;}
    
    		if ($chantier == 0 OR ($chantier < 5 AND $titane > 0)) 
    			{$message ->execute(array($repjoueur['idj']  , 'Manque d\'ouvriers !' , 'Construction' , $repconstruction['idconst'])) ;}
    
    		echo $biens . ' biens restants </br>' ;
    		echo $titane . ' titane restants </br>' ;
    		// Si je peux finir le chantier : 
    		if ($nouvavbien == 0 AND $nouvavtitane == 0)
    			{
    			echo 'début boucle fin de construction' ; 
    			$reqcategorie = $bdd->query("SELECT typeitem , nombatiment FROM items WHERE {$repconstruction['trucaconstruire']}");
    			$repcategorie = $reqcategorie ->fetch(); 
    			$construction = $bdd->prepare('INSERT INTO '.$repcategorie['typeitem'].' (typebat, idjoueurbat) VALUES (:typebat , :idjoueurbat )');
    			$construction->execute(array(
    				'typebat' => $repconstruction['trucaconstruire'],
    				'idjoueurbat' => $repjoueur['idj'] ));
    
            	$mess = $repcategorie['nombatiment'].' : Construction finie' ; 
            	$message = $bdd->prepare("INSERT INTO messagetour (idjoumess , message , domainemess)
            	VALUES (? , ?, ?) ") ;
    			$message ->execute(array($repjoueur['idj'] , $mess , 'Construction'));
    			
    				// Si je n'ai qu'un bâtiment à faire avant :
    				if ($nb < 2)
    					{
    					$sql_delete = $bdd->prepare('DELETE FROM construction WHERE idconst =  ? ');
    					$sql_delete->execute(array($repconstruction['idconst']));
    					echo 'Ordre delete (fin de prod)</br>'; 
    					}
    				else
    					{
    					$diminutiondeun = $bdd->prepare('UPDATE construction SET nombre = nombre - 1 , avancementbiens = ? , avancementtitane = ?  WHERE idconst = ? ' );
    					$avancementbiens = 100 ;
    					$avancementtitane = 0 ;
    					$diminutiondeun->execute(array($avancementbiens, $avancementtitane, $repconstruction['idconst']));
    					$nb = $nb -1 ;
    					echo 'Réduction de 1 </br>';
    					goto a;
    					}
    			}
    		}
    	}

     Et voici l'ancien code qui ne plante pas même lorsque je passe dans le dernier else :

    $reqjoueur = 'SELECT idjoueur , chantier FROM variationstour ORDER BY idjoueur' ; 
    $reponse = $bdd->query($reqjoueur);
    	while ($infojoueur = $reponse->fetch())
    	{
    		// echo $infojoueur[0] . ' id du joueur. </br>' ;
    		// echo $infojoueur[1] . ' = sa quantité de prod.</br>' ;
    		$chantier =  $infojoueur['chantier'] ;
    
    		$sql = 'SELECT 	c.nombre nb,
    						c.avancementbiens av1 ,
    						c.trucaconstruire item ,
    						i.coutbien cout ,
    						c.idjoueurconst idj,
    						c.idconst idconst,
    						i.nombatiment nombatiment,
    						i.typeitem typeitem
    		FROM construction c
    		INNER JOIN items i
    		ON i.iditem = c.trucaconstruire
    		ORDER BY c.idconst ' ;
    
    		$result = $bdd->query($sql);
    		while ($lesprods = $result->fetch())
    		{
    			// Je vais éviter une jointure à trois tables, donc je mets un if qui le fait.
    			if ($infojoueur['idjoueur'] == $lesprods['idj'])
    			{
    			$nb = $lesprods['nb'];
    			$av = $lesprods['av1'] ; 
    			// Revenir ici si prod se finie.
    			a:
    
    			// Si je n'ai pas assez de prod :
    			if ($chantier + $av < $lesprods['cout'])
    				{
    				$avancement = "UPDATE construction SET avancement1 = avancement1 + $chantier  WHERE idconst = $lesprods[5]";
    				$bdd->query($avancement);
    				$chantier = 0 ;
    
    				$message = $bdd->prepare("INSERT INTO messagetour (idjoumess , message , domainemess , numspemessage)
                	VALUES (? , ?, ? , ?)") ;
    				$message ->execute(array($infojoueur['idjoueur'] , 'Manque d\'ouvriers !' , 'Construction' , $lesprods['idconst'])) ;
    				}
    
    			// Si je peux finir le chantier : 
    			else
    				{
    				$construction = $bdd->prepare('INSERT INTO '.$lesprods['typeitem'].' (typebat, idjoueurbat) VALUES (:typebat , :idjoueurbat )');
    				$construction->execute(array(
    					'typebat' => $lesprods['item'],
    					'idjoueurbat' => $infojoueur['idjoueur'] ));
    				$chantier = $chantier - $lesprods['cout'] + $av ;
    
                	$mess = $lesprods['nombatiment'].' : Construction finie' ; 
                	$message = $bdd->prepare("INSERT INTO messagetour (idjoumess , message , domainemess)
                	VALUES (? , ?, ?) ") ;
    				$message ->execute(array($infojoueur['idjoueur'] , $mess , 'Construction'));
    				
    					// Si je n'ai qu'un bâtiment à faire avant :
    					if ($nb < 2)
    						{
    						$sql_delete = $bdd->prepare('DELETE FROM construction WHERE idconst =  ? ');
    						$sql_delete->execute(array($lesprods['idconst']));
    						echo 'Ordre delete (fin de prod)</br>'; 
    						}
    					else
    						{
    						$diminutiondeun = $bdd->prepare('UPDATE construction SET nombre = nombre - 1 , avancementbiens = 0   WHERE idconst = ? ' );
    						$diminutiondeun->execute(array($lesprods['idconst']));
    						$nb = $nb -1 ;
    						$av = 0 ; 
    						echo 'Réduction de 1 </br>';
    						goto a;
    						}
    				}
    			}
    		}
    	}
    		
    $reponse->closeCursor();
    $result->closeCursor();

    J'aimerais bien comprendre la différence entre ces deux cas.

    Cas 1 : ligne 19 avec la requete sur la table construction et un while. Ligne 92 dans laquelle on fait un update : Bug !
    Cas 2 :
    ligne 23 avec la requete sur la table construction et un while. Ligne 68 dans laquelle on fait un update : pas bug !

    -
    Edité par LaurentDubrulle 11 novembre 2019 à 0:08:13

    • Partager sur Facebook
    • Partager sur Twitter
      11 novembre 2019 à 11:54:18

      C'est probablement parce que $construction est au départ un SELECT puis écrasé par un INSERT (ligne 48) donc quand on revient sur while/fetch, boom, parce qu'un INSERT ne renvoie jamais rien chez MySQL => bien utiliser deux variables distinctes

      Toutes les requêtes devraient être préparées et ne l'être qu'une fois avant toute boucle, ce qui t'aurait permis d'éviter cette erreur d'ailleurs.

      On ne fait jamais un UPDATE pour in/décrémenter la valeur d'une colonne en reprenant les données d'un précédent SELECT, c'est le meilleur moyen de se retrouver avec des valeurs fausses s'il y a concurrence ! L'opération n'est pas atomique, s'il y a eu un ou des autres UPDATE ailleurs entre ton SELECT et ton UPDATE, ceux-ci s'en trouvent ignorés au final et c'est totalement faussé.

      -
      Edité par julp 11 novembre 2019 à 12:08:21

      • Partager sur Facebook
      • Partager sur Twitter
        11 novembre 2019 à 17:57:24

        Merci Julp.

        Quand j'ai fais mes modifications sur ce code, je n'ai pas touché la partie avec la ligne 48. J'ai complètement oublié que j'avais déjà une variable de ce nom sur cette page ... J'étais concentré sur les parties modifiées et je ne voulais pas avoir une même variable faisant deux requêtes.

        J'ai changé le nom d'une des deux variables et cela marche. Je me suis arraché les cheveux pour comprendre pourquoi ma nouvelle requête marchait totalement différemment dans les deux cas ... 

        julp a écrit:

        Toutes les requêtes devraient être préparées et ne l'être qu'une fois avant toute boucle, ce qui t'aurait permis d'éviter cette erreur d'ailleurs.

        Je connaissais pas ce conseil. Intéressant. Je vais certainement l'appliquer. Une raison particulière en dehors de l'organisation de la page ?



        -
        Edité par LaurentDubrulle 11 novembre 2019 à 20:31:09

        • Partager sur Facebook
        • Partager sur Twitter
          11 novembre 2019 à 18:24:21

          Tu vas repréparer strictement la même requête à chaque tour de boucle donc c'est inutile [de le faire lors de chaque itération]. Autant ne le faire qu'une fois, avant la boucle.

          En plus, avec une vraie requête préparée, tu serais gagnant en terme de "perfs".

          Ah, et le tout est dans une transaction bien sûr ?

          -
          Edité par julp 11 novembre 2019 à 23:28:54

          • Partager sur Facebook
          • Partager sur Twitter

          SQLSTATE[HY000] dans certains cas

          × Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
          × Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
          • Editeur
          • Markdown