Aller au contenu

Group by ne garde pas le dernier


Sujets conseillés

Posté

Bonjour,

J'ai la requète suivante :

SELECT membre, titre, date_envoi FROM table GROUP BY membre ORDER BY date_envoi DESC LIMIT 2

Seulement, il ne me prends pas le titre de la dernière occurence de la table, mais celle de la dernière ligne trouvée...

Par exemple si j'ai dans l'ordre chronologique :

1 titre1

2 titre2

3 titre3

1 titre4

La requète me renvoie

1 titre1

3 titre3

Au lieu de me renvoyer

1 titre4

3 titre3

Comment faire pour qu'il garde en réalité la dernière ligne de la table concernant le membre x ? Je parle bien entendu sans faire deux requètes SQL et un programme plus exotique, je cherche à le faire en une seule requète ;)

J'espère m'être fait comprendre...

Merci d'avance !

Posté

Une solution, en utilisant GROUP_CONCAT, mais qui ne répond pas totalement à ton pb.

CREATE TABLE `membre` (
`membre` varchar(30) NOT NULL,
`titre` varchar(30) NOT NULL,
`date` timestamp NOT NULL default CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=latin1;


INSERT INTO `membre` (`membre`, `titre`, `date`) VALUES
('membre1', 'titre1', '2007-02-13 20:48:51'),
('membre2', 'titre1', '2007-02-13 20:48:51'),
('membre1', 'titre2', '2007-02-13 20:49:16'),
('membre3', 'titre3', '2007-02-13 20:49:16'),
('membre1', 'titre3', '2007-02-13 20:49:36'),
('membre2', 'titre5', '2007-02-13 20:49:36');


SELECT membre,
GROUP_CONCAT ( titre order by date desc )
FROM `membre` group by membre


membre GROUP_CONCAT( titre order by date desc )
membre1 titre3,titre2,titre1
membre2 titre5,titre1
membre3 titre3

Posté

Merci de t'être penché sur mon cas ;)

Effectivement ça correspond pas exactement à ce que je cherche... J'attends donc d'autres idées !

Sinon je m'adapterai en conséquences !

Posté

Sarc,

j'ai rien compris a ce que tu souhaites faire mais a première vue, tu utilises mal la clause group by.

Elle sert uniquement a effectuer des regroupements de donner et a faire des calcul dessus (un dénombrement, une moyenne...)

tu ne peux pas mettre de champs dans le select qui ne sont pas spécifié dans le group by excepté les fonctions disponibles (AVG, MIN, MAX, COUNT ...)

Posté (modifié)

Salut,

SELECT membre, titre, MAX(date_envoi) as date FROM table GROUP BY membre, titre, date_envoi ORDER BY date DESC LIMIT 2

Je ne suis pas sûr que ça soit ça que tu souhaites exactement mais j'espère que ça te mettra sur la bonne voie ;).

Modifié par dièse
Posté

Dièse, ça ne renverra pas forcément le titre lié à la dernière date, il me semble...

Vincent, je sais que c'est sa fonction normalement, mais pour ce que je veux faire, ça aurait pu s'adapter :hypocrite:

En fait, vu que tu "n'as rien compris" je tente de mieux expliquer : je veux avoir les 5 derniers titres de la table, mais je ne veux pas de redondance de membres... Je veux donc les 5 derniers titres de la table, mais avec 5 membres différents, même si les 5 derniers titres sont du même membre.

Euh, c'est plus clair là ? :P

Je cherche encore... :wacko:

Posté (modifié)

Bah il faut bien que tu es un classement des titres pour un membre donné. Si Date_envoi ne peut pas remplir ce rôle peut-être disposes-tu d'une clé primaire. Ensuite pour obtenir ce que tu veux il suffit de prendre la requête que je t'ai proposé et d'enlever l'agrégation par titre il me semble.

Edit : je viens de tester ce que je dis et ça marche pas de souci ;)

Modifié par dièse
Posté

Reconsidérons alors le problème avec id, clef unique classée dans le "bon sens" avec la date...

J'ai essayé ta requète, et je regrette, chez moi ça ne marche pas ! Ca renvoie bien le dernier id, ça je suis d'accord, mais pas le titre associé au dernier id...

Je peux toujours récupérer ce titre avec "having id=idmax" mais bon, c'est pas ce qu'il y a de plus propre...

J'avoue que je bloque sur un truc idiot là :shutup:

Posté (modifié)

