mysql分組求最新一條資料(mysql5.7實現開窗)
需求
求按一定欄位進行分組之後的每組最新或者版本最大的一條資料。
假設:
如書名和書編碼相同時,則獲取版本號最大(或建立時間最近)的一條資料;
PS:本文就以版本號最大為例子吧。實際依葫蘆畫瓢就明白其他的場景了。
預熱準備
定義測試“書”表的DDL
CREATE TABLE `book` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `name` varchar(255) DEFAULT NULL COMMENT '書名', `code` varchar(64) DEFAULT NULL COMMENT '書編碼', `create_time` datetime DEFAULT NULL COMMENT '建立時間', `version` int(11) DEFAULT NULL COMMENT '版本號', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
PS:簡簡單單的資料欄位,如有不嚴謹的地方請不要在意,這裡僅僅用於說明本文需要講解的場景哦!!!
隨便插入幾條測試資料的DML
INSERT INTO `book` VALUES ('1', '小白', '0001', '2020-04-07 21:07:44', '1'); INSERT INTO `book` VALUES ('2', '小白', '0001', '2020-04-08 21:07:59', '2'); INSERT INTO `book` VALUES ('3', '小黑', '0002', '2020-04-08 21:08:16', '1'); INSERT INTO `book` VALUES ('4', '小明', '0003', '2020-04-01 21:08:28', '2'); INSERT INTO `book` VALUES ('5', '小明', '0003', '2020-04-08 21:08:40', '3');
- 1
- 2
- 3
- 4
- 5
- 6
PS:瞎插幾條資料,以表心意!!!!
方案設計
根據上面的樣本資料再結合對應的需求,那麼,**在理想狀態下,**就是獲取得到如下情況的資料:
PS:為了讓各位看的清楚,就特意按照id從小到大的排序了哈!!
錯誤方案一
採取簡單直接的方式,即先分組和排序一起操作
select *
FROM
book
GROUP BY name,code
order by version desc, id asc
- 1
- 2
- 3
- 4
- 5
結果
結論:這樣的方案是錯誤的
對比,理想情況和實際情況,只能說,這種方案當然不行啦。那麼為何不行呢?請繼續看。。。。。
分析
- 首先,確實是將相同的name和code的內容進行了分組,即同樣的name和code的資料只存在一條,說明group by沒有毛病,是分組了。
- order by:其中id asc是升序,那麼結果確實也是升序這個沒問題;假設,覺得沒用,那麼大家可以試試沒有加id asc的情況就如下,說明id asc 是生效了。
- order by:其中version desc是倒序,那麼再看下實際情況,確實也是按照version倒序進行排序,那麼說version desc也是有效果的。那麼到底哪裡有問題呢?
結論:這是因為當group by 和order by同時出現的時候,它是先執行group by 分組然後才對分組的結果進行的排序。因此,執行順序是有問題,這樣自然無法實現我們需要的取每組最大版本號的資料了。
錯誤方案二
經過上面錯誤方案的解析,那麼這一種肯定可以。。自信滿滿!!!
select *
from
(
select *
from
book
ORDER BY version desc
)as t1
GROUP BY t1.name,t1.code
order by id asc
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
結果
結論:這個方案結果還是不對!!!
分析
- 通過實際結果觀察,id確實是升序,但是每條資料的version欄位卻不是最大的呀。
- 通過第一種錯誤方案說到,要先根據version排序,然後再group by 分組。那看看SQL,確實是先排序,再分組了。可是為什麼不行?
結論:該方案還是由於排序和分組的效果被優化導致的。因為當外層存在group by語法時,會導致內層的order by 會失效,而mysql會預設採取“第一條”。關鍵來了,“第一條”,這個第一條並不是排序後的第一條,而是插入資料庫同等分組條件下的順序的第一條哦!
假設,我們把初始的資料變成如下,然後再執行該方案的SQL就發現結果與期望的一樣啦。其原因就是在於上面說的。
=======================================
好氣呀,這兩種方案都不行,那到底怎麼寫呢。
別慌,繼續往下面看
=======================================
正確方案一
select *
from
(
select *
from
book
ORDER BY version desc
limit 100000
)as t1
GROUP BY t1.name,t1.code
order by id asc
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
結果
結論:哇塞,實際和預期是一樣的結果了哦!!!
分析
- 我們對比一下錯誤方案二和現在這種方案,發現,就是因為該mysql內層採取了limit關鍵字進行“分頁”處理。
- why?why?why?
結論:因為在內層子查詢中採取limit和order by同時作用的話,就會將子查詢的結果根據對應語法進行實際的先排序後分頁,而不會與外層的group by語法進行優化。因此,這樣的效果就可以實現獲取每組的版本號最大的資料了。
缺點:很多朋友也應該想到了,那麼就是如果子查詢的結果集的條數是不知道的呢?那麼limit如果小了,就會導致一些資料丟失,自然這樣得到的結果就不準了。那麼如何解決呢?
優化:既然無法確定實際的查詢資料條數,那麼就可以先查詢一下總的資料條數,然後limit就以該結果作為引數不就可以了嘛。對的,先查詢子查詢的總資料條數,然後limit該結果就可以了。切記,是分開兩個SQL語句了哦!!!!
注意點:這裡要採取inner join而別採取left join或者right join(它們三者的區別就不多說了),除非你確定你的資料條件中,不存在null的情況,而都是一一對應而都存在,那麼就沒問題;
正確方案二
上面的方案存在一定的問題,那麼還可以怎麼做呢?
select t1.*
from
book as t1
INNER JOIN
(
SELECT name,code,max(version) as version
from
book
GROUP BY name,code
)as t2
on
t1.code = t2.code
and
t1.name = t2.name
and
t1.version = t2.version
order by id asc
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
結果
結論:實際和預期一樣!
分析
該SQL實現的方式主要是用到了max函式的作用。
(1)我們逐步的拆分來看:
首先,
SELECT name,code,max(version) as version
from
book
GROUP BY name,code
- 1
- 2
- 3
- 4
我們先根據要分組的內容進行group by,注意這時候select的欄位也是要將分組的欄位進行獲取,其次,就是採取max函式獲取我們需要的version最大(同理,如果是建立時間也是一樣)。那麼這樣處理得到的結果是什麼呢?自然,這樣就可以獲取到每組中版本號最大的資料資訊。注意,此時並沒有達到我們想要的結果。因為如果我們還要獲取到版本號最大的其他的欄位的資訊,而此時只是獲取到版本號最大的,而其他欄位並非就是版本號最大對應的所有欄位資訊。
(2)再通過inner join的語法作用。我們在(1)中已經獲取到了每個分組條件以及需要版本號的最大的值,那麼,通過inner join的等值連線,這樣就可以根據“等值原理”獲取到其對應的所有欄位的資訊了呀。那麼就實現了我們的需求;
缺點:可以發現在最裡層的子查詢中這樣的效率是不好的,是走的全表掃描(PS:如果存在其他的限制條件通過有索引可以實現一點優化),那麼全表掃描的效率就不夠好了。
優化:在裡層的查詢中,增加一定的限制條件,並且限制條件採取能夠以索引處理;
正確方案三
那麼還有其他的處理方案嗎?
select t1.*
from
bookas t1
,
(
SELECT name,code,max(version) as version
from
book
GROUP BY name,code
)as t2
where
t1.code = t2.code
and
t1.name = t2.name
and
t1.version = t2.version
order by id asc
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17