Lấy bản ghi cuối cùng trong mỗi nhóm - MySQL


957

Có một bảng messageschứa dữ liệu như hình dưới đây:

Id   Name   Other_Columns
-------------------------
1    A       A_data_1
2    A       A_data_2
3    A       A_data_3
4    B       B_data_1
5    B       B_data_2
6    C       C_data_1

Nếu tôi chạy một truy vấn select * from messages group by name, tôi sẽ nhận được kết quả là:

1    A       A_data_1
4    B       B_data_1
6    C       C_data_1

Truy vấn nào sẽ trả về kết quả sau?

3    A       A_data_3
5    B       B_data_2
6    C       C_data_1

Đó là, hồ sơ cuối cùng trong mỗi nhóm nên được trả lại.

Hiện tại, đây là truy vấn mà tôi sử dụng:

SELECT
  *
FROM (SELECT
  *
FROM messages
ORDER BY id DESC) AS x
GROUP BY name

Nhưng điều này có vẻ không hiệu quả cao. Bất kỳ cách nào khác để đạt được kết quả tương tự?


2
xem câu trả lời được chấp nhận trong stackoverflow.com/questions/1379565/
Ấn


7
Tại sao bạn không thể thêm DESC, tức là chọn * từ nhóm tin nhắn theo tên DESC
Kim Prince


2
@KimPrince Có vẻ như câu trả lời bạn đang đề xuất không làm những gì được mong đợi! Tôi vừa thử phương pháp của bạn và nó đã lấy hàng ĐẦU TIÊN cho mỗi nhóm và đặt hàng DESC. Nó KHÔNG lấy hàng cuối cùng của mỗi nhóm
Ayrat

Câu trả lời:


969

MySQL 8.0 hiện hỗ trợ các chức năng cửa sổ, giống như hầu hết các triển khai SQL phổ biến. Với cú pháp tiêu chuẩn này, chúng tôi có thể viết các truy vấn lớn nhất cho mỗi nhóm:

WITH ranked_messages AS (
  SELECT m.*, ROW_NUMBER() OVER (PARTITION BY name ORDER BY id DESC) AS rn
  FROM messages AS m
)
SELECT * FROM ranked_messages WHERE rn = 1;

Dưới đây là câu trả lời ban đầu tôi đã viết cho câu hỏi này trong năm 2009:


Tôi viết giải pháp theo cách này:

SELECT m1.*
FROM messages m1 LEFT JOIN messages m2
 ON (m1.name = m2.name AND m1.id < m2.id)
WHERE m2.id IS NULL;

Về hiệu suất, một giải pháp này hay giải pháp khác có thể tốt hơn, tùy thuộc vào bản chất của dữ liệu của bạn. Vì vậy, bạn nên kiểm tra cả hai truy vấn và sử dụng truy vấn tốt hơn về hiệu suất được cung cấp cho cơ sở dữ liệu của bạn.

Ví dụ: tôi có một bản sao của kết xuất dữ liệu tháng 8 của StackOverflow . Tôi sẽ sử dụng nó để điểm chuẩn. Có 1.114.357 hàng trong Postsbảng. Điều này đang chạy trên MySQL 5.0.75 trên Macbook Pro 2.40GHz của tôi.

Tôi sẽ viết một truy vấn để tìm bài đăng gần đây nhất cho một ID người dùng nhất định (của tôi).

Đầu tiên sử dụng kỹ thuật được hiển thị bởi @Eric với GROUP BYtruy vấn con:

SELECT p1.postid
FROM Posts p1
INNER JOIN (SELECT pi.owneruserid, MAX(pi.postid) AS maxpostid
            FROM Posts pi GROUP BY pi.owneruserid) p2
  ON (p1.postid = p2.maxpostid)
WHERE p1.owneruserid = 20860;

1 row in set (1 min 17.89 sec)

Ngay cả EXPLAINphân tích mất hơn 16 giây:

+----+-------------+------------+--------+----------------------------+-------------+---------+--------------+---------+-------------+
| id | select_type | table      | type   | possible_keys              | key         | key_len | ref          | rows    | Extra       |
+----+-------------+------------+--------+----------------------------+-------------+---------+--------------+---------+-------------+
|  1 | PRIMARY     | <derived2> | ALL    | NULL                       | NULL        | NULL    | NULL         |   76756 |             | 
|  1 | PRIMARY     | p1         | eq_ref | PRIMARY,PostId,OwnerUserId | PRIMARY     | 8       | p2.maxpostid |       1 | Using where | 
|  2 | DERIVED     | pi         | index  | NULL                       | OwnerUserId | 8       | NULL         | 1151268 | Using index | 
+----+-------------+------------+--------+----------------------------+-------------+---------+--------------+---------+-------------+
3 rows in set (16.09 sec)

Bây giờ tạo ra kết quả truy vấn tương tự bằng kỹ thuật của tôi với LEFT JOIN:

SELECT p1.postid
FROM Posts p1 LEFT JOIN posts p2
  ON (p1.owneruserid = p2.owneruserid AND p1.postid < p2.postid)
WHERE p2.postid IS NULL AND p1.owneruserid = 20860;

1 row in set (0.28 sec)

Các EXPLAINchương trình phân tích rằng cả hai bảng có thể sử dụng các chỉ số của họ:

