5160 sujets

Le Bar du forum

Bonsoir.

J'ai fait un sorte de 'mini-blog' avec articles et catégories et j'ai eu envie pour chaque catégorie d'afficher l'article le plus récent.

Ce qui ne me semblait pas très compliqué s'est avéré pas si trivial que ça...

Finalement, j'ai trouvé la solution (pas seule) mais elle avait un défaut... J'ai failli poster mon problème mais j'ai finalement trouvé la parade.

Comme j'ai eu du mal à trouver, j'ai eu envie de poster la solution pour ceux que ça intéresserait. (Et je crois que j'avais fait une réponse qui semblait fonctionner sur un sujet similaire mais ma réponse ne m'avait pas alors satisfaite...)

Je ne poste pas immédiatement pour ceux qui auraient envie de réfléchir.

Pour simplifier, ma table Article a trois colonnes :
nom_categorie, titre_article et date_creation
(et un article peut appartenir à plusieurs catégories...)
et c'est du MySQL...

Smiley smile
Pour avoir la catégorie et la date la plus récente par catégorie, évidemment il faut utiliser MAX et GROUP BY :
SELECT nom_categorie, MAX(date_creation)
FROM Article
GROUP BY nom_categorie


Mais cela ne donne pas le titre. Et bien sûr :
SELECT nom_categorie, titre, MAX(date_creation)
FROM Article
GROUP BY nom_categorie
ne marche pas.

MAX est une fonction d'agrégation et concerne un groupe de lignes bien qu'elle renvoie la valeur d'une ligne. Exception pour nom_categorie qui est commun au groupe...
Ce qui fait qu'on peut très bien écrire :
SELECT nom_categorie, MAX(date_creation), MIN(date_creation)
FROM Article
GROUP BY nom_categorie


Suite au prochain épisode...
Smiley smile
Modifié par Zelena (08 Mar 2017 - 09:59)
Pour l'instant, pour chaque catégorie, je n'ai que la date de création de l'article le plus récent et la catégorie correspondante. Je veux aussi le titre de l'article correspondant.

On ne peut trouver ce renseignement que sur la table Article d'où une jointure ou pour être plus précis une auto-jointure avec sous-requête :
SELECT titre_article
FROM Article
INNER JOIN (
   SELECT nom_categorie, MAX(date_creation) AS date_creation
   FROM Article
   GROUP BY nom_categorie
   ) AS Groupe_article_recent
ON Article.nom_categorie = Groupe_article_recent.nom_categorie
AND Article.date_creation = Groupe_article_recent.date_creation

(on peut noter que si l'article ne peut appartenir qu'à une seule catégorie, établir une correspondance par catégorie est inutile...)
Cette solution, je l'ai trouvée sur le site du JDN, je crois, je n'ai plus le lien...

Mais elle m'a posé un souci, elle laisse de côté une catégorie, la catégorie des 'Sans catégorie', les NULL...

Et c'est pour ainsi dire là que se situe la devinette.

Smiley smile
Modifié par Zelena (17 Mar 2017 - 19:49)
Désolée, j'avais l'impression que cela n'intéressait personne. Smiley confused

Pour avoir, enfin, le fin mot de l'histoire, il y a une phrase magique à dire (ça parle de langue et de chat. Plus facile que X-Files... et beaucoup moins long)

Mais je suppose que tant qu'on n'est pas confronté au problème, on se fiche un peu de cette histoire...

Smiley smile
Il est vrai que je ne suis confronté au problème. D'ailleurs je repousse autant que faire se peu le recourt à une base de donnée - pour l'instant j'en suis aux datas stockées sous forme de Json.

Mais je trouve le sujet très intéressant dans la mesure où, effectivement, de prime abord il semble s'agir d'une démarche facile à accomplir. Toutefois je m'aperçois au fil de votre réflexion, que ce qui semblait si simple se complexifie pour peu qu'on s'y penche.

J'imagine qu'il doit y avoir une commande ou un argument magique sorti de derrière les fagots qui puisse prendre en compte ces "sans famille", mais je m'attends aussi à un renversement de situation qui impliquerait de revoir l'appel à la base de donnée dès son premier caractère.

Je me dis aussi qu'il manque peut-être même bien plus qu'un dernier épisode. En effet, qu'en est-il de ces fameux articles qui appartiennent à plusieurs catégories ? Devons-nous leur infliger une seconde représentation ? J'imagine que non.

Et quand serait-il si certains de ces articles avaient une durée de parution limitée dans le temps ? - certes je sors ici des prérogatives mais cette question me titille; j'imagine que la table aurait une colonne date supplémentaire et me demande comment peut s'opérer dans ce cadre une comparaison avec la date du jour.


