J'ai une table qui ressemble à cet appelant 'makerar'
cname | wmname | avg
--------+-------------+------------------------
canada | zoro | 2.0000000000000000
spain | luffy | 1.00000000000000000000
spain | usopp | 5.0000000000000000
Et je veux sélectionner la moyenne maximale pour chaque nom de fichier.
SELECT cname, wmname, MAX(avg) FROM makerar GROUP BY cname;
mais j'aurai une erreur,
ERROR: column "makerar.wmname" must appear in the GROUP BY clause or be used in an aggregate function
LINE 1: SELECT cname, wmname, MAX(avg) FROM makerar GROUP BY cname;
alors je fais ça
SELECT cname, wmname, MAX(avg) FROM makerar GROUP BY cname, wmname;
toutefois, cela ne donnera pas les résultats escomptés et le résultat incorrect ci-dessous s'affiche.
cname | wmname | max
--------+--------+------------------------
canada | zoro | 2.0000000000000000
spain | luffy | 1.00000000000000000000
spain | usopp | 5.0000000000000000
Les résultats réels devraient être
cname | wmname | max
--------+--------+------------------------
canada | zoro | 2.0000000000000000
spain | usopp | 5.0000000000000000
Comment puis-je résoudre ce problème?
Remarque: cette table est une vue créée à partir d'une opération précédente.
Oui, il s'agit d'un problème d'agrégation commun. Avant SQL3 (1999) , les champs sélectionnés doivent apparaître dans la clause GROUP BY
[[]].
Pour résoudre ce problème, vous devez calculer l'agrégat dans une sous-requête, puis le joindre à lui-même pour obtenir les colonnes supplémentaires que vous devez afficher:
SELECT m.cname, m.wmname, t.mx
FROM (
SELECT cname, MAX(avg) AS mx
FROM makerar
GROUP BY cname
) t JOIN makerar m ON m.cname = t.cname AND t.mx = m.avg
;
cname | wmname | mx
--------+--------+------------------------
canada | zoro | 2.0000000000000000
spain | usopp | 5.0000000000000000
Mais vous pouvez également utiliser des fonctions de fenêtre, ce qui semble plus simple:
SELECT cname, wmname, MAX(avg) OVER (PARTITION BY cname) AS mx
FROM makerar
;
La seule chose avec cette méthode est qu’elle affichera tous les enregistrements (les fonctions de la fenêtre ne regroupent pas). Mais le résultat sera correct (c'est-à-dire limité au niveau cname
_) MAX
pour le pays de chaque ligne, vous pouvez donc choisir:
cname | wmname | mx
--------+--------+------------------------
canada | zoro | 2.0000000000000000
spain | luffy | 5.0000000000000000
spain | usopp | 5.0000000000000000
La solution, sans doute moins élégante, pour afficher les seuls tuples (cname, wmname)
correspondant à la valeur maximale est la suivante:
SELECT DISTINCT /* distinct here matters, because maybe there are various tuples for the same max value */
m.cname, m.wmname, t.avg AS mx
FROM (
SELECT cname, wmname, avg, ROW_NUMBER() OVER (PARTITION BY avg DESC) AS rn
FROM makerar
) t JOIN makerar m ON m.cname = t.cname AND m.wmname = t.wmname AND t.rn = 1
;
cname | wmname | mx
--------+--------+------------------------
canada | zoro | 2.0000000000000000
spain | usopp | 5.0000000000000000
[*]: Chose intéressante, même si la spécification permet de sélectionner des champs non groupés, les moteurs principaux ne semblent pas vraiment l'aimer. Oracle et SQLServer ne permettent tout simplement pas cela. Mysql l’autorisait par défaut, mais maintenant, depuis la version 5.7, l’administrateur doit activer cette option (ONLY_FULL_GROUP_BY
) manuellement dans la configuration du serveur pour que cette fonctionnalité soit prise en charge ...
Dans Postgres, vous pouvez également utiliser la syntaxe spéciale DISTINCT ON (expression)
:
SELECT DISTINCT ON (cname)
cname, wmname, avg
FROM
makerar
ORDER BY
cname, avg DESC ;
Le problème avec la spécification de champs non groupés et non agrégés dans la sélection group by
est que le moteur n'a aucun moyen de savoir quel champ de l'enregistrement il doit renvoyer dans ce cas. Est-ce le premier? Est-ce dernier? Il n'y a généralement pas d'enregistrement qui correspond naturellement au résultat agrégé (min
et max
sont des exceptions).
Il existe toutefois une solution de contournement: regroupez également le champ requis. En posgres, cela devrait marcher:
SELECT cname, (array_agg(wmname ORDER BY avg DESC))[1], MAX(avg)
FROM makerar GROUP BY cname;
Notez que cela crée un tableau de tous les noms, ordonné par avg, et retourne le premier élément (les tableaux dans postgres sont basés sur 1).
SELECT t1.cname, t1.wmname, t2.max
FROM makerar t1 JOIN (
SELECT cname, MAX(avg) max
FROM makerar
GROUP BY cname ) t2
ON t1.cname = t2.cname AND t1.avg = t2.max;
Utilisation de rank()
fonction window :
SELECT cname, wmname, avg
FROM (
SELECT cname, wmname, avg, rank()
OVER (PARTITION BY cname ORDER BY avg DESC)
FROM makerar) t
WHERE rank = 1;
Remarque
L'un ou l'autre conservera plusieurs valeurs maximales par groupe. Si vous ne voulez qu'un seul enregistrement par groupe, même s'il existe plusieurs enregistrements dont la moyenne est égale à max, vous devez vérifier la réponse de @ ypercube.
Pour moi, il ne s'agit pas d'un "problème d'agrégation commun", mais d'une requête SQL incorrecte. La seule réponse correcte pour "sélectionner le maximum moyen pour chaque nom de fichier ..." est
SELECT cname, MAX(avg) FROM makerar GROUP BY cname;
Le résultat sera:
cname | MAX(avg)
--------+---------------------
canada | 2.0000000000000000
spain | 5.0000000000000000
Ce résultat répond en général à la question "Quel est le meilleur résultat pour chaque groupe?" . Nous voyons que le meilleur résultat pour l'Espagne est 5 et pour le canada le meilleur résultat est 2. C'est vrai, il n'y a pas d'erreur. Si nous devons également afficher wmname, nous devons également répondre à la question: "Qu'est-ce que le RÈGLE pour choisir wmname dans l'ensemble résultant?" Modifions un peu les données d'entrée pour clarifier l'erreur:
cname | wmname | avg
--------+--------+-----------------------
spain | zoro | 1.0000000000000000
spain | luffy | 5.0000000000000000
spain | usopp | 5.0000000000000000
Quel résultat attendez-vous sur cette requête: SELECT cname, wmname, MAX(avg) FROM makerar GROUP BY cname;
? Devrait-il s'agir de spain+luffy
ou spain+usopp
? Pourquoi? Ce n'est pas déterminé dans la requête comment choisir "meilleur" wmname si plusieurs conviennent, ainsi le résultat n'est pas non plus déterminé. C'est pourquoi l'interpréteur SQL renvoie une erreur - la requête n'est pas correcte.
Dans l'autre mot, il n'y a pas de réponse correcte à la question "Qui est le meilleur dans le groupe spain
?" . Luffy n'est pas meilleur que usopp, car usopp a le même "score".
Cela semble fonctionner aussi bien
SELECT *
FROM makerar m1
WHERE m1.avg = (SELECT MAX(avg)
FROM makerar m2
WHERE m1.cname = m2.cname
)
J'ai récemment rencontré ce problème, lorsque j'essayais de compter à l'aide de case when
, et que le fait de modifier l'ordre des instructions which
et count
résout le problème:
SELECT date(dateday) as pick_day,
COUNT(CASE WHEN (apples = 'TRUE' OR oranges 'TRUE') THEN fruit END) AS fruit_counter
FROM pickings
GROUP BY 1
Au lieu d'utiliser - dans le dernier, où j'ai eu des erreurs que les pommes et les oranges devraient apparaître dans les fonctions d'agrégation
CASE WHEN ((apples = 'TRUE' OR oranges 'TRUE') THEN COUNT(*) END) END AS fruit_counter