PDO Fetch Modes - Treating PHP Delusions

C’est vraiment génial ça :-D

(oui je découvre des choses :p)

Normalement, en PDO, un fetchAll() retourne un objet, ou, dans mon cas, un tableau. Chaque élément du tableau est lui-même un tableau contenant les colonnes de la BDD. :

$bdd->query("SELECT id, nom, prenom FROM table")->fetchAll(PDO::FETCH_ASSOC);
array(
  [0] => Array
        (
            [id] => 184e34da69fe1a6b2337be58388dd3b9
            [nom] => dupond
            [prenom] => jean
        )

  [1] => Array
        (
            [id] => 53957652a3c1f9b6790717888fea0a31
            [nom] => ofgates
            [prenom] => john
        )
)

Notez que j’ai utilisé PDO::FETCH_ASSOC qui permet d’avoir un tableau associatif "colonne => valeur". Si on ne le met pas, on aura un index de la colonne, en plus d’un assoc :

  [1] => Array
        (
            [0] => 53957652a3c1f9b6790717888fea0a31
            [id] => 53957652a3c1f9b6790717888fea0a31
            [1] => ofgates
            [nom] => ofgates
            [2] => john
            [prenom] => john
        )

Ça peut servir si l’on fait des ittérations numériques sur les index, mais dans mon cas, j’utilise toujours les noms des colonnes.

Maintenant, problème : dans le tableau de tous les résultats obtenu avec fetchAll(), on a un index : 0, 1, mis en évidence là :

  [0] => Array
        (
            …
        )

  [1] => Array
        (
           … 
        )

Moi j’ai besoin que ces 0 et 1 soient remplacés par l’ID de l’élément du tableau.
Je sais que dans ma BDD, l’ID, qui n’est autre qu’un MD5() ou un GUID, est unique.

Je peux utiliser l’option PDO::FETCH_UNIQUE :

$bdd->query("SELECT id, nom, prenom FROM table")->fetchAll(PDO::FETCH_UNIQUE);

Cela va utiliser le contenu de la première colonne ("id") comme index du tableau retourné par fetchAll() :

array(
  [184e34da69fe1a6b2337be58388dd3b9] => Array
        (
            [0] => 184e34da69fe1a6b2337be58388dd3b9
            [id] => 184e34da69fe1a6b2337be58388dd3b9
            [1] => dupond
            [nom] => dupond
            [2] => jean
            [prenom] => jean
        )

  [53957652a3c1f9b6790717888fea0a31] => Array
        (
            [0] => 53957652a3c1f9b6790717888fea0a31
            [id] => 53957652a3c1f9b6790717888fea0a31
            [1] => ofgates
            [nom] => ofgates
            [2] => john
            [prenom] => john
        )
)

Ça marche, mais ça remis les données redondantes.
Heureusement, on peut mettre les deux options :

$bdd->query("SELECT id, nom, prenom FROM table")->fetchAll(PDO::FETCH_UNIQUE|PDO::FETCH_ASSOC);

Et là, j’ai ce que je veux :

array(
  [184e34da69fe1a6b2337be58388dd3b9] => Array
        (
            [id] => 184e34da69fe1a6b2337be58388dd3b9
            [nom] => dupond
            [prenom] => jean
        )

  [53957652a3c1f9b6790717888fea0a31] => Array
        (
            [id] => 53957652a3c1f9b6790717888fea0a31
            [nom] => ofgates
            [prenom] => john
        )
)

Attention :
— il existe un grand nombre d’options PDO. Ici, j’en ai deux qui sont compatibles entre-elles. Toutes ne le sont pas.
— FETCH_UNIQUE va faire en sorte que les éléments de l’index du tableau seront unique (c’est obligé, pour un tableau). Ici ça ne poste pas de problème car l’ID est un hash unique (je déclare cette colonne comme UNIQUE quand je construit la BDD, par exemple). Si votre "id" n’est pas unique, les derniers écraseront les premiers.
— FETCH_UNIQUE utilise la première colonne demandé dans la requête. Ici, c’est mon "id". Si j’avais voulu utiliser "nom", j’aurais dû utiliser cette requête :

$bdd->query("SELECT nom, id, prenom FROM table")->fetchAll(PDO::FETCH_UNIQUE|PDO::FETCH_ASSOC);

Ici, la première colonne est "nom", et "id" vient en second, suivi de "prenom".

Bien-sûr, si l’utilise le « * », on peut faire ça :

$bdd->query("SELECT nom, * FROM table")->fetchAll(PDO::FETCH_UNIQUE|PDO::FETCH_ASSOC);

Pour forcer d’avoir "nom" en premier, même si ce n’est pas la colonne en première dans la liste des colonnes de la BDD.

J’arrête là pour les exemples, ça me suffit.
Je vous laisse voir les différents modes. Le "FETCH_KEY_PAIR" peut-être utile parfois aussi.

#20395  

https://phpdelusions.net/pdo/fetch_modes

Note perso pour le SQL, inner joint, left join

