musi pojawić się w klauzuli GROUP BY lub być użyty w funkcji agregującej

Mam tabelę, która wygląda jak ten rozmówca 'makerar'

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

I chcę wybrać maksymalną wartość avg dla każdego cname.

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

Ale dostanę błąd,

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;

Więc robię to

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

Jednak nie da to zamierzonych wyników, a niepoprawne wyniki poniżej są pokazane.

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

Rzeczywiste wyniki powinny być

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

Jak mogę rozwiązać ten problem?

Uwaga: Ta tabela jest widokiem utworzonym z poprzedniego operacja.

Author: a_horse_with_no_name, 2013-10-26

7 answers

Tak, jest to powszechny problem agregacji. Przed SQL3 (1999) , wybrane pola muszą pojawić się w GROUP BY klauzuli[*].

Aby obejść ten problem, musisz obliczyć agregat w zapytaniu podrzędnym, a następnie połączyć go ze sobą, aby uzyskać dodatkowe kolumny, które musisz pokazać:

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

Ale można również korzystać z funkcji okien, które wyglądają prościej:

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

Jedyną rzeczą w tej metodzie jest to, że wyświetli ona wszystkie rekordy (funkcje okien nie Grupa). Ale pokaże poprawny (tj. maksymalnie na poziomie cname) MAX dla kraju w każdym wierszu, więc to zależy od ciebie:

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

Rozwiązaniem, prawdopodobnie mniej eleganckim, aby pokazać tylko (cname, wmname) krotki pasujące do wartości maksymalnej, jest:

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

[*]: Co ciekawe, mimo że spec pozwala na wybieranie pól niezagrupowanych, głównym silnikom wydaje się to nie podobać. Oracle i SQLServer po prostu na to nie pozwalają. Mysql domyślnie zezwalał na to, ale teraz Od 5.7 administrator musi ręcznie włączyć tę opcję (ONLY_FULL_GROUP_BY) w konfiguracji serwera, aby ta funkcja była obsługiwana...

 261
Author: Sebas,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-11-17 06:34:26

W Postgres można również użyć specjalnego DISTINCT ON (expression) składnia:

SELECT DISTINCT ON (cname) 
    cname, wmname, avg
FROM 
    makerar 
ORDER BY 
    cname, avg DESC ;
 136
Author: ypercubeᵀᴹ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-11-01 09:05:35

Problem z określeniem pól niezagregowanych i niezagregowanych w selektach group by polega na tym, że silnik nie ma możliwości poznania pola rekordu, które powinien zwrócić w tym przypadku. Najpierw? Ostatni? Zwykle nie ma zapisu, który w naturalny sposób odpowiada zagregowanemu wynikowi (min i max są wyjątkami).

Istnieje jednak obejście: zagreguj Wymagane pole. W posgres powinno to działać:

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

Zauważ, że tworzy to tablicę wszystkich wnames, uporządkowane przez avg i zwraca pierwszy element (tablice w postgres są oparte na 1).

 36
Author: e-neko,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-05-28 00:19:39

Dla mnie nie chodzi o "powszechny problem agregacji", ale o niepoprawne zapytanie SQL. Jedna poprawna odpowiedź dla "wybierz maksymalną wartość avg dla każdego cname..."is

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

Wynik będzie:

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

Ten wynik ogólnie odpowiada na pytanie "jaki jest najlepszy wynik dla każdej grupy?". Widzimy, że najlepszy wynik dla Hiszpanii to 5, a dla Kanady najlepszy wynik to 2. To prawda i nie ma błędu. Jeśli chcemy wyświetlić wmname Również, możemy musisz odpowiedzieć na pytanie: "Jaka jest reguła aby wybrać wmname z wynikowego zestawu?"Zmieńmy trochę dane wejściowe, aby wyjaśnić błąd:

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

Jakiego wyniku oczekujesz po uruchomieniu tego zapytania: SELECT cname, wmname, MAX(avg) FROM makerar GROUP BY cname;? Powinno być spain+luffy Czy spain+usopp? Dlaczego? Nie jest określone W zapytaniu jak wybrać "lepsze" wmname Jeśli kilka jest odpowiednich, więc wynik również nie jest określony. Dlatego interpreter SQL zwraca błąd-zapytanie nie jest zgadza się.

Innymi słowy, nie ma poprawnej odpowiedzi na pytanie "Kto jest najlepszy w grupie spain?". Luffy nie jest lepszy od Usoppa, ponieważ usopp ma ten sam "wynik".

 25
Author: ox160d05d,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-01-08 17:09:35
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;

Za pomocą rank() funkcja okna :

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

Uwaga

Każda z nich zachowa wiele wartości maksymalnych na Grupę. Jeśli chcesz tylko jeden rekord na Grupę, nawet jeśli jest więcej niż jeden rekord z avg równym max, powinieneś sprawdzić odpowiedź @ypercube.

 17
Author: zero323,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-11-01 22:52:31

To wydaje się działać również

SELECT *
FROM makerar m1
WHERE m1.avg = (SELECT MAX(avg)
                FROM makerar m2
                WHERE m1.cname = m2.cname
               )
 1
Author: daintym0sh,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-01-30 21:52:21

Ostatnio napotkałem ten problem, próbując policzyć za pomocą case when i stwierdziłem, że zmiana kolejności instrukcji which i count rozwiązuje problem:

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

Zamiast używać-w tym drugim, gdzie mam błędy, że jabłka i pomarańcze powinny pojawić się w funkcjach zbiorczych

CASE WHEN ((apples = 'TRUE' OR oranges 'TRUE') THEN COUNT(*) END) END AS fruit_counter
 -1
Author: Rachel Windzberg,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-11-09 14:10:49