Mysql分組查詢每組最新一條數(shù)據(jù)的三種實(shí)現(xiàn)方法
前言
在寫報表功能時遇到一個需要根據(jù)用戶id分組查詢最新一條錢包明細(xì)數(shù)據(jù)的需求,在寫sql測試時遇到一個有趣的問題,開始使用子查詢根據(jù)時間倒序+group by customer_id發(fā)現(xiàn)查詢出來的數(shù)據(jù)一直都是最舊的一條,而不是我需要的最新一條數(shù)據(jù)我明明已經(jīng)倒序排了,后來總結(jié)出了三種解決方案如下。
注意事項(xiàng)
- 數(shù)據(jù)庫版本 Mysql5.7+
- 執(zhí)行 GROUP BY 語句的時候出現(xiàn) sql_mode=only_full_group_by 解決方法(這里是Mysql8的解決方案,Mysql5.7也差不多自行百度即可)
1、執(zhí)行 select @@sql_mode; 查看sql模式
select @@sql_mode;

2、將sql_mode中的only_full_group_by模式剔除 重新設(shè)置sql_mode值,如果是使用JDBC連接需要重啟項(xiàng)目才能生效。
set global sql_mode='STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION'; set session sql_mode='STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION';
準(zhǔn)備SQL
這里模擬一個sql
DROP TABLE IF EXISTS `customer_wallet_detail`; CREATE TABLE `customer_wallet_detail` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `customer_id` bigint(20) NULL DEFAULT NULL COMMENT '用戶ID', `happen_amount` varchar(15) NULL DEFAULT '0' COMMENT '發(fā)生金額 帶'-'號的代表扣款', `balance_amount` varchar(15) NULL DEFAULT '0' COMMENT '可用余額', `create_time` bigint(20) NULL DEFAULT NULL COMMENT '發(fā)生時間', PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB COMMENT = '用戶錢包明細(xì)' ; INSERT INTO `test`.`customer_wallet_detail`(`id`, `customer_id`, `happen_amount`, `balance_amount`, `happen_time`) VALUES (1, 1, '100', '100', 1670300656630); INSERT INTO `test`.`customer_wallet_detail`(`id`, `customer_id`, `happen_amount`, `balance_amount`, `happen_time`) VALUES (2, 1, '-10', '90', 1670300656640); INSERT INTO `test`.`customer_wallet_detail`(`id`, `customer_id`, `happen_amount`, `balance_amount`, `happen_time`) VALUES (3, 1, '5', '95', 1670300656650); INSERT INTO `test`.`customer_wallet_detail`(`id`, `customer_id`, `happen_amount`, `balance_amount`, `happen_time`) VALUES (4, 3, '998', '998', 1670300656660); INSERT INTO `test`.`customer_wallet_detail`(`id`, `customer_id`, `happen_amount`, `balance_amount`, `happen_time`) VALUES (5, 3, '-100', '898', 1670300656670); INSERT INTO `test`.`customer_wallet_detail`(`id`, `customer_id`, `happen_amount`, `balance_amount`, `happen_time`) VALUES (6, 3, '-98', '800', 1670300656680); INSERT INTO `test`.`customer_wallet_detail`(`id`, `customer_id`, `happen_amount`, `balance_amount`, `happen_time`) VALUES (7, 2, '666', '666', 1670300656690); INSERT INTO `test`.`customer_wallet_detail`(`id`, `customer_id`, `happen_amount`, `balance_amount`, `happen_time`) VALUES (8, 2, '-66', '600', 1670300656695); INSERT INTO `test`.`customer_wallet_detail`(`id`, `customer_id`, `happen_amount`, `balance_amount`, `happen_time`) VALUES (9, 2, '-600', '0', 1670300656699);

錯誤查詢
SELECT * FROM ( SELECT * FROM customer_wallet_detail ORDER BY create_time DESC ) t1 GROUP BY t1.customer_id;

錯誤原因
在mysql5.7以及之后的版本,如果GROUP BY的子查詢中包含ORDER BY,但是 GROUP BY 不與 LIMIT 配合使用,ORDER BY會被忽略掉,所以子查詢在 GROUP BY 時排序不會生效,可能是因?yàn)樽硬樵兇蠖鄶?shù)是作為一個結(jié)果給主查詢使用,所以子查詢不需要排序。
方法一
鑒于以上的原因我們可以添加上 LIMIT 條件來實(shí)現(xiàn)功能。
PS:這個LIMIT的數(shù)量可以先自行 COUNT 出你要遍歷的數(shù)據(jù)條數(shù)(這個數(shù)據(jù)條數(shù)是所有滿足查詢條件的數(shù)據(jù)合,我這里共9條數(shù)據(jù))
SELECT * FROM ( SELECT * FROM customer_wallet_detail ORDER BY create_time DESC ) t1 GROUP BY t1.customer_id;