Bon désolé alors :( , on en revient à ce que disait Vincent impossible d'obtenir un résultat cohérent sur le champ titre si tu n'appliques pas une fonction dessus... C'est pas un problème "idiot" et je ne pense pas que tu puisses t'en sortir avec une requête simple, ça ne te dit pas de tenter des requêtes imbriquées ? :unsure: .

Bon courage ;).

Modifié par dièse
Posté
En fait, vu que tu "n'as rien compris" je tente de mieux expliquer : je veux avoir les 5 derniers titres de la table, mais je ne veux pas de redondance de membres... Je veux donc les 5 derniers titres de la table, mais avec 5 membres différents, même si les 5 derniers titres sont du même membre.

Euh, c'est plus clair là ? :P

Je cherche encore... :wacko:

Moi, non plus je n'avais pas compris. Je pensais que tu voulais pour chaque membre les derniers titres de chaque membre par date desc, d'où ma première réponse.

Posté

Hop j'ai rien dis.... :)

Et sa?

SELECT distinct membre+ titre as membre_titre FROM table ORDER BY date_envoi DESC LIMIT 2

J'ai pas trop capté non plus en faites... ^^

Posté

Bonjour,

Je pense avoir compris ce qu'il veut, pour résumer, il veut les x derniers enregistrements (en fonction de date_envoi) et qu'il n'y ait qu'un seul enregistrement par membre.

Premier point, qui a été évoqué précédemment, lorsqu'une clause GROUP BY est utilisée la requête ne renvoie des valeurs significatives uniquement pour les champs sur lesquels on applique une fonction d'agrégation. Pour les champs auxquels on n'applique pas de fonction d'agrégation, ils n'auront une valeur significative que si ils sont uniques DANS le groupe.

MySQL a étendu l'utilisation de la clause GROUP BY. Vous pouvez utiliser des colonnes ou des calculs de l'expression SELECT qui n'apparaissent pas dans la clause GROUP BY. Cela se dit n'import quelle valeur pour ce groupe. Vous pouvez utiliser cela pour améliorer les performances en évitant les tris ou les regroupements inutiles de valeurs.

Source: Manuel MySQL - GROUP BY avec les champs cachés

Cela implique que la clause ORDER BY ne peut pas se baser sur ces champs cachés efficacement, vu que leur valeur n'est présente qu'une fois le groupement effectué.

Concernant DISTINCT (proposé par Portekoi), le problème est fondamentalement le même, les champs sont "filtrés" avant d'être "ordonnés" et donc tu n'obtiendra pas le résultat souhaité (dans le cas de sa requête tu obtiendra même tous les enregistrements de ta table à moins qu'il y ait un couple "membre/titre" qui se répète... la clause DISTINC ne se fait pas sur un champs, mais sur tous).

Tout cela pour te dire que ce que tu souhaites faire ne peut pas se réaliser en une seule requête à ma connaissance. Apparemment, vu ton premier message, tu sais comment le faire autrement donc je ne vais pas t'expliquer quelles sont les autres solutions ;)

Posté

Merci TheRec d'avoir compris !

Et les autres, bah.. Tant pis. C'était pourtant super clair :hypocrite::hypocrite:

Bon eh bien, si SQL ne veut pas faire ça proprement avec une seule requète, ce que je trouve honteux, je vais donc envoyer plusieurs requètes...

Merci à vous d'avoir répondu ;)

EDIT : Moi, me tromper dans un pseudo, jamais... :hypocrite:

Posté

De rien... Comme d'habitude, ma réponse est rédigée en l'état de mes connaissances, je serais vraiment surpris et content qu'il y ait un moyen de faire ceci en une seule requête ;)

Ce qui épargnera le plus ton serveur de base de donnée ça serait de faire une simple requête classée par le champ date_envoi avec le langage interprété côté serveur que tu utilises (PHP je suppose) de ne pas afficher les enregistrements des membres pour lesquels un enregistrement a déjà été affiché (à chaque enregistrement affiché tu remplis un tableau avec l'id du membre et avant chaque affichage tu vérifies que l'id du membre ne soit pas déjà présent dans ce tableau, avec la fonction in_array).

Au fait moi c'est TheRec... sa_rc :P

Posté

Merci ZeRec :P

Le problème de ton idée, c'est que je ne sais pas combien d'enregistrements sélectionner dans un premier temps afin d'en avoir au minimum 5 à l'arrivée... Imaginons qu'un membre ait posté 50 articles, il me faudrait en sélectionner 54 pour avoir une chance d'avoir 5 articles affichés, ce qui me pose problème.

Non, ce que j'ai fait, c'est que j'ai sélectionner avec un

SELECT DISTINCT membre FROM table ORDER BY date_envoi DESC, id DESC LIMIT 5

Puis pour chaque membre,

SELECT titre FROM table WHERE membre = '$membre'  ORDER BY date_envoi DESC, id DESC LIMIT 1

Ca me fait 6 requètes au total.. C'est pas génial, mais au moins je suis sûr du résultat !

Posté

Oui... ou bien tu ne limites pas la requête qui est juste ordonnée par date et tu arrêtes ta boucle (instruction break) une fois que tu as affiché 5 enregistrement (pour 5 membres différents).

La sélection des données est une des tâches les plus optimisée surtout sur les requêtes simples (pas de clause WHERE, etc.) et il en est de même pour le traitement de ressources (résultats de requêtes) "volumineuses", et côté PHP l'API je stock les données que lorsque on y fait appel, tu peux toujours évaluer cela en utilisant les fonctionnalités de benchmark de MySQL.

Mais la solution dont tu parles fonctionne également, c'est juste que je ne suis pas fanatique des requêtes à l'intérieur des boucles, quoi que dans ton cas le nombre n'évolue que si tu modifie ta limite et donc elle a de fortes chances de se révéler plus performante.

Posté

Je t'avoue ma totale incompétence en terme de SQL, je n'ai pas acheté de littérature à ce sujet et je ne connais que les bases des bases... Et trouver autre chose que les bases sur Internet relève du parcours du combattant, alors que je suis sûr qu'il y a beaucoup de choses suppléementaires à savoir sur le SQL, en termes de techniques, d'optimisation, de construction de la base, etc !

Et j'avoue au passage que je comprends pas bien ta technique avec break... :/ Tu peux me faire un petit code simple pour que je comprenne mieux ? C'est en PHP que je code effectivement ;)