+----+-------------+-------+------+----------------------------+-------------+---------+-------+------+--------------------------------------+
| id | select_type | table | type | possible_keys              | key         | key_len | ref   | rows | Extra                                |
+----+-------------+-------+------+----------------------------+-------------+---------+-------+------+--------------------------------------+
|  1 | SIMPLE      | p1    | ref  | OwnerUserId                | OwnerUserId | 8       | const | 1384 | Using index                          | 
|  1 | SIMPLE      | p2    | ref  | PRIMARY,PostId,OwnerUserId | OwnerUserId | 8       | const | 1384 | Using where; Using index; Not exists | 
+----+-------------+-------+------+----------------------------+-------------+---------+-------+------+--------------------------------------+
2 rows in set (0.00 sec)

Đây là DDL cho Postsbảng của tôi :

CREATE TABLE `posts` (
  `PostId` bigint(20) unsigned NOT NULL auto_increment,
  `PostTypeId` bigint(20) unsigned NOT NULL,
  `AcceptedAnswerId` bigint(20) unsigned default NULL,
  `ParentId` bigint(20) unsigned default NULL,
  `CreationDate` datetime NOT NULL,
  `Score` int(11) NOT NULL default '0',
  `ViewCount` int(11) NOT NULL default '0',
  `Body` text NOT NULL,
  `OwnerUserId` bigint(20) unsigned NOT NULL,
  `OwnerDisplayName` varchar(40) default NULL,
  `LastEditorUserId` bigint(20) unsigned default NULL,
  `LastEditDate` datetime default NULL,
  `LastActivityDate` datetime default NULL,
  `Title` varchar(250) NOT NULL default '',
  `Tags` varchar(150) NOT NULL default '',
  `AnswerCount` int(11) NOT NULL default '0',
  `CommentCount` int(11) NOT NULL default '0',
  `FavoriteCount` int(11) NOT NULL default '0',
  `ClosedDate` datetime default NULL,
  PRIMARY KEY  (`PostId`),
  UNIQUE KEY `PostId` (`PostId`),
  KEY `PostTypeId` (`PostTypeId`),
  KEY `AcceptedAnswerId` (`AcceptedAnswerId`),
  KEY `OwnerUserId` (`OwnerUserId`),
  KEY `LastEditorUserId` (`LastEditorUserId`),
  KEY `ParentId` (`ParentId`),
  CONSTRAINT `posts_ibfk_1` FOREIGN KEY (`PostTypeId`) REFERENCES `posttypes` (`PostTypeId`)
) ENGINE=InnoDB;

8
Có thật không? Điều gì xảy ra nếu bạn có một tấn các mục? Ví dụ: nếu bạn đang làm việc với điều khiển phiên bản nội bộ, giả sử, và bạn có hàng tấn phiên bản cho mỗi tệp, kết quả tham gia đó sẽ rất lớn. Bạn đã bao giờ điểm chuẩn phương pháp truy vấn con với phương pháp này chưa? Tôi khá tò mò muốn biết ai sẽ thắng, nhưng không đủ tò mò để không hỏi bạn trước.
Eric

2
Đã làm một số thử nghiệm. Trên một bảng nhỏ (~ 300k bản ghi, ~ 190k nhóm, vì vậy không phải nhóm lớn hay bất cứ thứ gì), các truy vấn được buộc (mỗi nhóm 8 giây).
Eric

1
@BillKarwin: Xem meta.stackexchange.com/questions/123017 , đặc biệt là các bình luận bên dưới câu trả lời của Adam Rackis. Hãy cho tôi biết nếu bạn muốn đòi lại câu trả lời của bạn cho câu hỏi mới.
Robert Harvey

3
@Tim, không, <=sẽ không giúp ích nếu bạn có một cột không duy nhất. Bạn phải sử dụng một cột duy nhất như một tiebreaker.
Bill Karwin

2
Hiệu suất giảm theo cấp số nhân khi số lượng hàng tăng lên hoặc khi các nhóm trở nên lớn hơn. Ví dụ: một nhóm bao gồm 5 ngày sẽ mang lại 4 + 3 + 2 + 1 + 1 = 11 hàng thông qua nối trái trong đó một hàng được lọc cuối cùng. Hiệu suất của việc tham gia với các kết quả được nhóm là gần như tuyến tính. Các xét nghiệm của bạn trông thật thiếu sót.
Salman A

148

CẬP NHẬT: 2017-03-31, phiên bản 5.7.5 của MySQL đã khiến công tắc CHỈ_ULL_GROUP_BY được bật theo mặc định (do đó, các truy vấn GROUP BY không xác định đã bị vô hiệu hóa). Hơn nữa, họ đã cập nhật triển khai GROUP BY và giải pháp có thể không hoạt động như mong đợi nữa ngay cả với công tắc bị vô hiệu hóa. Một cần phải kiểm tra.

Giải pháp Bill Karwin của trên công trình tốt khi số mặt hàng trong nhóm là khá nhỏ, nhưng việc thực hiện các truy vấn trở nên xấu khi các nhóm khá lớn, kể từ khi giải pháp đòi hỏi về n*n/2 + n/2chỉ IS NULLso sánh.