Donc s'il me faille confier mon organe à la féline, je le fais volontiers s'il lui plaît et dans la mesure où celle-ci s'engage à me le rendre avant l'heure du repas que je puisse en satisfaire un autre.
Smiley smile
Bon, une personne intéressée, ça me suffit. Smiley smile

Cela dit, tant qu'il n'y a pas de catégorie NULL ou qu'on ne la prend pas en compte, la proposition précédente est tout à fait valable. (D'une façon générale, ceux qui utilisent les bases de données n'aiment pas trop les NULLs qui peuvent être très embêtants.)

La réaction que j'attendais - et que je n'aurai pas - est de me faire remarquer que j'ai utilisé INNER JOIN. Or, INNER JOIN exclut les NULLs de la jointure. Pour prendre en compte la catégorie nulle, il faut utiliser RIGHT JOIN donc :
SELECT titre_article, nom_categorie, date_creation
FROM Article
RIGHT JOIN (
   SELECT nom_categorie, MAX(date_creation) AS date_creation
   FROM Article
   GROUP BY nom_categorie
   ) AS Groupe_article_recent
ON Article.nom_categorie = Groupe_article_recent.nom_categorie
AND Article.date_creation = Groupe_article_recent.date_creation

(remarque : j'ai laissé de côté l'ajout des alias pour les tables pour simplifier au maximum... mais il faut les ajouter... une jointure se fait normalement entre deux tables différentes)

Voilà qui devait marcher... enfin c'est ce que j'ai crû, car si j'ai bien une catégorie nulle qui apparait, le titre et la date de création sont nuls aussi... Smiley confus

Suite et fin demain... Smiley lol

Greg_Lumière a écrit :

En effet, qu'en est-il de ces fameux articles qui appartiennent à plusieurs catégories ?

Un même article peut être le dernier article de deux catégories différentes... Un cas assez particulier.

Si le sujet des bases de données et de MySQL vous intéresse, je vous conseille le tutoriel de Taguan alias Chantal Gribaumont sur OpenClassrooms. Très bien... même si elle n'a pas assez parlé des auto-jointures à mon goût.
Modifié par Zelena (17 Mar 2017 - 19:44)
Pourquoi le titre et la date de création du dernier article correspondant à la catégorie nulle apparaissent-ils, eux-aussi, nuls ?

RIGHT JOIN permet de faire apparaitre la catégorie nulle dans la jointure (celle-ci fait partie des résultats de la sous-requête) mais elle n'est pas habituellement utilisée dans ce but.

INNER JOIN permet de joindre les tables uniquement sur les correspondances indiquées par la clause ON.
LEFT JOIN permet de joindre les tables en ayant toutes les lignes de la table de gauche. Si il n'y a pas de correspondance, les valeurs manquantes sont remplacées par NULL.
Et donc RIGHT JOIN permet de joindre les tables en ayant toutes les lignes de la table de droite. Si il n'y a pas de correspondance, les valeurs manquantes sont remplacées par NULL.

Par conséquent, il n'y a pas eu de correspondance avec la catégorie NULL.
Mais pourquoi ?
Il y a bien dans la table de gauche des articles qui sont sans catégorie, d'ailleurs c'est la même table à gauche et à droite (c'est une auto-jointure...)

Alors là, il y a un truc étrange avec SQL (et aussi avec MySQL), on ne peut pas savoir si une valeur est nulle avec l'opérateur '='. Par exemple, la requête suivante ne renvoie rien :
SELECT titre_article FROM Article WHERE nom_categorie = NULL


Il faut écrire :
SELECT titre_article FROM Article WHERE nom_categorie IS NULL


La rubrique nulle n'a donc pas été prise en compte par la clause :
ON Article.categorie = Groupe_article_recent.nom_categorie


Dès lors, la solution qui fonctionne (mais il y en a peut-être une autre plus simple...), c'est (et j'ai rajouté les alias des tables pour qu'elle fonctionne vraiment) :
SELECT A1.titre_article AS titre, A1.nom_categorie AS nom_categorie, A1.date_creation AS date_creation
FROM Article A1
RIGHT JOIN (
   SELECT A2.nom_categorie, MAX(A2.date_creation) AS date_creation
   FROM Article A2
   GROUP BY A2.nom_categorie
   ) AS Groupe_article_recent
ON (A1.nom_categorie = Groupe_article_recent.nom_categorie OR Groupe_article_recent.nom_categorie IS NULL)
AND A1.date_creation = Groupe_article_recent.date_creation


Voilà, c'est fait.
Smiley smile
Bravo et merci pour ce retour d'expérience fort instructif !
Smiley clapclap

Voici bien un sujet que je me garde précieusement sous le coude.