Posté

Bien sûr, voilà un exemple :

<?php
$limit = 5;
$result = mysql_query('SELECT membre, titre, date_envoi FROM membres ORDER BY date_envoi DESC');

$members_done = array();
while(($member = mysql_fetch_array($result)) && count($members_done)+1 <= $limit) { // Dès qu'il n'y a plus de résultat ou que le nombre de titres affiché max est atteint la boucle s'arrêtera.
if(!in_array($member['membre'],$members_done)) { // Le membre a-t-il déjà été affiché ?
print_r($member); // Affichage du membre
$members_done[] = $member['membre']; // Ajout du membre (id) au tableau de membre ayant déjà un titre affiché, vu qu'il vient d'être affiché.
}
}
?>

En gros c'est ça, je n'ai même pas utilisé l'instruction break, en rajoutant la vérification de la limite à la boucle cela va aussi et c'est plus propre.

Suivant la charge du serveur et la taille de la table entre autres ta solution sera plus efficace à mon avis, mais je n'ai pas fait de benchmarks.

Posté

Ok je vois maintenant ce que tu voulais dire...

Mais le fait de prélever la totalité des résultats n'est-il pas contraignant pour la base de données ? Je veux dire, le jour où il y a 300 000 entrées dans la table, prélever ces 300 000 entrées juste pour en garder 5 au final, ce n'est pas "moins optimisé" qu'une méthode comme celle que j'ai donnée, ou tout autre qui se limiterait avec des "where" et "limit" ?

De plus, autre question, tu as des bonnes références sur l'optimisation SQL et le SQL "avancé" ?

Posté

Le tri devra de toute façon être fait avec les deux solutions sur l'entier des entrées de la table, mais comme je te l'ai dit sans faire de benchmark je ne pourrais pas affirmer quune solution est plus rapide que lautre.

Il te suffit de générer beaucoup d'enregistrements et tester les deux méthodes avec la fonction BENCHMARK (car il faut prendre en compte le fait que MySQL effectue un cache des résultats tables lorsque tu fais une requête) qui génère autant de requêtes successives que tu le souhaites... ainsi tu peux voir la charge que chacune des solution génère.

Dans ton cas, comme je l'ai dit précédemment, je pense que ta solution sera plus efficace car tu as cette limite sur la boucle qui fait que le nombre de requêtes est contrôlé.

Concernant les ressources d'apprentissage au sujet de SQL il y a de bon tutoriaux et cours sur SQLPro ;)

Veuillez vous connecter pour commenter

Vous pourrez laisser un commentaire après vous êtes connecté.



Connectez-vous maintenant
×
×
  • Créer...