Tôi đã thực hiện các thử nghiệm của mình trên bảng 18684446hàng InnoDB với 1182các nhóm. Bảng chứa các kiểm tra cho các kiểm tra chức năng và có (test_id, request_id)khóa chính. Vì vậy, test_idlà một nhóm và tôi đã tìm kiếm cuối cùng request_idcho mỗi nhóm test_id.

Giải pháp của Bill đã chạy được vài giờ trên dell e4310 của tôi và tôi không biết khi nào nó sẽ hoàn thành mặc dù nó hoạt động theo chỉ số bảo hiểm (do đó using indextrong EXPLAIN).

Tôi có một vài giải pháp khác dựa trên cùng một ý tưởng:

  • nếu chỉ số cơ bản là chỉ số BTREE (thường là trường hợp), (group_id, item_value)cặp lớn nhất là giá trị cuối cùng trong mỗi chỉ số group_id, đó là giá trị đầu tiên cho mỗi group_idnếu chúng ta đi qua chỉ số theo thứ tự giảm dần;
  • nếu chúng ta đọc các giá trị được bao phủ bởi một chỉ mục, các giá trị được đọc theo thứ tự của chỉ mục;
  • mỗi chỉ mục ngầm chứa các cột khóa chính được gắn vào đó (đó là khóa chính nằm trong chỉ mục bảo hiểm). Trong các giải pháp bên dưới tôi hoạt động trực tiếp trên khóa chính, trong trường hợp của bạn, bạn sẽ chỉ cần thêm các cột khóa chính trong kết quả.
  • trong nhiều trường hợp, việc thu thập các id hàng bắt buộc theo thứ tự được yêu cầu trong một truy vấn con sẽ rẻ hơn nhiều và tham gia vào kết quả của truy vấn con trên id. Vì đối với mỗi hàng trong kết quả truy vấn phụ, MySQL sẽ cần một lần tìm nạp dựa trên khóa chính, truy vấn phụ sẽ được đặt đầu tiên trong liên kết và các hàng sẽ được xuất theo thứ tự các id trong truy vấn phụ (nếu chúng tôi bỏ qua ORDER BY cho tham gia)

3 cách MySQL sử dụng chỉ mục là một bài viết tuyệt vời để hiểu một số chi tiết.

Giải pháp 1

Cái này cực kỳ nhanh, mất khoảng 0,8 giây trên các hàng 18M + của tôi:

SELECT test_id, MAX(request_id) AS request_id
FROM testresults
GROUP BY test_id DESC;

Nếu bạn muốn thay đổi thứ tự thành ASC, hãy đặt nó trong truy vấn con, chỉ trả lại id và sử dụng đó làm truy vấn con để nối với các cột còn lại:

SELECT test_id, request_id
FROM (
    SELECT test_id, MAX(request_id) AS request_id
    FROM testresults
    GROUP BY test_id DESC) as ids
ORDER BY test_id;

Cái này mất khoảng 1,2 giây trên dữ liệu của tôi.

Giải pháp 2

Đây là một giải pháp khác mất khoảng 19 giây cho bảng của tôi:

SELECT test_id, request_id
FROM testresults, (SELECT @group:=NULL) as init
WHERE IF(IFNULL(@group, -1)=@group:=test_id, 0, 1)
ORDER BY test_id DESC, request_id DESC

Nó trả về các bài kiểm tra theo thứ tự giảm dần là tốt. Nó chậm hơn nhiều vì nó thực hiện quét chỉ mục đầy đủ nhưng nó ở đây để cung cấp cho bạn ý tưởng làm thế nào để xuất N hàng tối đa cho mỗi nhóm.

Nhược điểm của truy vấn là kết quả của nó không thể được lưu trong bộ đệm truy vấn.


Vui lòng liên kết đến một bãi chứa các bảng của bạn để mọi người có thể kiểm tra nó trên nền tảng của họ.
Pacerier 3/2/2015

3
Giải pháp 1 không thể hoạt động, bạn không thể chọn request_id mà không có điều khoản trong nhóm theo mệnh đề,
giò

2
@ giò, đây là câu trả lời là 5 tuổi. Cho đến khi MySQL 5.7.5 ONLY_FULL_GROUP_BY bị tắt theo mặc định và giải pháp này đã hoạt động ngoài hộp dev.mysql.com/doc/relnotes/mysql/5.7/vi/ . Bây giờ tôi không chắc liệu giải pháp có còn hoạt động khi bạn tắt chế độ không, vì việc triển khai GROUP BY đã bị thay đổi.
newtover

Nếu bạn muốn ASC trong giải pháp đầu tiên, nó có hoạt động không nếu bạn chuyển MAX thành MIN?
Jin

@JinIzzraeel, bạn có MIN theo mặc định ở đầu mỗi nhóm (đó là thứ tự của chỉ số bao phủ): SELECT test_id, request_id FROM testresults GROUP BY test_id;sẽ trả về request_id tối thiểu cho mỗi test_id.
newtover

102

Sử dụng truy vấn con của bạn để trả về nhóm chính xác, bởi vì bạn đang ở giữa chừng.

Thử cái này:

select
    a.*
from
    messages a
    inner join 
        (select name, max(id) as maxid from messages group by name) as b on
        a.id = b.maxid

Nếu đó không phải là idbạn muốn tối đa:

select
    a.*
from
    messages a
    inner join 
        (select name, max(other_col) as other_col 
         from messages group by name) as b on
        a.name = b.name
        and a.other_col = b.other_col

