[英]Query for top N per group code works for MySQL but different result for MariaDB
我有一個SQL查詢,它提取每組的最新3條記錄。 MySQL的查詢結果與MariaDB不同。 此查詢在下面的sqlfiddle中實現
http://sqlfiddle.com/#!9/c09fe/2
表的內容
CREATE TABLE tmp
(`mac_addr` varchar(10), `reader_name` varchar(22), `value` numeric, `time_change` datetime)
;
INSERT INTO tmp
(`mac_addr`, `reader_name`, `value`, `time_change`)
VALUES
('''B99A88''', '''name_8''', 1, '2016-07-07 19:21:48'),
('''B99A88''', '''own__detect_1''', 1, '2016-06-21 13:30:00'),
('''B99A88''', '''own__temperature_1''', 37.4, '2016-05-04 18:23:03'),
('''B99A88''', '''own__temperature_1''', 29.4, '2016-05-04 18:19:33'),
('''B99A88''', '''own__temperature_1''', 28.4, '2016-05-04 18:17:32'),
('''B99A88''', '''own__temperature_1''', 27.4, '2016-05-04 18:04:08'),
('''B99A88''', '''own__temperature_1''', 21.4, '2016-05-04 15:11:42'),
('''B99A88''', '''own__detect_1''', 0, '2016-04-20 15:22:23'),
('''B99A88''', '''own__detect_1''', 1, '2016-04-15 17:39:52'),
('''B99A88''', '''own__detect_1''', 0, '2016-04-15 17:39:46'),
('''B99A88''', '''own__detect_1''', 1, '2016-04-11 17:34:00'),
('''B99A88''', '''own__detect_1''', 1, '2016-04-11 17:33:00'),
('''B99A88''', '''own__detect_1''', 0, '2016-04-11 17:33:00'),
('''B99A88''', '''own__temperature_1''', 28.4, '2016-04-10 21:20:20'),
('''B99A88''', '''own__temperature_1''', 32.5, '2016-04-10 21:00:00'),
('''B99A88''', '''own__temperature_1''', 34.2, '2016-04-10 11:29:00')
;
查詢以提取每個組的最新3條記錄。
SELECT mac_addr, reader_name, value, time_change
FROM (
SELECT t1.*,
IF(@rn = reader_name, @rowno := @rowno + 1, @rowno := 1) AS rowno,
@rn := reader_name
FROM (
SELECT *
FROM tmp
ORDER BY reader_name, time_change DESC
) t1
CROSS JOIN (SELECT @rn := null, @rowno := 0) t2
) t
WHERE rowno <= 3
使用MySQL v5.6時的結果如下;
mac_addr reader_name value time_change
'B99A88' 'name_8' 1 July, 07 2016 19:21:48
'B99A88' 'own__detect_1' 1 June, 21 2016 13:30:00
'B99A88' 'own__detect_1' 0 April, 20 2016 15:22:23
'B99A88' 'own__detect_1' 1 April, 15 2016 17:39:52
'B99A88' 'own__temperature_1' 37 May, 04 2016 18:23:03
'B99A88' 'own__temperature_1' 29 May, 04 2016 18:19:33
'B99A88' 'own__temperature_1' 28 May, 04 2016 18:17:32
MySQL的結果就是我想要的。 但是,我使用的是MariaDB,結果與MySQL結果不同。
MariaDB結果如下所示;
mac_addr reader_name value time_change
'B99A88' 'name_8' 1 2016-07-07 19:21:48
'B99A88' 'own__detect_1' 1 2016-06-21 13:30:00
'B99A88' 'own__temperature_1' 37 2016-05-04 18:23:03
'B99A88' 'own__temperature_1' 29 2016-05-04 18:19:33
'B99A88' 'own__temperature_1' 28 2016-05-04 18:17:32
'B99A88' 'own__detect_1' 0 2016-04-20 15:22:23
'B99A88' 'own__detect_1' 1 2016-04-15 17:39:52
'B99A88' 'own__detect_1' 0 2016-04-15 17:39:46
'B99A88' 'own__temperature_1' 28 2016-04-10 21:20:20
'B99A88' 'own__temperature_1' 33 2016-04-10 21:00:00
'B99A88' 'own__temperature_1' 34 2016-04-10 11:29:00
如何修改查詢代碼,以便MariaDB的查詢輸出可以與MySQL相同? 在MariaDB中使用窗口函數是個好主意嗎?
允許查詢執行忽略 FROM ( SELECT ... )
的ORDER BY
。 這可能是你所看到的差異的真正原因。 (我不認為戈登的答案是相關的。)
這里討論了這個問題(4年前): https : //mariadb.com/kb/en/mariadb/group-by-trick-has-been-optimized-away/ ; 有一個解決方案,通過設置。
其他一些解決方案在這里: http : //mysql.rjweb.org/doc.php/groupwise_max ; 它們旨在提高效率。
另一種可能的解決方案是在子查詢上添加一個帶有大量數字的偽LIMIT
。
您使用的ORDER BY
有兩個鍵:
ORDER BY reader_name, time_change DESC
但是,這些鍵不能唯一標識每一行。 因此,無法保證密鑰相同的行的排序 - 即使在同一數據庫上的兩次查詢運行之間也是如此。 通常的解決方案是添加唯一的id列作為最后一個ORDER BY
鍵,以便唯一標識每一行。
更一般地說,在SQL中, ORDER BY
不使用穩定的排序。 當鍵相同時,穩定排序是保留鍵的原始排序的排序。 原因很簡單。 SQL表和結果集表示無序集。 沒有初始訂單要保留。
如果您有主鍵列,那么ORDER BY
將是:
ORDER BY reader_name, time_change DESC, pk
其余代碼不需要更改。 您只希望排序穩定。
使用標准SQL語言結構選擇每組前N的經典方法是使用ROW_NUMBER
:
SELECT
T.*
FROM
(
SELECT *
,ROW_NUMBER() OVER (PARTITION BY reader_name ORDER BY time_change DESC) AS rn
FROM tmp
) AS T
WHERE T.rn <= 3
ORDER BY reader_name, time_change DESC;
此查詢應適用於支持ROW_NUMBER
所有DBMS。 MySQL不支持它,所以人們必須使用脆弱的技巧和特定於MySQL的變量。
Window函數最初是在MariaDB 10.2.0中引入的。 MariaDB在優化查詢方面有更多的自由,而且這個帶變量的MySQL技巧不再可靠。
所以,回答你的問題, 是的,使用MariaDB中的窗口函數是個好主意 。
另一種選擇每組前N個的常用方法是使用LATERAL
連接,當組數較小且表中的行數較大且具有適當的索引和第二個包含組列表的表時,優於ROW_NUMBER
。 我不知道MariaDB是否支持LATERAL
連接。 看起來沒有。
(我的)SQL不需要保持子查詢結果的順序。 您必須在上層查詢中對結果集進行排序,但在您的情況下,您實際上可以擺脫子查詢:
SELECT mac_addr, reader_name, value, time_change
FROM (
SELECT t1.*,
IF(@rn = reader_name, @rowno := @rowno + 1, @rowno := 1) AS rowno,
@rn := reader_name
FROM tmp t1, (SELECT @rn := null, @rowno := 0) t2
ORDER BY reader_name, time_change DESC
) t
WHERE rowno <= 3;
只是為了完整性:這種行為特定於變量的使用,並且由於結果實際上沒有在sql標准中定義,它可能會在某一天發生變化(就像現在導致麻煩的優化),但這很可能不會發生,如果有的話,直到完全支持窗口函數,所以你可以忽略這個細節。 對於強制執行訂單的其他方式也是如此,例如在Rick建議的內部查詢中添加limit 999999999
,盡管我可以想到一些(尚未實現的)優化路徑可能再次導致未指定的順序。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.