phải xuất hiện trong mệnh đề GROUP BY hoặc được sử dụng trong hàm tổng hợp


276

Tôi có một bảng trông giống như người gọi này 'Makerar'

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

Và tôi muốn chọn avg tối đa cho mỗi cname.

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

nhưng tôi sẽ gặp lỗi

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;

vì vậy tôi làm điều này

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

tuy nhiên điều này sẽ không cho kết quả có ý định và đầu ra không chính xác bên dưới được hiển thị.

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

Kết quả thực tế phải là

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

Làm thế nào tôi có thể sửa chữa vấn đề này?

Lưu ý: Bảng này là một XEM được tạo từ một hoạt động trước đó.



Tôi không hiểu Tại sao được wmname="usopp"mong đợi và không ví dụ wmname="luffy"?
AndreKR

Câu trả lời:


226

Vâng, đây là một vấn đề tổng hợp phổ biến. Trước SQL3 (1999) , các trường đã chọn phải xuất hiện trong GROUP BYmệnh đề [*].

Để giải quyết vấn đề này, bạn phải tính toán tổng hợp trong một truy vấn phụ và sau đó kết hợp nó với chính nó để có được các cột bổ sung mà bạn cần hiển thị:

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

Nhưng bạn cũng có thể sử dụng các chức năng của cửa sổ, trông đơn giản hơn:

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

Điều duy nhất với phương thức này là nó sẽ hiển thị tất cả các bản ghi (các chức năng của cửa sổ không nhóm). Nhưng nó sẽ hiển thị chính xác (nghĩa là tối đa ở cnamecấp độ) MAXcho quốc gia trong mỗi hàng, do đó tùy thuộc vào bạn:

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

Giải pháp, được cho là kém thanh lịch hơn, để hiển thị các (cname, wmname)bộ dữ liệu duy nhất khớp với giá trị tối đa, là:

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

[*]: Thật thú vị, mặc dù loại thông số kỹ thuật cho phép chọn các trường không được nhóm, các công cụ chính dường như không thực sự thích nó. Oracle và SQLServer hoàn toàn không cho phép điều này. Mysql được sử dụng để cho phép nó theo mặc định, nhưng bây giờ kể từ 5.7, quản trị viên cần bật tùy chọn này ( ONLY_FULL_GROUP_BY) theo cách thủ công trong cấu hình máy chủ để tính năng này được hỗ trợ ...


1
Cú pháp cảm ơn là đúng, nhưng, bạn phải so sánh các giá trị của mx và avg khi tham gia
RandomGuy

1
Có, cú pháp của bạn là chính xác và loại bỏ trùng lặp tuy nhiên bạn cần m.avg = t.mx cuối cùng (sau khi bạn viết JOING) để có kết quả như mong muốn
RandomGuy

1
@Sebas Nó có thể được thực hiện mà không cần tham gia MAX(xem câu trả lời của @ypercube, đó cũng là một giải pháp khác trong câu trả lời của tôi) nhưng không phải là cách bạn làm. Kiểm tra sản lượng dự kiến.
zero323

1
@Sebas Giải pháp của bạn chỉ thêm một cột (MAX avgmỗi cname) nhưng nó không hạn chế các hàng của kết quả (như OP muốn). Xem kết quả thực tế nên được đoạn trong câu hỏi.
ypercubeᵀᴹ

1
Biến tắt ONLY_FULL_GROUP_BY trong MySQL 5.7 không kích hoạt đường SQL quy định cụ thể tiêu chuẩn khi cột có thể được bỏ qua từ group by(hoặc làm cho MySQL cư xử như Postgres). Nó chỉ trở lại hành vi cũ trong đó MySQL trả về kết quả ngẫu nhiên (= "không xác định").
a_horse_with_no_name

126

Trong Postgres, bạn cũng có thể sử dụng đặc biệt DISTINCT ON (expression) cú pháp :

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

5
Nó sẽ không hoạt động như mong đợi nếu một người muốn sắp xếp các cột như avg
amenzhinsky

@amenzhinsky Ý bạn là gì? Nếu một người muốn có tập kết quả được sắp xếp theo một thứ tự khác BY cname?
ypercubeᵀᴹ

@ypercube, Trên thực tế, psql sắp xếp trước và sau đó áp dụng DISTINCT. Trong trường hợp sắp xếp theo avg, chúng tôi sẽ nhận được các kết quả khác nhau cho mỗi giá trị tối thiểu và tối đa của hàng tùy thuộc vào hướng sắp xếp
amenzhinsky