Bon, un truc probablement trivial en SQL, mais je dois la noter.

Pour mon lecteur RSS.
J’ai une table avec les flux, et une table avec les posts.
Dans celle avec les posts, j’ai l’id du flux. Logique.

Quand je liste les flux, je veux un compte des nombres de posts non lus qui lui sont associés :

flux1 : 10
flux2 : 5
flux3 : 0
flux4 : 0

Donc ça :

SELECT flux.id, COUNT(posts.id) AS unread FROM flux LEFT JOIN posts ON (posts.flux_id = flux.id) WHERE posts.unread=TRUE GROUP BY flux.id 

Sauf que ça, ça me retourne :

flux1 : 10
flux2 : 5

Comment faire pour avoir ceux où le nombre de posts est 0 ?
Il suffit de mettre le WHERE dans le la parenthèse :

SELECT flux.id, COUNT(posts.id) AS unread FROM flux LEFT JOIN posts ON (posts.flux_id = flux.id AND posts.unread=TRUE) GROUP BY flux.id 

Et là :

flux1 : 10
flux2 : 5
flux3 : 0
flux4 : 0

*faceplam*
J’ai mis beaucoup trop de temps à chercher ça

Du coup la requête complète, avec un ordre selon le titre du site dont j’ai le flux, et en incluant aussi (au passage), la date du dernier poste pour un flux donné :

SELECT flux.id, COUNT(posts.id) AS unread, MAX(posts.date) AS latestpost FROM flux LEFT JOIN posts ON (posts.flux_id = flux.id AND post.unread=TRUE) GROUP BY flux.id ORDER BY flux.titre

C’est là que je suis heureux de ne plus avoir de fichiers plain text serializé ;)

(Ces fichiers sérialisés sont très bon quand on a une liste de posts simple, mais ça devient vite la merde quand on a plusieurs tables dont les données sont mises en relation (les posts appartiennent à un flux, par exemple ; ou les commentaires à un article, les fichiers mp3 à un album, à un artiste…)

#20390  

https://lehollandaisvolant.net/?mode=links&id=20210919185725

Comment gérer une base de données comme un fichier Excel ? – Korben

Ça peut être utile.

Au boulot, j’ai à répertorier des trucs. Beaucoup de trucs. Donc comme tout bon bullshitto-corporate en entreprise, j’utilise Excel. Puis ça m’a saoulé, j’ai utilisé LibreOffice Base : un truc destiné à ça, au fond, bien plus qu’un tableau.

Et puis ça m’a saoûlé : Base n’est pas pratique, passe son temps à se mettre en travers de mon chemin, etc. (j’ai pas essayé MS Access, mais vu tout le bien qu’on en dit, j’ai préféré éviter).

Du coup j’ai sorti SQLiteBrowser et j’ai tout mis dans un fichier SQLite. C’est nettement plus rapide.

Pour insérer des données, soit je fais des requêtes manuelles (si j’en ai beaucoup, j’en écris des dizaines à la fois avec SublimeText), soit je les copie-colle dans Excel, j’exporte en CSV et j’importe dans SQLiteBrowser.

C’est de la bidouille, mais c’est rapide et faisable. Et j’épate tout le monde quand je retrouve un enregistrement très précis en 4 secondes, là où les autres, encore au format papier doivent trouver le bâtiment, le carton, le dossier, puis la fiche où est l’information. Et je peux compter, filtrer, comparer, faire des stats sur les enregistrement.

SQL = le bien.

#20268  

https://korben.info/nocodb-clone-airtable.html

sql - How to update an SQLite database with a search and replace query? - Stack Overflow

Faire un rechercher-rempacer directement avec une requête SQLite :

update table set field = replace(field, 'search', 'replace') where field like '%search%';

Remplacera directement dans le champ "field" toutes les occurrences de "search" par "replace".

bien-sûr, on peut ne pas utiliser "field" à chaque fois. On peut vouloir stocker dans "field1" le résultat d’un S&R sur le contenu d’un champ "field2", le tout avec une condition sur "field3".

update table set field1 = replace(field2, 'search', 'replace') where field3 like '%search%';

On n’est même pas obligé de mettre le "where", mais ça sera bien plus long.

Voir la doc : https://sqlite.org/lang_corefunc.html#replace

#20077  

https://stackoverflow.com/questions/1061692/how-to-update-an-sqlite-database-with-a-search-and-replace-query

Many Small Queries Are Efficient In SQLite

In a client/server database, each SQL statement requires a message round-trip from the application to the database server and back to the application. Doing over 200 round-trip messages, sequentially, can be a serious performance drag.
SQLite is not client/server, however. The SQLite database runs in the same process address space as the application. Queries do not involve message round-trips, only a function call. The latency of a single SQL query is far less in SQLite. Hence, using a large number of queries with SQLite is not the problem.

#19823  

https://sqlite.org/np1queryprob.html

Code Of Conduct

Soit c’est un troll, soit c’est une critique de tous les "code of conduct" absurdes qu’on voit désormais.

J’ai du mal à voir quelqu’un capable de pondre SQLite être aussi con que ça :O

Quoi qu’il en soit, tout ça s’applique aux dév de SQLite (ceux qui font SQLite), pas ceux qui l’utilisent (ni en tant que codeurs qui utilisent SQLite dans le code, ni l’utilisateur final), donc à la limite… osef :) .