Bằng cách này, bạn tránh các truy vấn con tương ứng và / hoặc đặt hàng trong các truy vấn con của bạn, có xu hướng rất chậm / không hiệu quả.


1
Lưu ý một lời cảnh báo cho giải pháp với other_col: nếu cột đó không phải là duy nhất, bạn có thể lấy lại nhiều bản ghi giống nhau name, nếu chúng liên kết max(other_col). Tôi tìm thấy bài đăng này mô tả một giải pháp cho nhu cầu của tôi, nơi tôi cần chính xác một bản ghi cho mỗi name.
Eric Simonton

Trong một số tình huống, bạn chỉ có thể sử dụng giải pháp này nhưng không được chấp nhận.
tom10271

Theo kinh nghiệm của tôi, nó đang nhóm toàn bộ bảng thông báo chết tiệt có xu hướng chậm / không hiệu quả! Nói cách khác, lưu ý rằng truy vấn con yêu cầu quét toàn bộ bảng thực hiện một nhóm trên đó để khởi động ... trừ khi trình tối ưu hóa của bạn đang làm điều gì đó mà tôi không làm. Vì vậy, giải pháp này phụ thuộc rất nhiều vào việc giữ toàn bộ bảng trong bộ nhớ.
Timo

Những người sẽ được hưởng lợi từ INDEX(name, id)INDEX(name, other_col)
Rick James

55

Tôi đã đến một giải pháp khác, đó là lấy ID cho bài đăng cuối cùng trong mỗi nhóm, sau đó chọn từ bảng thông báo sử dụng kết quả từ truy vấn đầu tiên làm đối số cho WHERE x INcấu trúc:

SELECT id, name, other_columns
FROM messages
WHERE id IN (
    SELECT MAX(id)
    FROM messages
    GROUP BY name
);

Tôi không biết làm thế nào điều này thực hiện so với một số giải pháp khác, nhưng nó hoạt động ngoạn mục cho bảng của tôi với hơn 3 triệu hàng. (Thực hiện 4 giây với hơn 1200 kết quả)

Điều này sẽ hoạt động cả trên MySQL và SQL Server.


Chỉ cần chắc chắn rằng bạn có một chỉ mục trên (tên, id).
Samuel Åslund

1
Tốt hơn nhiều là tự tham gia
anwerj

Tôi đã học được điều gì đó từ bạn đó là một công việc tốt và truy vấn này nhanh hơn
Humphrey

33

Giải pháp bằng liên kết truy vấn phụ

select * from messages where id in
(select max(id) from messages group by Name)

Giải pháp bằng cách tham gia liên kết điều kiện fiddle

select m1.* from messages m1 
left outer join messages m2 
on ( m1.id<m2.id and m1.name=m2.name )
where m2.id is null

Lý do cho bài viết này là chỉ cung cấp liên kết fiddle. SQL tương tự đã được cung cấp trong các câu trả lời khác.


1
@AlexanderSuraphel mysql5.5 hiện không có sẵn trong fiddle, liên kết fiddle đã được tạo bằng cách đó. Bây giờ một ngày fiddle hỗ trợ mysql5.6, tôi đã thay đổi cơ sở dữ liệu thành mysql 5.6 và tôi có thể xây dựng lược đồ và chạy sql.
Vipin

8

Một cách tiếp cận với tốc độ đáng kể là như sau.

SELECT * 
FROM messages a
WHERE Id = (SELECT MAX(Id) FROM messages WHERE a.Name = Name)

Kết quả

Id  Name    Other_Columns
3   A   A_data_3
5   B   B_data_2
6   C   C_data_1

Giả định idnày được ra lệnh theo cách bạn cần. Trong trường hợp chung, một số cột khác là cần thiết.
Rick James

6

Đây là hai gợi ý. Đầu tiên, nếu mysql hỗ trợ ROW_NUMBER (), thì rất đơn giản:

WITH Ranked AS (
  SELECT Id, Name, OtherColumns,
    ROW_NUMBER() OVER (
      PARTITION BY Name
      ORDER BY Id DESC
    ) AS rk
  FROM messages
)
  SELECT Id, Name, OtherColumns
  FROM messages
  WHERE rk = 1;

Tôi giả sử "cuối cùng" bạn có nghĩa là cuối cùng theo thứ tự Id. Nếu không, thay đổi mệnh đề ORDER BY của cửa sổ ROW_NUMBER () tương ứng. Nếu ROW_NUMBER () không khả dụng, đây là một giải pháp khác:

Thứ hai, nếu không, đây thường là một cách tốt để tiến hành:

SELECT
  Id, Name, OtherColumns
FROM messages
WHERE NOT EXISTS (
  SELECT * FROM messages as M2
  WHERE M2.Name = messages.Name
  AND M2.Id > messages.Id
)

Nói cách khác, chọn các tin nhắn không có tin nhắn Id sau này có cùng Tên.


8
MySQL không hỗ trợ ROW_NUMBER () hoặc CTE.
Bill Karwin

1
MySQL 8.0 (và MariaDB 10.2) hiện hỗ trợ ROW_NUMBER()và CTE.
Rick James

6

Tôi chưa thử nghiệm với DB lớn nhưng tôi nghĩ rằng điều này có thể nhanh hơn so với việc tham gia các bảng:

