Archives par mot-clef : PgSQL

Mysql et la recherche sur plusieurs champs : avec CONCAT ou CONCAT_WS

Catégorie : SQL | 3 commentaires

Après avoir parlé un peu de la recherche en MySQL via [LIKE|/post/2009/mysql-et-la-recherche-avec-like] puis [REGEXP|/post/2009/mysql-et-la-recherche-avec-regexp], et avant d’aller plus loin, parlons un peu de la recherche dans plusieurs champs à la fois. Eh oui, c’est possible grâce à CONCAT, ou plutôt CONCAT_WS. Prenons une table contenant les billets d’un blog, et donc possédant les champs @@id@@, @@titre@@ et @@contenu@@. Une recherche classique cherchera un mot clef dans le titre et dans le contenu du billet. Pour cela, on pourrait utiliser @@OR@@, mais un @@CONCAT@@ qui concatène (met l’un à la suite de l’autre) le contenu de @@titre@@ et de @@contenu@@, c’est pas mal aussi. /// SELECT * FROM billet WHERE CONCAT(titre, contenu) LIKE « %mot%clef% »; /// On peut ainsi concaténer autant de champs que l’on souhaite. Un petit détails cependant, si l’un de ces champs est NULL, le résultat renvoyé est NULL. Pour éviter cela, il convient d’utiliser @@CONCAT_WS@@ qui fonctionne de la même manière, sans se soucier des champs vides. /// SELECT * FROM billet WHERE CONCAT_WS(date, ‘ ‘, heure, ‘h’, minute) LIKE « 10/11/2009 23h30″; /// Ce deuxième exemple est certes moins compréhensible, mais explique bien l’intérêt d’utiliser @@CONCAT@@ plutôt que @@OR@@ dans certains cas. %%% Bien sûr, ceci fonctionne aussi très bien avec la commande @@REGEXP@@ ! Bref, vive la concaténation ! %%% %%% Puisque ses rétroliens ne semblent pas fonctionner, merci à CrazyCat de [G33k-zone|http://www.g33k-zone.org/post/2009/09/18/Concat%C3%A9nation-de-champs-avec-MySQL] pour m’avoir fait découvrir cette commande SQL bien utile.

SQL et RENAME

Catégorie : SQL | Laisser un commentaire

ça fait plusieurs fois que je cherche à renommer une table en ligne de commandes, et plusieurs fois que je re-re-regarde la doc MySQL sur @@RENAME@@. Dans l’espoir que ça rentre, et par ne pas avoir à re-re-re-rechercher la prochaine fois, voici comment fonctionne cette commande. L’idée est de renommer la table « chaise » pour l’appeler « chaise_pliante » (hum, original !). Pour cela, rien de plus simple : /// RENAME TABLE chaise TO chaise_pliante; /// Pour renommer plusieurs tables à la fois, il suffit de séparer les blocs @@ancien_nom TO nouveau_nom@@ par une virgule. /// RENAME TABLE chaise TO chaise_pliante, tableau TO tableau_pliante; /// Mais cette commande est aussi utile pour déplacer une table d’une base de données dans une autre. Pour cela, il faut faire précéder le nom de la table par le nom de la base de données. Donc en résumé, on a : /// RENAME TABLE [ancien_nom_db.]ancien_nom_table TO [nouveau_nom_db.]nouveau_nom_table; /// A noter que cela fonctionne aussi pour les vues (tant qu’on ne change pas de base de données) et pour les tables temporaires. Et on peut aussi faire cela avec @@ALTER TABLE@@ : /// ALTER TABLE ancien_nom RENAME nouveau_nom; /// Bon, avec un peu de chance, je crois que ça va me rester !

Les requêtes imbriquées c’est (parfois) le pied

Catégorie : SQL | Laisser un commentaire

J’ai une table annonce1 contenant 10 000 tuples, et une autre annonce2 qui en contient aussi 10 000. Tous les tuples sont différents. Le but ? Fusionner ces 2 tables. Export/Import classique créé des problèmes de Primary Key qui se dupliquent. (mon ID est en auto_increment) Moralité : /// INSERT INTO annonce (ann_surface, ann_prix) SELECT ann_surface, ann_prix FROM annonce2 WHERE ann_surface>150 AND ann_ville!=0; /// Une petite requête SQL imbriquée et pof le tour est joué ! 2 choses importantes à remarquer : * La syntaxe : le VALUES n’apparait pas ici. (moi je m’étais fait avoir) * Inutile de préciser de préfixes même si le champ des tables ont le même nom. D’ailleurs j’ai testé avec @@annonce1 a1 (a1.ann_surface, …@@ et cela n’a pas fonctionné. J’en déduis que les préfixes doivent être interdit avec la commande INSERT. La doc MySQL n’est pas avare à propos de la [commande INSERT|http://dev.mysql.com/doc/refman/5.1/en/insert.html], pas plus que les commentaires. Profitons-en !

Optimiser ses requêtes MySQL

Catégorie : SQL | Laisser un commentaire

