web-dev-qa-db-fra.com

doit apparaître dans la clause GROUP BY ou être utilisé dans une fonction d'agrégat

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.

221
RandomGuy

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 ...

191
Sebas

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 ;
104
ypercubeᵀᴹ

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).

18
e-neko
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.

15
zero323

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".

5
ox160d05d

Cela semble fonctionner aussi bien

SELECT *
FROM makerar m1
WHERE m1.avg = (SELECT MAX(avg)
                FROM makerar m2
                WHERE m1.cname = m2.cname
               )
0
daintym0sh

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
0
Rachel Windzberg