SELECT *, Max(Id) FROM messages GROUP BY Name

14
Điều này trả về dữ liệu tùy ý. Nói cách khác, các cột được trả về có thể không xuất phát từ bản ghi với MAX (Id).
làm hại

Hữu ích khi chọn Id tối đa từ một tập hợp bản ghi với điều kiện WHERE: "CHỌN Max (Id) TỪ Prod WHERE Pn = '" + Pn + "'" Nó trả về Id tối đa từ một tập hợp các bản ghi có cùng Pn.In c # sử dụng reader.GetString (0) để nhận kết quả
Nicola

5

Đây là một cách khác để có được bản ghi liên quan cuối cùng bằng cách sử dụng GROUP_CONCATtheo thứ tự và SUBSTRING_INDEXchọn một trong các bản ghi từ danh sách

SELECT 
  `Id`,
  `Name`,
  SUBSTRING_INDEX(
    GROUP_CONCAT(
      `Other_Columns` 
      ORDER BY `Id` DESC 
      SEPARATOR '||'
    ),
    '||',
    1
  ) Other_Columns 
FROM
  messages 
GROUP BY `Name` 

Truy vấn trên sẽ nhóm tất cả những Other_Columnsngười trong cùng một Namenhóm và sử dụng ORDER BY id DESCsẽ tham gia tất cả Other_Columnstrong một nhóm cụ thể theo thứ tự giảm dần với dấu phân cách được cung cấp trong trường hợp tôi đã sử dụng ||, sử dụng SUBSTRING_INDEXtrong danh sách này sẽ chọn nhóm đầu tiên

Trình diễn Fiddle


Hãy lưu ý rằng group_concat_max_lengiới hạn số lượng hàng bạn có thể xử lý.
Rick James

5

Rõ ràng có rất nhiều cách khác nhau để có được kết quả giống nhau, câu hỏi của bạn dường như là cách hiệu quả để có được kết quả cuối cùng trong mỗi nhóm trong MySQL. Nếu bạn đang làm việc với lượng dữ liệu khổng lồ và giả sử rằng bạn đang sử dụng InnoDB với ngay cả các phiên bản mới nhất của MySQL (như 5.7,21 và 8,0,4-rc) thì có thể không có cách nào hiệu quả để làm việc này.

Đôi khi chúng ta cần làm điều này với các bảng có hơn 60 triệu hàng.

Đối với những ví dụ này, tôi sẽ sử dụng dữ liệu chỉ với khoảng 1,5 triệu hàng trong đó các truy vấn sẽ cần tìm kết quả cho tất cả các nhóm trong dữ liệu. Trong các trường hợp thực tế của chúng tôi, chúng tôi thường sẽ cần phải trả lại dữ liệu từ khoảng 2.000 nhóm (theo giả thuyết sẽ không yêu cầu kiểm tra rất nhiều dữ liệu).

Tôi sẽ sử dụng các bảng sau:

CREATE TABLE temperature(
  id INT UNSIGNED NOT NULL AUTO_INCREMENT, 
  groupID INT UNSIGNED NOT NULL, 
  recordedTimestamp TIMESTAMP NOT NULL, 
  recordedValue INT NOT NULL,
  INDEX groupIndex(groupID, recordedTimestamp), 
  PRIMARY KEY (id)
);

CREATE TEMPORARY TABLE selected_group(id INT UNSIGNED NOT NULL, PRIMARY KEY(id)); 

Bảng nhiệt độ được điền với khoảng 1,5 triệu bản ghi ngẫu nhiên và với 100 nhóm khác nhau. Nhóm được chọn được điền với 100 nhóm đó (trong trường hợp của chúng tôi, tỷ lệ này thường sẽ dưới 20% cho tất cả các nhóm).

Vì dữ liệu này là ngẫu nhiên, điều đó có nghĩa là nhiều hàng có thể có cùng các bản ghi được ghi lại. Điều chúng tôi muốn là có được một danh sách tất cả các nhóm được chọn theo thứ tự của nhómID với bản ghi cuối cùng cho mỗi nhóm và nếu cùng một nhóm có nhiều hơn một hàng khớp như vậy thì id khớp cuối cùng của các hàng đó.

Nếu giả thuyết MySQL có hàm () cuối cùng trả về các giá trị từ hàng cuối cùng trong mệnh đề ORDER BY đặc biệt thì chúng ta có thể thực hiện đơn giản:

SELECT 
  last(t1.id) AS id, 
  t1.groupID, 
  last(t1.recordedTimestamp) AS recordedTimestamp, 
  last(t1.recordedValue) AS recordedValue
FROM selected_group g
INNER JOIN temperature t1 ON t1.groupID = g.id
ORDER BY t1.recordedTimestamp, t1.id
GROUP BY t1.groupID;

trong đó chỉ cần kiểm tra một vài hàng trong trường hợp này vì nó không sử dụng bất kỳ hàm GROUP BY bình thường nào. Điều này sẽ thực hiện trong 0 giây và do đó có hiệu quả cao. Lưu ý rằng thông thường trong MySQL, chúng ta sẽ thấy mệnh đề ORDER BY theo mệnh đề GROUP BY tuy nhiên mệnh đề ORDER BY này được sử dụng để xác định ORDER cho hàm () cuối cùng, nếu nó nằm sau GROUP BY thì nó sẽ ra lệnh NHÓM. Nếu không có mệnh đề GROUP BY thì các giá trị cuối cùng sẽ giống nhau trong tất cả các hàng được trả về.