Je travaille actuellement sur une table MySQL qui va contenir près de 7 millions de tuples (avec une quinzaine de colonnes chacun bien sûr, ce ne serait pas drôle sinon !), il se trouve qu’elle en contient déjà près de 3 millions, et que je suis obligé de faire une vingtaine de requêtes différentes dans cette table pour chaque page et que, par conséquent, le site qui l’utilise plante à fond. La mémoire utilisable par MySQL est dépassée. Alors certes, on peut modifier la taille de la mémoire utilisable par MySQL (et il faudrait aussi sûrement modifier la durée maximum d’exécution d’un script PHP), mais le but c’est quand même de charger des pages en moins de 30 secondes. J’ai donc cherché des réponses sur le Web et auprès des développeurs de ma boîte. Voici donc quelques points que j’ai pu retenir : !!!Structure de la table Il vaut mieux éviter, sauf si c’est nécessaire, l’utilisation de champ  »NULL » par défaut. En effet, comme on peut le voir sur [cet article d'Apprendre PHP|http://www.apprendre-php.com/tutoriels/tutoriel-26-mysql-introduction-a-l-optimisation.html]  »NULL » est une valeur spéciale qui nécessite un traitement spécial, MySQL ne peut pas le tester avec un égal et ne peut le comparer à la chaine vide ou à 0, il est obligé d’utiliser @@IS@@ ou le symbole équivalent @@<=>@@.  »NULL » par défaut est d’autant plus à bannir pour les index. Utiliser des colonnes avec le meilleur type et la meilleure taille possible. Un prénom n’a par exemple par besoin d’être en VARCHAR(255), un VARCHAR(20) suffit plus que largement. Une bonne technique pour optimiser ses tables est d’utiliser @@PROCEDURE ANALYSE()@@. On créé ses tables au feeling (je parle juste de la structure, vous ne couperez pas à la normalisation tout ça, héhé), on les remplie avec un contenu à peu près définitif est on utilise une requête du style : ///[mysql] SELECT id, titre, message FROM blog PROCEDURE ANALYSE() /// MySQL va nous répondre un joli petit array avec la taille max et min de chaque colonne id, titre et message, quelques autres informations intéressantes et surtout une proposition de type(taille) optimisée à votre table. Encore une fois, c’est [l'article d'Apprendre PHP|http://www.apprendre-php.com/tutoriels/tutoriel-26-mysql-introduction-a-l-optimisation.html] qui en montre un exemple vraiment parlant. !!!SELECT * Si l’on effectue une recherche sur une colonne et que celle-ci change assez rarement (ou n’est modifié que par un administrateur), il peut être très avantageux de créer un index ou un index unique (si le contenu de cette colonne est différente pour chaque tuple) dessus. Il suffit de le faire en ligne de commande, ou directement dans PhpMyAdmin. Cela peut mettre un petit peu de temps si la table est grande, ce qui explique qu’il vaut mieux éviter de changer trop souvent la table par la suite puisqu’il faut mettre à jour l’index à chaque insert, update, delete. Si j’ai bien compris MySQL se charge alors de créer une sorte de table virtuelle avec cette colonne ce qui accélère grandement la recherche sur cette dernière. On peut créer autant d’index que l’on souhaite, mais s’il y en a trop l’avantage peut être restreint. (o alors il faut utiliser des USE INDEX (nom_index) dans vos requêtes, mais pour moi ça a plutôt ralenti mes requêtes) * Bien entendu, l’utilisation du joker (*) est à bannir. (en l’utilisant on effectue 2 requêtes au lieu d’une : chercher tous les champs qui correspondent au joker (ce que l’on aurait du faire à la main), puis sélectionner les bonne colonnes selon les bonne lignes) De même, si c’est possible comme pour un forum ou un système de commentaires, au lieu d’utilisation un count(champ1), il vaut mieux mettre le nombre de tuples en dur dans la base de données. * Lors de jointure, il est important que les critères de jointure soient de même type pour que MySQL n’est pas à faire de conversion. L’utilisation de jointures avec JOIN est a priori plus rapide que celles (à l’ancienne) dans le WHERE. Et de même, il est plus intéressant de faire un LEFT JOIN qu’un JOIN (quand le résultat convient avec un LEFT JOIN bien sûr) * On peut faire précéder sa requête SQL par EXPLAIN pour savoir comment MySQL traite notre SELECT. MySQL nous renvoie alors un tableau avec tout un tas d’information. Il suffit de lire la [doc MySQL de EXPLAIN|http://dev.mysql.com/doc/refman/5.0/fr/explain.html] pour utiliser cette commande. !!!INSERT Si l’on a a effectuer plusieurs insertions à la suite, il est plus avantageux de faire un seul INSERT que plusieurs à la suite. Bref, au lieu de faire : /// for($i=0;$i<100;$i++) { $qry ='INSERT INTO ma_table (numero) VALUES ("'.$i.'")'; $insert = mysql_query($qry); if (!$insert) echo 'Erreur lors de l\'insertion : '.mysql_error().'
‘; } /// Faites plutôt : /// $values = array(); for($i=0;$i<100;$i++) { $values[] = $i; } $qry ='INSERT INTO ma_table (numero) VALUES ("'.implode('"), ("', $values).'")'; $insert = mysql_query($qry); if (!$insert) echo 'Erreur lors de l\'insertion : '.mysql_error().'
‘; /// !!!Partitionnement L’autre solution consiste à partitionner sa table. Je vous laisse lire ce [très bon tutoriel sur Developpez.com|http://krierjon.developpez.com/mysql/partitionnement/]. C’est au final ce que j’ai choisi et le résultat est plutôt positif : j’arrive à accéder à toutes mes pages en moins de 30 secondes, et les pages qui n’avaient vraiment pas besoin de toutes les données sont vraiment rapide à charger. !!!Conclusion Vous pouvez lire ce long article très intéressant et très complet de PHP Facile à ce sujet : [Optimisation de MySQL|http://www.lephpfacile.com/manuel-mysql/mysql-optimization.php] Et bien sur l’article dont j’ai déjà parlé sur Apprendre PHP : [Introduction à l'optimisation|http://www.apprendre-php.com/tutoriels/tutoriel-26-mysql-introduction-a-l-optimisation.html]. Aucun de ces 2 articles ne parlent de partitionnement ce qui est assez étonnant. Bon, tout cela étant dit, la meilleure technique consiste assurément à créer un système de cache. Le premier utilisateur allant sur la page se tape les requêtes SQL et la génération du cache, mais toutes les prochaines visites (pour tout le monde) seront très rapide puisqu’elles iront juste lire un fichier HTML. Bien sûr, vous pouvez aussi générer automatiquement tout votre cache en visitant chaque page avec @@file_get_content()@@. D’autre part, si vos données changent assez souvent, vous pouvez créer un petit robot qui se charge de supprimer vos fichiers de cache à intervalle régulier, mais dans ce cas là, il vaut mieux regénérer le cache à chaque modification de la BDD.