#17855  

https://www.sqlite.org/codeofconduct.html

[SQLITE] Note : astuce rapide pour + de perfs

Je me mets à optimiser un peu mon lecteur RSS. Pas qu’il soit lent (il tri 70k entrées RSS en 0,22 secondes), mais je viens de trouver un truc.

Dans le lecteur, je fais une requête particulière pour compter le nombre d’éléments non lus pour chaque flux :

SELECT bt_feed, SUM(bt_statut) AS nbrun FROM rss GROUP BY bt_feed

Ensuite, en PHP, je fais une liste des flux avec le nombre d’éléments non-lus associés. Jusque là, rien d’alarmant.

Sauf que la liste des flux utilisée en PHP à ce stade est déjà recyclée. J’ai juste besoin d’une autre requête pour obtenir le nombre d’articles non lus.

Je peux optimiser un peu ma seconde requête :

SELECT bt_feed, SUM(bt_statut) AS nbrun FROM rss WHERE bt_statut = 1 GROUP BY bt_feed

Ici, le « WHERE bt_statut = 1 » signifie qu’il ne doit faire la "somme" que sur les éléments dont le statut est 1 (pas ceux où il est à 0).

Étant donnée que la BDD contient ~69 000 éléments lus (0) et seulement ~1 000 éléments non lus (1), ça revient à sommer sur seulement 1k éléments au lieu de 69k.

Ça me fait gagner environ 10~15 % du temps de la requête : je passe de 0,22 s à 0,19 s. C’est loin d’être du grappillage de micro-secondes.

#17831  

https://lehollandaisvolant.net/?mode=links&id=20181014161302

35% Faster Than The Filesystem

Utiliser SQLite permet de gagner ~35 % de performances (en moyenne) pour stocker des fichiers, par rapport à un système de fichier normal.

Le gain de perfs est le plus impressionnant sur W10.

#17726  

https://www.sqlite.org/fasterthanfs.html

Scaling SQLite to 4M QPS on a Single Server (EC2 vs Bare Metal) « Expensify Blog

SQLite a le petit problème qu’ils ne peut pas être écrit par deux processus à la fois.

Pourtant, en bricolant un peu et avec les bons softs et les bons réglages, et sur un (très) gros serveur, ils arrivent ici à faire environ 4 millions de requêtes par seconde sur du SQLite.

#17158  

https://blog.expensify.com/2018/01/08/scaling-sqlite-to-4m-qps-on-a-single-server/

DBeaver | Free Universal SQL Client

Oh bien !
Un client SQL qui gère tout un tas de SGBD.

C'est parfait à l'heure où le petit SQLite Browser est devenu une usine à gaz qui ne gère plus tout correctement.

#16152  

http://dbeaver.jkiss.org/

How do I unlock a SQLite database? - Stack Overflow

Si en SQLite il vous arrive d’avoir une erreur disant que la BDD est verouillée (« General error: 5 database is locked »), c’est que vous avez sûrement manqué de fermer un curseur quelque part.

Vérifiez les fetch() et ajoutez un closeCursor() en dessous de votre boucle.

En PHP par exemple :

$req = $handle->query("SELECT * FROM table");
$data = $req->fetch();

Ajoutez ça à la suite :

$req->closeCursor();

Logique, mais quand on le sait pas on peut toujours chercher…
Les forums sont pleins de cette question spécifique, mais la plupart ont des hacks farfelues (relancer Apache, etc.) qui ne sont pas des solutions.

#15872  

http://stackoverflow.com/questions/151026/how-do-i-unlock-a-sqlite-database

Note : si vous utilisez SQLite

Pensez à faire un VACUUM de temps en temps.

Vacuum c’est la commande SQL qui permet de nettoyer la base SQLite.

Par exemple, si vous insérez 500 entrées dans la BDD, le fichier .sqlite prend plus de place. Si maintenant vous virez 499 de ces entrées, le fichier .sqlite ne diminue pas en volume. L’espace est réservé par SQLite pour une utilisation future par SQLite.

En pratique, c’est mieux : SQLite n’aura plus à demander de la place au système de fichier, mais au bout d’un moment la taille du fichier peut-être trop grand par rapport aux données réelles qui sont dans la base.

Vacuum sert justement à reconstruire la base à partir de rien. Un peu comme une défragmentation des données.

Firefox aussi utilise SQLite pour stocker des choses (historique, cookies…). Pensez à appliquer ça une à deux fois par an : http://www.commentcamarche.net/faq/11807-compacter-les-bases-sqlite-de-firefox-3

#14931  

http://lehollandaisvolant.net/?mode=links&id=20160401203858