Tuy nhiên, MySQL không có điều này vì vậy chúng ta hãy xem xét các ý tưởng khác nhau về những gì nó có và chứng minh rằng không có cách nào trong số này là hiệu quả.

ví dụ 1

SELECT t1.id, t1.groupID, t1.recordedTimestamp, t1.recordedValue
FROM selected_group g
INNER JOIN temperature t1 ON t1.id = (
  SELECT t2.id
  FROM temperature t2 
  WHERE t2.groupID = g.id
  ORDER BY t2.recordedTimestamp DESC, t2.id DESC
  LIMIT 1
);

Điều này đã kiểm tra 3.009.254 hàng và mất ~ 0.859 giây vào ngày 5.7,21 và lâu hơn một chút trên 8.0.4-RC

Ví dụ 2

SELECT t1.id, t1.groupID, t1.recordedTimestamp, t1.recordedValue 
FROM temperature t1
INNER JOIN ( 
  SELECT max(t2.id) AS id   
  FROM temperature t2
  INNER JOIN (
    SELECT t3.groupID, max(t3.recordedTimestamp) AS recordedTimestamp
    FROM selected_group g
    INNER JOIN temperature t3 ON t3.groupID = g.id
    GROUP BY t3.groupID
  ) t4 ON t4.groupID = t2.groupID AND t4.recordedTimestamp = t2.recordedTimestamp
  GROUP BY t2.groupID
) t5 ON t5.id = t1.id;

Điều này đã kiểm tra 1,505.331 hàng và mất ~ 1,25 giây vào ngày 5.7,21 và lâu hơn một chút trên 8.0.4-rc

Ví dụ 3

SELECT t1.id, t1.groupID, t1.recordedTimestamp, t1.recordedValue 
FROM temperature t1
WHERE t1.id IN ( 
  SELECT max(t2.id) AS id   
  FROM temperature t2
  INNER JOIN (
    SELECT t3.groupID, max(t3.recordedTimestamp) AS recordedTimestamp
    FROM selected_group g
    INNER JOIN temperature t3 ON t3.groupID = g.id
    GROUP BY t3.groupID
  ) t4 ON t4.groupID = t2.groupID AND t4.recordedTimestamp = t2.recordedTimestamp
  GROUP BY t2.groupID
)
ORDER BY t1.groupID;

Điều này đã kiểm tra 3.009.685 hàng và mất ~ 1,95 giây vào ngày 5.7,21 và lâu hơn một chút trên 8.0.4-RC

Ví dụ 4

SELECT t1.id, t1.groupID, t1.recordedTimestamp, t1.recordedValue
FROM selected_group g
INNER JOIN temperature t1 ON t1.id = (
  SELECT max(t2.id)
  FROM temperature t2 
  WHERE t2.groupID = g.id AND t2.recordedTimestamp = (
      SELECT max(t3.recordedTimestamp)
      FROM temperature t3 
      WHERE t3.groupID = g.id
    )
);

Điều này đã kiểm tra 6.137.810 hàng và mất ~ 2,2 giây vào ngày 5.7,21 và lâu hơn một chút trên 8.0.4-RC

Ví dụ 5

SELECT t1.id, t1.groupID, t1.recordedTimestamp, t1.recordedValue
FROM (
  SELECT 
    t2.id, 
    t2.groupID, 
    t2.recordedTimestamp, 
    t2.recordedValue, 
    row_number() OVER (
      PARTITION BY t2.groupID ORDER BY t2.recordedTimestamp DESC, t2.id DESC
    ) AS rowNumber
  FROM selected_group g 
  INNER JOIN temperature t2 ON t2.groupID = g.id
) t1 WHERE t1.rowNumber = 1;

Điều này đã kiểm tra 6.017.808 hàng và mất ~ 4.2 giây trên 8.0.4-rc

Ví dụ 6

SELECT t1.id, t1.groupID, t1.recordedTimestamp, t1.recordedValue 
FROM (
  SELECT 
    last_value(t2.id) OVER w AS id, 
    t2.groupID, 
    last_value(t2.recordedTimestamp) OVER w AS recordedTimestamp, 
    last_value(t2.recordedValue) OVER w AS recordedValue
  FROM selected_group g
  INNER JOIN temperature t2 ON t2.groupID = g.id
  WINDOW w AS (
    PARTITION BY t2.groupID 
    ORDER BY t2.recordedTimestamp, t2.id 
    RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
  )
) t1
GROUP BY t1.groupID;

Điều này đã kiểm tra 6.017.908 hàng và mất ~ 17,5 giây trên 8.0.4-RC

Ví dụ 7

SELECT t1.id, t1.groupID, t1.recordedTimestamp, t1.recordedValue 
FROM selected_group g
INNER JOIN temperature t1 ON t1.groupID = g.id
LEFT JOIN temperature t2 
  ON t2.groupID = g.id 
  AND (
    t2.recordedTimestamp > t1.recordedTimestamp 
    OR (t2.recordedTimestamp = t1.recordedTimestamp AND t2.id > t1.id)
  )
WHERE t2.id IS NULL
ORDER BY t1.groupID;