Une seule et même fonction pour différents systèmes de gestion de base de données

Catégorie : PHP objet | Un commentaire

Je suis depuis longtemps déjà un féru utilisateur de [FluxBB|http://www.punbb.fr/] (anciennement PunBB), un système de forum bien fichu, valide W3C, très léger et donc configurable à souhait. En ce moment ce projet open source bat un peu de l’aile, mais de nouvelles mises à jours sortent encore assez régulièrement et la nouvelle version (attendue depuis des années) semble de nouveau repartir sur de bonnes bases. Mais bref, je parle de cela car FluxBB gère les bases de données MySQL, PostgreSQL (ou PgSQL) et même SQLite je crois. Pour se faire, au lieu d’avoir un switch ou des if else à chaque requête à effectuer, ils ont créé une class qui regroupe toutes les fonctions utiles (connect, query, fetch_array, …) et dans le code ils y font appelle après avoir instanciée cette class : $db->query, $db->fetch_array. Reste à sélectionner le type de sa base de données dans la partie administration et hop, on change de SGBD sans rien changer au code ! Pratique ! Je n’ai pas encore fini d’apprendre le PHP objet, mais j’avais envi de pratiquer un peu, alors sans regarder ailleurs, j’ai commencé à coder. Et voici une première version. Ma class oublie surement pas mal de fonctions, et je n’ai pas pu la tester partout, et puis il manque la gestion des erreurs, j’aurai pu ajouter un compteur de requêtes, mais c’est ma première véritable class, alors soyez indulgent ^^ /// nom = $nom; } public function connect($hote, $port, $nomBdd, $utilisateur, $mdp) { switch ($this->nom) { case ‘pg’: $requete = ‘host=’.$hote.’ port=’.($port == NULL ? ’5432′ : $port).’ dbname=’.$nomBdd.’ user=’.$utilisateur.’ password=’.$mdp; pg_connect($requete); break; default: mysql_connect($hote.($port == NULL ? ‘:3307′ : ‘:’.$port), $utilisateur, $mdp); mysql_select_db($nomBdd); } } public function close() { switch ($this->nom) { case ‘pg’: pg_close(); break; default: mysql_close(); } } public function query($requete) { switch ($this->nom) { case ‘pg’: $result = pg_query($requete); break; default: $result = mysql_query($requete); } return $result; } public function fetch_array($result) { switch ($this->nom) { case ‘pg’: $arr = pg_fetch_array($result); break; default: $arr = mysql_fetch_array($result, MYSQL_BOTH); } return $arr; } public function fetch_row($result) { switch ($this->nom) { case ‘pg’: $row = pg_fetch_row($result); break; default: $row = mysql_fetch_row($result); } return $row; } public function num_rows($result) { switch ($this->nom) { case ‘pg’: $num_rows = pg_num_rows($result); break; default: $num_rows = mysql_num_rows($result); } return $num_rows; } } $sgbd = new Sgbd(‘mysql’); ?> ///