3
Tất nhiên. Nếu bạn không chạy truy vấn tôi đã đăng, bạn sẽ nhận được kết quả khác nhau! Điều đó không giống như "nó sẽ không hoạt động như mong đợi" ...
ypercubeᵀᴹ

1
@Batfan thnx. Lưu ý rằng mặc dù điều này khá thú vị, nhỏ gọn và dễ viết, nhưng đây không phải là cách hiệu quả nhất cho loại truy vấn này.
ypercubeᵀᴹ

27

Vấn đề với việc chỉ định các trường không được nhóm và không tổng hợp trong các group bylựa chọn là công cụ đó không có cách nào để biết trường nào sẽ được trả về trong trường hợp này. Là nó đầu tiên? Có phải là cuối cùng? Thường không có hồ sơ tương ứng tự nhiên với kết quả tổng hợp ( minmax là trường hợp ngoại lệ).

Tuy nhiên, có một cách giải quyết: làm cho trường bắt buộc cũng được tổng hợp. Trong posgres, điều này sẽ làm việc:

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

Lưu ý rằng điều này tạo ra một mảng của tất cả các tên, được sắp xếp theo avg và trả về phần tử đầu tiên (các mảng trong postgres là dựa trên 1).


Điểm tốt. Mặc dù có vẻ như DB có thể thực hiện một phép nối ngoài để liên kết các trường không tổng hợp từ mỗi hàng với kết quả tổng hợp mà hàng đã đóng góp. Tôi thường tò mò tại sao họ không có lựa chọn nào cho việc đó. Mặc dù tôi chỉ đơn giản là không biết gì về tùy chọn này :)
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;

Sử dụng rank() chức năng cửa sổ :

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

Ghi chú

Một trong hai sẽ bảo tồn nhiều giá trị tối đa cho mỗi nhóm. Nếu bạn chỉ muốn một bản ghi cho mỗi nhóm ngay cả khi có nhiều hơn một bản ghi với avg bằng max, bạn nên kiểm tra câu trả lời của @ ypercube.


16

Đối với tôi, nó không phải là về một "vấn đề tổng hợp chung", mà chỉ là về một truy vấn SQL không chính xác. Câu trả lời đúng duy nhất cho "chọn avg tối đa cho mỗi tên ..." là

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

Kết quả sẽ là:

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

Kết quả này trong câu trả lời chung cho câu hỏi "Kết quả tốt nhất cho mỗi nhóm là gì?" . Chúng tôi thấy rằng kết quả tốt nhất cho Tây Ban Nha là 5 và đối với Canada, kết quả tốt nhất là 2. Đó là sự thật và không có lỗi. Nếu chúng ta cũng cần hiển thị wmname , chúng ta phải trả lời câu hỏi: " QUY TẮC để chọn wmname từ tập kết quả là gì?" Hãy thay đổi dữ liệu đầu vào một chút để làm rõ lỗi:

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

Kết quả nào bạn mong đợi trên runnig truy vấn này : SELECT cname, wmname, MAX(avg) FROM makerar GROUP BY cname;? Có nên spain+luffyhay spain+usoppkhông? Tại sao? Nó không được xác định trong truy vấn làm thế nào để chọn wmname "tốt hơn" nếu một số phù hợp, do đó kết quả cũng không được xác định. Đó là lý do tại sao trình thông dịch SQL trả về lỗi - truy vấn không chính xác.

Nói cách khác, không có câu trả lời chính xác cho câu hỏi "Ai là người giỏi nhất trong spainnhóm?" . Luffy không giỏi hơn usopp, vì usopp có cùng "điểm số".


Giải pháp này làm việc cho tôi quá. Tôi gặp vấn đề về truy vấn vì ORM của tôi cũng bao gồm khóa chính được liên kết, dẫn đến truy vấn không chính xác sau : SELECT cname, id, MAX(avg) FROM makerar GROUP BY cname;, đã đưa ra lỗi sai này.
Roberto

1

Điều này dường như cũng làm việc

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

0

Gần đây tôi đã gặp phải vấn đề này, khi cố gắng đếm bằng cách sử dụng case whenvà thấy rằng việc thay đổi thứ tự của các câu lệnh whichcountkhắc phục sự cố:

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

Thay vì sử dụng - sau này, ở đó tôi gặp lỗi mà táo và cam sẽ xuất hiện trong các hàm tổng hợp

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

1
Các whichtuyên bố?
Hillary Sanders
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.