Cái này đã mất mãi mãi nên tôi phải giết nó.


Đây là một vấn đề khác nhau. Và giải pháp là một truy vấn UNION ALL rất lớn.
Paul Spiegel

@PaulSpiegel Tôi đoán bạn đang nói đùa về UNION ALL khổng lồ. Bên cạnh thực tế là người ta sẽ cần phải biết trước tất cả các nhóm được chọn và với 2.000 nhóm được chọn sẽ là một truy vấn cực kỳ lớn, nó sẽ thực hiện thậm chí còn tệ hơn ví dụ nhanh nhất ở trên, vì vậy, đó sẽ không phải là một giải pháp.
Yoseph

Tôi hoàn toàn nghiêm túc. Tôi đã thử nghiệm điều đó trong quá khứ với một vài trăm nhóm. Khi bạn cần xử lý các mối quan hệ trong các nhóm lớn, UNION ALL là cách duy nhất trong MySQL để buộc một kế hoạch thực hiện tối ưu. SELECT DISTINCT(groupID)là nhanh và sẽ cung cấp cho bạn tất cả dữ liệu mà bạn cần để xây dựng một truy vấn như vậy. Bạn sẽ ổn với kích thước truy vấn miễn là nó không vượt quá max_allowed_packet, mặc định là 4MB trong MySQL 5.7.
Paul Spiegel

5

chúng tôi sẽ xem xét cách bạn có thể sử dụng MySQL để nhận bản ghi cuối cùng trong Nhóm theo bản ghi. Ví dụ nếu bạn có tập kết quả này của bài viết.

id category_id post_title

1 1 Title 1

2 1 Title 2

3 1 Title 3

4 2 Title 4

5 2 Title 5

6 3 Title 6

Tôi muốn có thể nhận được bài đăng cuối cùng trong mỗi danh mục là Tiêu đề 3, Tiêu đề 5 và Tiêu đề 6. Để có được các bài đăng theo danh mục, bạn sẽ sử dụng Nhóm MySQL theo bàn phím.

select * from posts group by category_id

Nhưng kết quả chúng tôi nhận được từ truy vấn này là.

id category_id post_title

1 1 Title 1

4 2 Title 4

6 3 Title 6

Nhóm theo sẽ luôn trả về bản ghi đầu tiên trong nhóm trên tập kết quả.

SELECT id, category_id, post_title FROM posts WHERE id IN ( SELECT MAX(id) FROM posts GROUP BY category_id );

Điều này sẽ trả về các bài đăng có ID cao nhất trong mỗi nhóm.

id category_id post_title

3 1 Title 3

5 2 Title 5

6 3 Title 6

Tham khảo Bấm vào đây


4
SELECT 
  column1,
  column2 
FROM
  table_name 
WHERE id IN 
  (SELECT 
    MAX(id) 
  FROM
    table_name 
  GROUP BY column1) 
ORDER BY column1 ;

Bạn có thể giải thích một chút về câu trả lời của bạn? Tại sao truy vấn của bạn thích hợp hơn với truy vấn ban đầu của Vijays?
11:30

4

Đây là giải pháp của tôi:

SELECT 
  DISTINCT NAME,
  MAX(MESSAGES) OVER(PARTITION BY NAME) MESSAGES 
FROM MESSAGE;

Điều này không trả về tin nhắn mới nhất cho mỗi tên. Và nó chỉ là một phiên bản quá phức tạp của SELECT NAME, MAX(MESSAGES) MESSAGES FROM MESSAGE GROUP BY NAME.
Paul Spiegel

Hơn nữa, công thức này là không hiệu quả.
Rick James

3

Thử cái này:

SELECT jos_categories.title AS name,
       joined .catid,
       joined .title,
       joined .introtext
FROM   jos_categories
       INNER JOIN (SELECT *
                   FROM   (SELECT `title`,
                                  catid,
                                  `created`,
                                  introtext
                           FROM   `jos_content`
                           WHERE  `sectionid` = 6
                           ORDER  BY `id` DESC) AS yes
                   GROUP  BY `yes`.`catid` DESC
                   ORDER  BY `yes`.`created` DESC) AS joined
         ON( joined.catid = jos_categories.id )  

3

Xin chào @Vijay Dev nếu thông báo bảng của bạn chứa Id là khóa chính tăng tự động sau đó để tìm nạp cơ sở bản ghi mới nhất trên khóa chính mà truy vấn của bạn sẽ đọc như dưới đây:

SELECT m1.* FROM messages m1 INNER JOIN (SELECT max(Id) as lastmsgId FROM messages GROUP BY Name) m2 ON m1.Id=m2.lastmsgId

Đây là cái nhanh nhất tôi tìm thấy
CORSAIR

3

Bạn có thể xem từ đây là tốt.

http://sqlfiddle.com/#!9/ef42b/9

GIẢI PHÁP ĐẦU TIÊN

SELECT d1.ID,Name,City FROM Demo_User d1
INNER JOIN
(SELECT MAX(ID) AS ID FROM Demo_User GROUP By NAME) AS P ON (d1.ID=P.ID);

GIẢI PHÁP THỨ HAI

SELECT * FROM (SELECT * FROM Demo_User ORDER BY ID DESC) AS T GROUP BY NAME ;

3
SELECT * FROM table_name WHERE primary_key IN (SELECT MAX(primary_key) FROM table_name GROUP BY column_name )

