Nhận bản ghi với giá trị tối đa cho từng nhóm kết quả SQL được nhóm


229

Làm thế nào để bạn có được các hàng chứa giá trị tối đa cho mỗi bộ được nhóm?

Tôi đã thấy một số biến thể quá phức tạp cho câu hỏi này, và không có câu trả lời hay. Tôi đã cố gắng đưa ra ví dụ đơn giản nhất có thể:

Đưa ra một bảng như thế bên dưới, với các cột người, nhóm và tuổi, làm thế nào bạn có được người già nhất trong mỗi nhóm? (Một cà vạt trong một nhóm sẽ cho kết quả bảng chữ cái đầu tiên)

Person | Group | Age
---
Bob  | 1     | 32  
Jill | 1     | 34  
Shawn| 1     | 42  
Jake | 2     | 29  
Paul | 2     | 36  
Laura| 2     | 39  

Bộ kết quả mong muốn:

Shawn | 1     | 42    
Laura | 2     | 39  

3
Thận trọng: Câu trả lời được chấp nhận hoạt động vào năm 2012 khi nó được viết. Tuy nhiên, nó không còn hoạt động vì nhiều lý do, như được đưa ra trong Nhận xét.
Rick James

Câu trả lời:


132

Có một cách cực kỳ đơn giản để làm điều này trong mysql:

select * 
from (select * from mytable order by `Group`, age desc, Person) x
group by `Group`

Điều này hoạt động vì trong mysql bạn được phép không tổng hợp các cột không theo nhóm, trong trường hợp đó, mysql chỉ trả về hàng đầu tiên . Giải pháp là trước tiên sắp xếp dữ liệu sao cho mỗi nhóm hàng bạn muốn là đầu tiên, sau đó nhóm theo các cột bạn muốn giá trị.

Bạn tránh các truy vấn con phức tạp cố gắng tìm max()vv, và cả vấn đề trả lại nhiều hàng khi có nhiều hàng có cùng giá trị tối đa (như các câu trả lời khác sẽ làm)

Lưu ý: Đây là một giải pháp chỉ dành cho mysql . Tất cả các cơ sở dữ liệu khác mà tôi biết sẽ đưa ra lỗi cú pháp SQL với thông báo "các cột không tổng hợp không được liệt kê trong nhóm theo mệnh đề" hoặc tương tự. Vì giải pháp này sử dụng hành vi không có giấy tờ , nên thận trọng hơn có thể muốn bao gồm một thử nghiệm để khẳng định rằng nó vẫn hoạt động nếu phiên bản tương lai của MySQL thay đổi hành vi này.

Cập nhật phiên bản 5.7:

Kể từ phiên bản 5.7, sql-modecài đặt bao gồm ONLY_FULL_GROUP_BYtheo mặc định, vì vậy để thực hiện công việc này, bạn không được có tùy chọn này (chỉnh sửa tệp tùy chọn cho máy chủ để xóa cài đặt này).


66
"mysql chỉ trả về hàng đầu tiên." - có thể đây là cách nó hoạt động nhưng nó không được bảo đảm. Các tài liệu cho biết: "Máy chủ là tự do lựa chọn bất kỳ giá trị từ mỗi nhóm, vì vậy, trừ khi họ là như nhau, các giá trị được lựa chọn là không xác định." . Máy chủ không chọn các hàng nhưng các giá trị (không nhất thiết phải từ cùng một hàng) cho mỗi cột hoặc biểu thức xuất hiện trong SELECTmệnh đề và không được tính bằng cách sử dụng hàm tổng hợp.
axiac

16
Hành vi này đã thay đổi trên MySQL 5.7.5 và theo mặc định, nó từ chối truy vấn này vì các cột trong SELECTmệnh đề không phụ thuộc chức năng vào các GROUP BYcột. Nếu nó được cấu hình để chấp nhận nó (`ONLY_FULL_GROUP_BY` bị vô hiệu hóa), thì nó hoạt động giống như các phiên bản trước (nghĩa là các giá trị của các cột đó không xác định).
axiac

