GROUP BY 절에 나타나거나 집계 함수에 사용되어야합니다.


276

이 발신자 '메이커'처럼 보이는 테이블이 있습니다.

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

그리고 각 cname에 대한 최대 평균을 선택하고 싶습니다.

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

하지만 오류가 발생합니다.

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;

그래서 나는 이것을한다

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

그러나 이것은 의도 한 결과를 제공하지 않으며 아래의 잘못된 출력이 표시됩니다.

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

실제 결과는

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

이 문제를 해결하려면 어떻게해야합니까?

참고 :이 테이블은 이전 작업에서 생성 된 VIEW입니다.



이해가 안 돼요 wmname="usopp"예를 들어 왜 그렇지 wmname="luffy"않습니까?
AndreKR

답변:


226

예, 이것은 일반적인 집계 문제입니다. SQL3 (1999) 이전 에는 선택된 필드가 GROUP BY절 [*]에 나타나야합니다 .

이 문제를 해결하려면 하위 쿼리에서 집계를 계산 한 다음 자체와 결합하여 표시해야 할 추가 열을 가져와야합니다.

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

그러나 더 단순 해 보이는 창 함수를 사용할 수도 있습니다.

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

이 방법의 유일한 점은 모든 레코드를 표시한다는 것입니다 (창 함수는 그룹화되지 않음). 그러나 각 행의 국가에 대한 올바른 (즉, 최대 cname수준) MAX을 표시하므로 귀하에게 달려 있습니다.

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

(cname, wmname)최대 값과 일치하는 유일한 튜플 을 표시하는 덜 우아한 솔루션 은 다음과 같습니다.

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

[*] : 흥미롭게도 사양 종류에 따라 그룹화되지 않은 필드를 선택할 수 있지만 주요 엔진은 실제로 마음에 들지 않는 것 같습니다. Oracle과 SQLServer는 이것을 전혀 허용하지 않습니다. MySQL은 기본적으로 허용했지만 5.7 이후 관리자는 ONLY_FULL_GROUP_BY이 기능을 지원하려면 서버 구성 에서이 옵션 ( )을 수동으로 활성화해야합니다 ...


1
감사 구문은 중요하지만, 참여할 때 mx와 avg의 값을 비교해야합니다
RandomGuy

1
그렇습니다. 구문이 정확하고 중복을 제거하지만 원하는 결과를 얻으려면 마지막에 m.avg = t.mx가 필요합니다 (JORING을 쓴 후)
RandomGuy

1
@Sebas 그것은 참여하지 않고 수행 할 수 있지만 MAX(@ypercube의 답변 참조, 내 답변에는 다른 솔루션이 있습니다) 당신이하는 방식은 아닙니다. 예상 출력을 확인하십시오.
zero323

1
@Sebas 솔루션은 열 (MAX avgper cname) 만 추가 하지만 결과 행을 제한하지 않습니다 (OP가 원하는대로). 문제의 실제 결과는 단락을 참조하십시오 .
ypercubeᵀᴹ

1
켜기 끄기 ONLY_FULL_GROUP_BY MySQL의 5.7에서하는 방법을 활성화하지 않는 열이 생략 될 수있는 SQL 표준 지정 group by(또는 포스트 그레스 같은 MySQL의 동작합니다한다). 이는 MySQL이 대신 무작위 (= "불확정") 결과를 반환하는 이전 동작으로 되돌아갑니다.
a_horse_with_no_name

126

Postgres에서는 특수 DISTINCT ON (expression)구문을 사용할 수도 있습니다 .

SELECT DISTINCT ON (cname) 
    cname, wmname, avg
FROM 
    makerar 
ORDER BY 
    cname, avg DESC ;

5
평균과 같은 열을 정렬하려는 경우 예상대로 작동하지 않습니다.
amenzhinsky

@amenzhinsky 무슨 뜻인가요? 결과 세트를 BY cname? 와 다른 순서로 정렬하려면 ?
ypercubeᵀᴹ

@ypercube, 실제로 psql은 먼저 정렬 한 다음 DISTINCT를 적용합니다. 평균으로 정렬하는 경우 정렬 방향에 따라 모든 행의 최소값과 최대 값에 대해 다른 결과를 얻을 수 있습니다.
amenzhinsky