3

**

Xin chào, truy vấn này có thể giúp:

**

SELECT 
  *
FROM 
  message 

WHERE 
  `Id` IN (
    SELECT 
      MAX(`Id`) 
    FROM 
      message 
    GROUP BY 
      `Name`
  ) 
ORDER BY 
   `Id` DESC

2

Có cách nào chúng ta có thể sử dụng phương pháp này để xóa các bản sao trong một bảng không? Tập kết quả về cơ bản là một tập hợp các bản ghi duy nhất, vậy nếu chúng ta có thể xóa tất cả các bản ghi không có trong tập kết quả, chúng ta có thực sự không có bản sao không? Tôi đã thử điều này nhưng myQuery đã đưa ra một lỗi 1093.

DELETE FROM messages WHERE id NOT IN
 (SELECT m1.id  
 FROM messages m1 LEFT JOIN messages m2  
 ON (m1.name = m2.name AND m1.id < m2.id)  
 WHERE m2.id IS NULL)

Có cách nào để có thể lưu đầu ra vào một biến tạm thời sau đó xóa khỏi KHÔNG IN (biến temp) không? @Bill cảm ơn vì một giải pháp rất hữu ích.

EDIT: Hãy nghĩ rằng tôi đã tìm thấy giải pháp:

DROP TABLE IF EXISTS UniqueIDs; 
CREATE Temporary table UniqueIDs (id Int(11)); 

INSERT INTO UniqueIDs 
    (SELECT T1.ID FROM Table T1 LEFT JOIN Table T2 ON 
    (T1.Field1 = T2.Field1 AND T1.Field2 = T2.Field2 #Comparison Fields  
    AND T1.ID < T2.ID) 
    WHERE T2.ID IS NULL); 

DELETE FROM Table WHERE id NOT IN (SELECT ID FROM UniqueIDs);

2

Các truy vấn dưới đây sẽ hoạt động tốt theo câu hỏi của bạn.

SELECT M1.* 
FROM MESSAGES M1,
(
 SELECT SUBSTR(Others_data,1,2),MAX(Others_data) AS Max_Others_data
 FROM MESSAGES
 GROUP BY 1
) M2
WHERE M1.Others_data = M2.Max_Others_data
ORDER BY Others_data;

2

Nếu bạn muốn hàng cuối cùng cho mỗi hàng Name, thì bạn có thể đưa ra một số hàng cho mỗi nhóm hàng theo Namethứ tự và theo Idthứ tự giảm dần.

TRUY VẤN

SELECT t1.Id, 
       t1.Name, 
       t1.Other_Columns
FROM 
(
     SELECT Id, 
            Name, 
            Other_Columns,
    (
        CASE Name WHEN @curA 
        THEN @curRow := @curRow + 1 
        ELSE @curRow := 1 AND @curA := Name END 
    ) + 1 AS rn 
    FROM messages t, 
    (SELECT @curRow := 0, @curA := '') r 
    ORDER BY Name,Id DESC 
)t1
WHERE t1.rn = 1
ORDER BY t1.Id;

Câu đố SQL


2

Còn cái này thì sao:

SELECT DISTINCT ON (name) *
FROM messages
ORDER BY name, id DESC;

Tôi đã có vấn đề tương tự (trên postgresql khó khăn) và trên bảng hồ sơ 1M. Giải pháp này mất 1.7 giây so với 44 giây được tạo bởi LEFT THAM GIA. Trong trường hợp của tôi, tôi đã phải lọc sửa lỗi trường tên của bạn theo các giá trị NULL, dẫn đến hiệu suất thậm chí tốt hơn 0,2 giây


1

Nếu hiệu suất thực sự là mối quan tâm của bạn, bạn có thể giới thiệu một cột mới trên bảng có tên IsLastInGroup loại BIT.

Đặt nó thành true trên các cột cuối cùng và duy trì nó với mỗi lần chèn / cập nhật / xóa hàng. Bài viết sẽ chậm hơn, nhưng bạn sẽ có lợi khi đọc. Nó phụ thuộc vào trường hợp sử dụng của bạn và tôi chỉ khuyên bạn nên tập trung vào việc đọc tập trung.

Vì vậy, truy vấn của bạn sẽ trông như:

SELECT * FROM Messages WHERE IsLastInGroup = 1

Một số bảng trong Moodle có một cột cờ như thế này.
Lawrence


0

Bạn có thể nhóm bằng cách đếm và cũng có thể nhận được mục cuối cùng của nhóm như:

SELECT 
    user,
    COUNT(user) AS count,
    MAX(id) as last
FROM request 
GROUP BY user

0

Hy vọng bên dưới truy vấn của Oracle có thể giúp:

WITH Temp_table AS
(
    Select id, name, othercolumns, ROW_NUMBER() over (PARTITION BY name ORDER BY ID 
    desc)as rank from messages
)
Select id, name,othercolumns from Temp_table where rank=1

0

Cách tiếp cận khác :

Tìm phần thích hợp với m2_price tối đa trong mỗi chương trình (n thuộc tính trong 1 chương trình):

select * from properties p
join (
    select max(m2_price) as max_price 
    from properties 
    group by program_id
) p2 on (p.program_id = p2.program_id)
having p.m2_price = max_price
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.