17
Tôi ngạc nhiên câu trả lời này đã nhận được rất nhiều upvote. Đó là sai và nó là xấu. Truy vấn này không được đảm bảo để làm việc. Dữ liệu trong truy vấn con là một tập hợp không có thứ tự bất chấp thứ tự theo mệnh đề. MySQL có thể thực sự đặt hàng các bản ghi ngay bây giờ và giữ thứ tự đó, nhưng nó sẽ không vi phạm bất kỳ quy tắc nào nếu nó ngừng làm như vậy trong một số phiên bản trong tương lai. Sau đó GROUP BYngưng tụ thành một bản ghi, nhưng tất cả các trường sẽ được chọn tùy ý từ các bản ghi. Có thể là MySQL hiện tại chỉ đơn giản là luôn chọn hàng đầu tiên, nhưng nó cũng có thể chọn bất kỳ hàng nào khác hoặc thậm chí các giá trị từ các hàng khác nhau trong phiên bản tương lai.
Thorsten Kettner

9
Được rồi, chúng tôi không đồng ý ở đây. Tôi không sử dụng các tính năng không có giấy tờ hiện đang hoạt động và dựa vào một số thử nghiệm hy vọng sẽ bao gồm điều này. Bạn biết rằng bạn thật may mắn khi việc triển khai hiện tại mang lại cho bạn bản ghi đầu tiên hoàn chỉnh trong đó các tài liệu nói rõ rằng bạn có thể có bất kỳ giá trị không xác định nào thay thế, nhưng bạn vẫn sử dụng nó. Một số phiên đơn giản hoặc cài đặt cơ sở dữ liệu có thể thay đổi điều này bất cứ lúc nào. Tôi cho rằng điều này quá rủi ro.
Thorsten Kettner

3
Câu trả lời này có vẻ sai. Theo tài liệu , máy chủ có thể tự do chọn bất kỳ giá trị nào từ mỗi nhóm ... Hơn nữa, việc lựa chọn các giá trị từ mỗi nhóm có thể bị ảnh hưởng bằng cách thêm mệnh đề ORDER BY. Sắp xếp tập kết quả xảy ra sau khi các giá trị được chọn và ORDER BY không ảnh hưởng đến giá trị nào trong mỗi nhóm mà máy chủ chọn.
Tgr

296

Giải pháp đúng là:

SELECT o.*
FROM `Persons` o                    # 'o' from 'oldest person in group'
  LEFT JOIN `Persons` b             # 'b' from 'bigger age'
      ON o.Group = b.Group AND o.Age < b.Age
WHERE b.Age is NULL                 # bigger age not found

Làm thế nào nó hoạt động:

Nó khớp với từng hàng ovới tất cả các hàng bcó cùng giá trị trong cột Groupvà giá trị lớn hơn trong cột Age. Bất kỳ hàng nào okhông có giá trị tối đa của nhóm trong cột Agesẽ khớp với một hoặc nhiều hàng từ đó b.

Điều này LEFT JOINlàm cho nó phù hợp với người lớn tuổi nhất trong nhóm (bao gồm cả những người ở một mình trong nhóm của họ) với một hàng đầy đủ NULLtừ b('không có tuổi lớn nhất trong nhóm').
Việc sử dụng INNER JOINlàm cho các hàng này không khớp và chúng bị bỏ qua.

Các WHEREkhoản giữ chỉ có các hàng cóNULL s trong các lĩnh vực chiết xuất từ b. Họ là những người lớn tuổi nhất từ ​​mỗi nhóm.

Đọc thêm

Giải pháp này và nhiều giải pháp khác được giải thích trong cuốn sách SQL Antipotypes: Tránh những cạm bẫy của lập trình cơ sở dữ liệu


43
BTW này có thể trả về hai hoặc nhiều hàng cho cùng một nhóm nếu o.Age = b.Age, ví dụ: nếu Paul từ nhóm 2 ở trên 39 như Laura. Tuy nhiên, nếu chúng tôi không muốn hành vi như vậy, chúng tôi có thể làm:ON o.Group = b.Group AND (o.Age < b.Age or (o.Age = b.Age and o.id < b.id))
Todor

8
Đáng kinh ngạc! Đối với các bản ghi 20 triệu, nó nhanh hơn 50 lần so với thuật toán "ngây thơ" (tham gia vào một truy vấn con với max ())
user2706534

3
Hoạt động hoàn hảo với các bình luận @Todor. Tôi sẽ thêm rằng nếu có thêm các điều kiện truy vấn thì chúng phải được thêm vào trong TỪ và trong THAM GIA TRÁI. Cái gì đó như: TỪ (SELECT * FROM Person ĐÂU Age = 32!) O LEFT JOIN (SELECT * FROM Person ĐÂU Age = 32!) B - nếu bạn muốn bỏ người là 32
Alain Zelink

