it-swarm.com.de

muss in der GROUP BY-Klausel enthalten sein oder in einer Aggregatfunktion verwendet werden

Ich habe einen Tisch, der aussieht wie dieser Anrufer 'makerar'

 cname  | wmname |          avg           
--------+-------------+------------------------
 canada | zoro   |     2.0000000000000000
 spain  | luffy  | 1.00000000000000000000
 spain  | usopp  |     5.0000000000000000

Und ich möchte den maximalen Durchschnitt für jeden C-Namen auswählen.

SELECT cname, wmname, MAX(avg)  FROM makerar GROUP BY cname;

aber ich werde einen Fehler bekommen,

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;

also mache ich das

SELECT cname, wmname, MAX(avg)  FROM makerar GROUP BY cname, wmname;

dies führt jedoch nicht zu den beabsichtigten Ergebnissen, und die unten angegebene falsche Ausgabe wird angezeigt.

 cname  | wmname |          max           
--------+--------+------------------------
 canada | zoro   |     2.0000000000000000
 spain  | luffy  | 1.00000000000000000000
 spain  | usopp  |     5.0000000000000000

Tatsächliche Ergebnisse sollten sein

 cname  | wmname |          max           
--------+--------+------------------------
 canada | zoro   |     2.0000000000000000
 spain  | usopp  |     5.0000000000000000

Wie kann ich dieses Problem beheben?

Hinweis: Diese Tabelle ist eine ANSICHT, die aus einer vorherigen Operation erstellt wurde.

221
RandomGuy

Ja, dies ist ein häufiges Aggregationsproblem. Vor SQL3 (1999) müssen die ausgewählten Felder in der GROUP BY -Klausel [*] erscheinen.

Um dieses Problem zu umgehen, müssen Sie das Aggregat in einer Unterabfrage berechnen und dann mit sich selbst verknüpfen, um die zusätzlichen Spalten zu erhalten, die Sie anzeigen müssen:

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

Sie können aber auch Fensterfunktionen verwenden, was einfacher aussieht:

SELECT cname, wmname, MAX(avg) OVER (PARTITION BY cname) AS mx
FROM makerar
;

Das einzige, was mit dieser Methode möglich ist, ist, dass alle Datensätze angezeigt werden (Fensterfunktionen gruppieren sich nicht). Es wird jedoch die korrekte (d. H. Maximal auf cname Ebene) MAX für das Land in jeder Zeile angezeigt. Es liegt also an Ihnen:

 cname  | wmname |          mx           
--------+--------+------------------------
 canada | zoro   |     2.0000000000000000
 spain  | luffy  |     5.0000000000000000
 spain  | usopp  |     5.0000000000000000

Die wohl weniger elegante Lösung, die einzigen (cname, wmname) Tupel anzuzeigen, die dem Maximalwert entsprechen, lautet:

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

[*]: Interessanterweise scheinen die wichtigsten Suchmaschinen es nicht wirklich zu mögen, obwohl die Spezifikation die Auswahl von nicht gruppierten Feldern erlaubt. Oracle und SQLServer erlauben dies überhaupt nicht. Mysql hat es standardmäßig zugelassen, aber seit 5.7 muss der Administrator diese Option (ONLY_FULL_GROUP_BY) manuell in der Serverkonfiguration aktivieren, damit diese Funktion unterstützt wird ...

191
Sebas

In Postgres können Sie auch die spezielle DISTINCT ON (expression) -Syntax verwenden:

SELECT DISTINCT ON (cname) 
    cname, wmname, avg
FROM 
    makerar 
ORDER BY 
    cname, avg DESC ;
104
ypercubeᵀᴹ

Das Problem bei der Angabe von nicht gruppierten und nicht aggregierten Feldern in der Auswahl von group by besteht darin, dass die Engine nicht weiß, welches Datensatzfeld in diesem Fall zurückgegeben werden soll. Ist es zuerst? Ist es das letzte Normalerweise gibt es keinen Datensatz, der dem aggregierten Ergebnis entspricht (min und max sind Ausnahmen).

Es gibt jedoch eine Problemumgehung: Machen Sie das erforderliche Feld auch aggregiert. In posgres sollte dies funktionieren:

SELECT cname, (array_agg(wmname ORDER BY avg DESC))[1], MAX(avg)
FROM makerar GROUP BY cname;

Beachten Sie, dass hierdurch ein Array aller wnames erstellt wird, sortiert nach avg, und das erste Element zurückgegeben wird (Arrays in postgres basieren auf 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;

Mit rank()Fensterfunktion :

SELECT cname, wmname, avg
FROM (
    SELECT cname, wmname, avg, rank() 
    OVER (PARTITION BY cname ORDER BY avg DESC)
    FROM makerar) t
WHERE rank = 1;

Hinweis

In beiden Fällen werden mehrere Maximalwerte pro Gruppe beibehalten. Wenn Sie nur einen Datensatz pro Gruppe möchten, auch wenn es mehr als einen Datensatz mit einem Durchschnitt von max gibt, sollten Sie die Antwort von @ ypercube überprüfen.

15
zero323

Für mich geht es nicht um ein "häufiges Aggregationsproblem", sondern nur um eine falsche SQL-Abfrage. Die einzige richtige Antwort für "Wählen Sie den maximalen Durchschnitt für jeden C-Namen ..." ist

SELECT cname, MAX(avg) FROM makerar GROUP BY cname;

Das Ergebnis wird sein:

 cname  |      MAX(avg)
--------+---------------------
 canada | 2.0000000000000000
 spain  | 5.0000000000000000

Dieses Ergebnis beantwortet im Allgemeinen die Frage "Was ist das beste Ergebnis für jede Gruppe?" . Wir sehen, dass das beste Ergebnis für Spanien 5 ist und für Kanada das beste Ergebnis 2. Es ist wahr und es gibt keinen Fehler. Wenn wir auch wmname anzeigen müssen, müssen wir die Frage beantworten: "Was ist RULE, um wmname aus der Ergebnismenge auszuwählen?" Lassen Sie uns die Eingabedaten etwas ändern, um den Fehler zu klären:

  cname | wmname |        avg           
--------+--------+-----------------------
 spain  | zoro   |  1.0000000000000000
 spain  | luffy  |  5.0000000000000000
 spain  | usopp  |  5.0000000000000000

Welches Ergebnis erwarten Sie beim Ausführen dieser Abfrage: SELECT cname, wmname, MAX(avg) FROM makerar GROUP BY cname;? Sollte es spain+luffy oder spain+usopp sein? Warum? Es ist nicht bestimmt in der Abfrage, wie "besser" zu wählen ist wmname wenn mehrere geeignet sind, so wird das Ergebnis auch nicht bestimmt. Deshalb gibt der SQL-Interpreter einen Fehler zurück - die Abfrage ist nicht korrekt.

Im anderen Wort gibt es keine richtige Antwort auf die Frage "Wer ist der Beste in der spain Gruppe?" . Ruffy ist nicht besser als lysop, weil lysop die gleiche "Punktzahl" hat.

5
ox160d05d

Dies scheint auch zu funktionieren

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

Ich bin kürzlich auf dieses Problem gestoßen, als ich versucht habe, mit case when zu zählen, und habe festgestellt, dass das Ändern der Reihenfolge der Anweisungen which und count das Problem behebt:

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

Anstatt - in letzterem zu verwenden, wo ich Fehler bekam, sollten Äpfel und Orangen in aggregierten Funktionen erscheinen

CASE WHEN ((apples = 'TRUE' OR oranges 'TRUE') THEN COUNT(*) END) END AS fruit_counter
0