方法二(適用于自增ID和創(chuàng)建時間排序一致)
方法一需要先 COUNT 查詢?nèi)缓髮⒉樵兘Y(jié)果設(shè)置到 LIMIT 條件中比較麻煩,這里還可以使用 MAX() 函數(shù)來實(shí)現(xiàn)該功能。
PS:因?yàn)槲疫@里的業(yè)務(wù)數(shù)據(jù)是有序插入的,使用主鍵自增id和create_time結(jié)果是一樣的而且使用id查詢效率更高,如果沒有唯一且有序的id可以替代create_time那么就用方案一,不能直接使用 SELECT id,MAX(create_time) 這種操作來獲取最新一條數(shù)據(jù)id原因在總結(jié)中有詳細(xì)描述。
SELECT * FROM customer_wallet_detail WHERE id IN ( SELECT MAX( id ) FROM customer_wallet_detail GROUP BY customer_id ) ORDER BY customer_id;

方法三(適用于自增ID和創(chuàng)建時間排序一致)
方法三和方法二實(shí)現(xiàn)邏輯基本一致只是將IN查詢替換成了連接查詢,本地20w條數(shù)據(jù)測試 方法三比方法二性能提升50%,有興趣的可以增大數(shù)據(jù)集測試后續(xù)性能變化。
SELECT t1.* FROM customer_wallet_detail t1 INNER JOIN ( SELECT MAX( id ) AS id FROM customer_wallet_detail GROUP BY customer_id ) t2 ON t1.id = t2.id

總結(jié)
結(jié)合我的業(yè)務(wù)經(jīng)過測試,目前看來方案三是最合適的,sql簡單性能適中,方案一比方案二性能更差而且實(shí)現(xiàn)麻煩,最終選擇那個方案主要看業(yè)務(wù)而定。
MAX()函數(shù)和MIN()這一類函數(shù)和GROUP BY配合使用存在問題
MAX()函數(shù)和MIN()這一類函數(shù)和GROUP BY配合使用,GROUP BY拿到的數(shù)據(jù)永遠(yuǎn)都是這個分組排序最上面的一條,而MAX()函數(shù)和MIN()這一類函數(shù)會將這個分組中最大 | 最小的值取出來,這樣會導(dǎo)致查詢出來的數(shù)據(jù)對應(yīng)不上。
正確查詢:

錯誤查詢:這里的確拿到每個分組最新創(chuàng)建時間了但是拿的數(shù)據(jù)id還是排序的第一條


到此這篇關(guān)于Mysql分組查詢每組最新一條數(shù)據(jù)的三種實(shí)現(xiàn)方法的文章就介紹到這了,更多相關(guān)Mysql分組查詢每組最新數(shù)據(jù)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
MySQL性能優(yōu)化之Open_Table配置參數(shù)的合理配置建議
這篇文章主要介紹了MySQL性能優(yōu)化之Open_Table配置參數(shù)的合理配置建議,在MySQL數(shù)據(jù)庫中,Opened_tables表示打開過的表數(shù)量,需要的朋友可以參考下2014-07-07
mysql函數(shù)之常見數(shù)學(xué)函數(shù)示例詳解
文章總結(jié)了多個數(shù)學(xué)和字符串處理函數(shù)的功能和使用示例,包括格式化數(shù)字、計(jì)算絕對值、平方根、取整、生成隨機(jī)數(shù)、四舍五入、截斷、返回符號、冪運(yùn)算以及最大值最小值的計(jì)算,感興趣的朋友一起看看吧2025-03-03
詳解MySQL 重做日志(redo log)與回滾日志(undo logo)
這篇文章主要介紹了MySQL redo與undo日志的相關(guān)資料,幫助大家更好的理解和學(xué)習(xí)MySQL,感興趣的朋友可以了解下2020-08-08
MySQL實(shí)戰(zhàn)之Insert語句的使用心得
這篇文章主要給大家介紹了關(guān)于MySQL實(shí)戰(zhàn)之Insert語句的使用心得的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-10-10
MySQL創(chuàng)建數(shù)據(jù)庫和創(chuàng)建數(shù)據(jù)表
MySQL?是最常用的數(shù)據(jù)庫,在數(shù)據(jù)庫操作中,基本都是增刪改查操作,簡稱CRUD。但是,這篇文章主要介紹了數(shù)據(jù)庫和數(shù)據(jù)表如何創(chuàng)建,想詳細(xì)了解的小伙伴可以參考閱讀一下2023-03-03
解決Mysql收縮事務(wù)日志和日志文件過大無法收縮問題
這篇文章主要介紹了解決Mysql收縮事務(wù)日志和日志文件過大無法收縮問題,非常不錯,具有參考借鑒價值,需要的朋友可以參考下2017-08-08
與MSSQL對比學(xué)習(xí)MYSQL的心得(四)--BLOB數(shù)據(jù)類型
在MYSQL中BLOB是一個二進(jìn)制大對象,用來儲存可變數(shù)量的數(shù)據(jù),而MSSQL中并沒有BLOB數(shù)據(jù)類型,只有大型對象數(shù)據(jù)類型(LOB)2014-06-06