1
@AlainZelink không phải là những "điều kiện truy vấn tiếp theo" tốt hơn nên được đưa vào danh sách điều kiện WHERE cuối cùng, để không đưa ra các truy vấn con - điều không cần thiết trong câu trả lời gốc @ axiac?
tarilabs

5
Giải pháp này đã làm việc; tuy nhiên, nó bắt đầu được báo cáo trong nhật ký truy vấn chậm khi thử với hơn 10.000 hàng chia sẻ cùng một ID. Đã tham gia vào cột được lập chỉ mục. Một trường hợp hiếm hoi, nhưng cho rằng nó đáng được đề cập.
chaseisabelle

50

Bạn có thể tham gia chống lại một truy vấn con kéo MAX(Group)Age. Phương thức này có thể di động trên hầu hết RDBMS.

SELECT t1.*
FROM yourTable t1
INNER JOIN
(
    SELECT `Group`, MAX(Age) AS max_age
    FROM yourTable
    GROUP BY `Group`
) t2
    ON t1.`Group` = t2.`Group` AND t1.Age = t2.max_age;

Michael, cảm ơn vì điều này - nhưng bạn có câu trả lời cho vấn đề trả lại nhiều hàng trên các mối quan hệ, theo nhận xét của Bohemian không?
Yarin

1
@Yarin Nếu có 2 hàng chẳng hạn Group = 2, Age = 20, truy vấn con sẽ trả về một trong số chúng, nhưng ONmệnh đề nối sẽ khớp với cả hai hàng, vì vậy bạn sẽ nhận được 2 hàng trở lại với cùng một nhóm / tuổi mặc dù các vals khác nhau cho các cột khác, hơn là một.
Michael Berkowski

Vì vậy, có phải chúng ta đang nói rằng không thể giới hạn kết quả cho mỗi nhóm trừ khi chúng ta đi theo lộ trình chỉ có Bohemians?
Yarin

@Yarin không phải là không thể, chỉ cần thêm công việc nếu có thêm các cột - có thể là một truy vấn con lồng nhau khác để lấy id liên kết tối đa cho mỗi cặp như nhóm / tuổi, sau đó tham gia vào đó để lấy phần còn lại của hàng dựa trên id.
Michael Berkowski

Đây phải là câu trả lời được chấp nhận (câu trả lời hiện được chấp nhận sẽ thất bại trên hầu hết các RDBMS khác và trên thực tế thậm chí sẽ thất bại trên nhiều phiên bản của MySQL).
Tim Biegeleisen

28

Giải pháp đơn giản của tôi cho SQLite (và có lẽ là MySQL):

SELECT *, MAX(age) FROM mytable GROUP BY `Group`;

Tuy nhiên, nó không hoạt động trong PostgreSQL và có thể một số nền tảng khác.

Trong PostgreSQL, bạn có thể sử dụng mệnh đề DISTINCT ON :

SELECT DISTINCT ON ("group") * FROM "mytable" ORDER BY "group", "age" DESC;

@Bohemian xin lỗi, tôi nhận được nó biết, đây là MySQL chỉ vì nó bao gồm các cột không tổng hợp
Cec

2
@IgorKulagin - Không hoạt động trong Postgres- Thông báo lỗi: cột "mytable.id" phải xuất hiện trong mệnh đề GROUP BY hoặc được sử dụng trong hàm tổng hợp
Yarin

13
Truy vấn MySQL có thể chỉ hoạt động tình cờ trong nhiều trường hợp. "CHỌN *" có thể trả về thông tin không tương ứng với MAX (tuổi). Câu trả lời này là sai. Đây có lẽ cũng là trường hợp đối với SQLite.
Albert Hendriks

2
Nhưng điều này phù hợp với trường hợp chúng ta cần chọn cột được nhóm và cột tối đa. Điều này không phù hợp với yêu cầu ở trên, nơi nó sẽ có kết quả ('Bob', 1, 42) nhưng kết quả mong đợi là ('Shawn', 1, 42)
Ram Babu S

1
Tốt cho postgres
Karol Gasienica

4

Sử dụng phương pháp xếp hạng.

SELECT @rn :=  CASE WHEN @prev_grp <> groupa THEN 1 ELSE @rn+1 END AS rn,  
   @prev_grp :=groupa,
   person,age,groupa  
FROM   users,(SELECT @rn := 0) r        
HAVING rn=1
ORDER  BY groupa,age DESC,person