3
물론이야. 내가 게시 한 쿼리를 실행하지 않으면 다른 결과가 나타납니다! 그것은 "예상대로 작동하지 않습니다"와 동일하지 않습니다 ...
ypercubeᵀᴹ

1
@Batfan thnx. 이것은 매우 시원하고 작고 작성하기 쉽지만 종종 이런 종류의 쿼리에 가장 효율적인 방법은 아닙니다.
ypercubeᵀᴹ

27

group by선택 에서 그룹화되지 않은 필드와 집계되지 않은 필드를 지정할 때의 문제점 은 엔진이이 경우 어떤 레코드의 필드를 리턴해야하는지 알 수 없다는 것입니다. 처음인가요? 마지막인가요? 자연스럽게 집계 결과에 해당하는 (아무 기록 일반적으로 없습니다 minmax예외가).

그러나 해결 방법이 있습니다. 필수 필드도 집계하십시오. posgres에서는 다음과 같이 작동합니다.

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

이것은 avg 순으로 모든 wname의 배열을 생성하고 첫 번째 요소를 반환합니다 (postgres의 배열은 1을 기반으로 함).


좋은 지적. DB가 각 행의 비 집계 필드를 행이 기여한 집계 결과에 연결하기 위해 외부 조인을 수행 할 수있는 것 같습니다. 나는 왜 그들이 그것을위한 옵션이 없는지 궁금했다. 이 옵션을 간단히 알 수는 없지만 :)
Ben Simmons

16
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;

rank() 창 기능 사용 :

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

노트

둘 중 하나는 그룹당 여러 개의 최대 값을 유지합니다. 평균이 max와 동일한 레코드가 두 개 이상인 경우에도 그룹당 단일 레코드 만 원하는 경우 @ypercube의 답변을 확인해야합니다.


16

저에게는 "일반적인 집계 문제"가 아니라 잘못된 SQL 쿼리에 관한 것입니다. "각 cname에 대한 최대 평균 선택 ..."에 대한 정답은

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

결과는 다음과 같습니다.

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

이 결과는 일반적으로 "각 그룹에 가장 적합한 결과는 무엇입니까?"라는 질문에 대답합니다. . 스페인의 경우 최상의 결과는 5이고 캐나다의 경우 최상의 결과는 2입니다. 사실이며 오류가 없습니다. wmname 도 표시해야하는 경우 , " 결과 세트에서 wmname을 선택 하는 규칙 은 무엇입니까 ?"라는 질문에 대답해야합니다. 실수를 명확히하기 위해 입력 데이터를 약간 변경해 봅시다 :

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

이 쿼리를 실행하면 어떤 결과가 예상 SELECT cname, wmname, MAX(avg) FROM makerar GROUP BY cname;됩니까? 그것은이어야한다 spain+luffyspain+usopp? 왜? 되지 않은 결정 "더 나은"를 선택하는 방법을 쿼리에서 wmname을 여러 적합하면 결과도 결정되지 않도록. 이것이 SQL 인터프리터가 오류를 반환하는 이유입니다. 쿼리가 올바르지 않습니다.

다시 말해, " spain그룹 에서 누가 최고 입니까?"라는 질문에 대한 정답은 없습니다. . 루피는 usopp보다 낫지 않습니다. 왜냐하면 usopp는 같은 "점수"를 가지고 있기 때문입니다.


이 솔루션도 저에게 효과적이었습니다. 내 ORM에 관련 기본 키가 포함되어있어 다음과 같은 잘못된 쿼리 SELECT cname, id, MAX(avg) FROM makerar GROUP BY cname;가 발생하여 쿼리 문제가 발생했습니다.이 잘못된 오류가 발생했습니다.
로베르토

1

이것은 잘 작동하는 것 같습니다

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

0

최근에을 사용하여 계산하려고 할 때이 문제가 발생 case when하여 whichand count문의 순서를 변경 하면 문제가 해결 된다는 것을 알았습니다 .

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

후자를 사용하는 대신 사과와 오렌지가 집계 함수에 표시되어야하는 오류가 발생했습니다.

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

1
which문?
Hillary Sanders
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.