sel - cần một số lời giải thích - tôi thậm chí chưa từng thấy :=trước đây - đó là gì?
Yarin

1
: = là toán tử gán. Bạn có thể đọc thêm trên dev.mysql.com/doc/refman/5.0/en/user-variables.html
sel

Tôi sẽ phải thâm nhập vào this- Tôi nghĩ câu trả lời overcomplicates kịch bản của chúng tôi, nhưng nhờ dạy tôi những điều mới mẻ ..
Yarin

3

Không chắc chắn nếu MySQL có chức năng row_number. Nếu vậy bạn có thể sử dụng nó để có được kết quả mong muốn. Trên SQL Server, bạn có thể làm một cái gì đó tương tự như:

CREATE TABLE p
(
 person NVARCHAR(10),
 gp INT,
 age INT
);
GO
INSERT  INTO p
VALUES  ('Bob', 1, 32);
INSERT  INTO p
VALUES  ('Jill', 1, 34);
INSERT  INTO p
VALUES  ('Shawn', 1, 42);
INSERT  INTO p
VALUES  ('Jake', 2, 29);
INSERT  INTO p
VALUES  ('Paul', 2, 36);
INSERT  INTO p
VALUES  ('Laura', 2, 39);
GO

SELECT  t.person, t.gp, t.age
FROM    (
         SELECT *,
                ROW_NUMBER() OVER (PARTITION BY gp ORDER BY age DESC) row
         FROM   p
        ) t
WHERE   t.row = 1;

1
Nó có, kể từ 8.0.
Ilja Everilä

2

giải pháp của axiac là những gì làm việc tốt nhất cho tôi cuối cùng. Tuy nhiên, tôi có một độ phức tạp bổ sung: "giá trị tối đa" được tính toán, xuất phát từ hai cột.

Hãy sử dụng cùng một ví dụ: Tôi muốn người già nhất trong mỗi nhóm. Nếu có những người bằng nhau, hãy lấy người cao nhất.

Tôi đã phải thực hiện tham gia bên trái hai lần để có hành vi này:

SELECT o1.* WHERE
    (SELECT o.*
    FROM `Persons` o
    LEFT JOIN `Persons` b
    ON o.Group = b.Group AND o.Age < b.Age
    WHERE b.Age is NULL) o1
LEFT JOIN
    (SELECT o.*
    FROM `Persons` o
    LEFT JOIN `Persons` b
    ON o.Group = b.Group AND o.Age < b.Age
    WHERE b.Age is NULL) o2
ON o1.Group = o2.Group AND o1.Height < o2.Height 
WHERE o2.Height is NULL;

Hi vọng điêu nay co ich! Tôi đoán nên có cách tốt hơn để làm điều này mặc dù ...


2

Giải pháp của tôi chỉ hoạt động nếu bạn chỉ cần truy xuất một cột, tuy nhiên đối với nhu cầu của tôi là giải pháp tốt nhất được tìm thấy về mặt hiệu suất (nó chỉ sử dụng một truy vấn duy nhất!):

SELECT SUBSTRING_INDEX(GROUP_CONCAT(column_x ORDER BY column_y),',',1) AS xyz,
   column_z
FROM table_name
GROUP BY column_z;

Nó sử dụng GROUP_CONCAT để tạo một danh sách concat theo thứ tự và sau đó tôi chuỗi con thành chỉ đầu tiên.


Có thể xác nhận rằng bạn có thể nhận được nhiều cột bằng cách sắp xếp trên cùng một khóa bên trong group_concat, nhưng cần phải viết một nhóm_concat / index / chuỗi con riêng biệt cho mỗi cột.
Rasika

Phần thưởng ở đây là bạn có thể thêm nhiều cột vào sắp xếp bên trong nhóm_concat và nó sẽ giải quyết các mối quan hệ một cách dễ dàng và chỉ đảm bảo một bản ghi cho mỗi nhóm. Cũng được thực hiện trên các giải pháp đơn giản và hiệu quả!
Rasika

2

Tôi có một giải pháp đơn giản bằng cách sử dụng WHERE IN

SELECT a.* FROM `mytable` AS a    
WHERE a.age IN( SELECT MAX(b.age) AS age FROM `mytable` AS b GROUP BY b.group )    
ORDER BY a.group ASC, a.person ASC

1

Sử dụng CTE - Biểu thức bảng chung:

WITH MyCTE(MaxPKID, SomeColumn1)
AS(
SELECT MAX(a.MyTablePKID) AS MaxPKID, a.SomeColumn1
FROM MyTable1 a
GROUP BY a.SomeColumn1
  )
SELECT b.MyTablePKID, b.SomeColumn1, b.SomeColumn2 MAX(b.NumEstado)
FROM MyTable1 b
INNER JOIN MyCTE c ON c.MaxPKID = b.MyTablePKID
GROUP BY b.MyTablePKID, b.SomeColumn1, b.SomeColumn2

--Note: MyTablePKID is the PrimaryKey of MyTable

1

Trong Oracle dưới đây truy vấn có thể cho kết quả mong muốn.

SELECT group,person,Age,
  ROWNUMBER() OVER (PARTITION BY group ORDER BY age desc ,person asc) as rankForEachGroup
  FROM tablename where rankForEachGroup=1

0
with CTE as 
(select Person, 
[Group], Age, RN= Row_Number() 
over(partition by [Group] 
order by Age desc) 
from yourtable)`


`select Person, Age from CTE where RN = 1`

0

Bạn cũng có thể thử

SELECT * FROM mytable WHERE age IN (SELECT MAX(age) FROM mytable GROUP BY `Group`) ;

1
Cảm ơn, mặc dù điều này trả về nhiều kỷ lục cho một độ tuổi khi có một chiếc cà vạt
Yarin

Ngoài ra, truy vấn này sẽ không chính xác trong trường hợp có một người 39 tuổi trong nhóm 1. Trong trường hợp đó, người đó cũng sẽ được chọn, mặc dù tuổi tối đa trong nhóm 1 cao hơn.
Joshua Richardson

0

Tôi sẽ không sử dụng Nhóm làm tên cột vì nó là từ dành riêng. Tuy nhiên, sau SQL sẽ hoạt động.

SELECT a.Person, a.Group, a.Age FROM [TABLE_NAME] a
INNER JOIN 
(
  SELECT `Group`, MAX(Age) AS oldest FROM [TABLE_NAME] 
  GROUP BY `Group`
) b ON a.Group = b.Group AND a.Age = b.oldest

Cảm ơn, mặc dù điều này trả về nhiều kỷ lục cho một độ tuổi khi có một chiếc cà vạt
Yarin

@Yarin làm thế nào để quyết định đó là người già chính xác nhất? Nhiều câu trả lời dường như là câu trả lời đúng nhất nếu không sử dụng giới hạn và thứ tự
Duncan

0

Phương pháp này có lợi ích là cho phép bạn xếp hạng theo một cột khác và không bỏ rác các dữ liệu khác. Nó khá hữu ích trong trường hợp bạn đang cố gắng liệt kê các đơn đặt hàng với một cột cho các mục, liệt kê các mục nặng nhất trước tiên.

Nguồn: http://dev.mysql.com/doc/refman/5.0/en/group-by-fifts.html#feft_group-concat

SELECT person, group,
    GROUP_CONCAT(
        DISTINCT age
        ORDER BY age DESC SEPARATOR ', follow up: '
    )
FROM sql_table
GROUP BY group;

0

đặt tên bảng là người

select O.*              -- > O for oldest table
from people O , people T
where O.grp = T.grp and 
O.Age = 
(select max(T.age) from people T where O.grp = T.grp
  group by T.grp)
group by O.grp; 

0

Nếu cần ID (và tất cả coulmns) từ mytable

SELECT
    *
FROM
    mytable
WHERE
    id NOT IN (
        SELECT
            A.id
        FROM
            mytable AS A
        JOIN mytable AS B ON A. GROUP = B. GROUP
        AND A.age < B.age
    )

0

Đây là cách tôi nhận được N hàng tối đa cho mỗi nhóm trong mysql

SELECT co.id, co.person, co.country
FROM person co
WHERE (
SELECT COUNT(*)
FROM person ci
WHERE  co.country = ci.country AND co.id < ci.id
) < 1
;

làm thế nào nó hoạt động:

  • tự tham gia vào bàn
  • các nhóm được thực hiện bởi co.country = ci.country
  • N phần tử trên mỗi nhóm được kiểm soát bởi ) < 13 phần tử -) <3
  • để có được tối đa hoặc tối thiểu phụ thuộc vào: co.id < ci.id
    • co.id <ci.id - tối đa
    • co.id> ci.id - phút

Ví dụ đầy đủ ở đây:

mysql chọn n giá trị tối đa cho mỗi nhóm

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.