Pobieranie ostatniego rekordu w każdej grupie-MySQL
Istnieje tabela messages
, która zawiera dane, jak pokazano poniżej:
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
Jeśli uruchomię zapytanie select * from messages group by name
, otrzymam wynik jako:
1 A A_data_1
4 B B_data_1
6 C C_data_1
Jakie zapytanie zwróci następujący wynik?
3 A A_data_3
5 B B_data_2
6 C C_data_1
Oznacza to, że należy zwrócić ostatni rekord w każdej grupie.
Obecnie jest to zapytanie, którego używam:
SELECT
*
FROM (SELECT
*
FROM messages
ORDER BY id DESC) AS x
GROUP BY name
Ale to wygląda na wysoce nieefektywne. Jakieś inne sposoby na osiągnięcie tego samego wyniku? 21 answers
MySQL 8.0 obsługuje teraz funkcje windowing, jak prawie wszystkie popularne implementacje SQL. Z tą standardową składnią możemy pisać zapytania greatest-n-per-group:
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;
Poniżej znajduje się oryginalna odpowiedź, którą napisałem na to pytanie w 2009 roku:
Piszę rozwiązanie w ten sposób:
SELECT m1.*
FROM messages m1 LEFT JOIN messages m2
ON (m1.name = m2.name AND m1.id < m2.id)
WHERE m2.id IS NULL;
Jeśli chodzi o wydajność, jedno lub drugie rozwiązanie może być lepsze, w zależności od charakteru danych. Więc należy przetestować oba zapytania i użyć tego, który jest lepszy w wydajność biorąc pod uwagę bazę danych.
Na przykład, mam kopię zrzutu sierpniowego Stoskoverflow . Użyję tego do analizy porównawczej. W tabeli Posts
znajduje się 1 114 357 wierszy. To działa na MySQL 5.0.75 na moim MacBooku Pro 2.40 GHz.
Napiszę zapytanie, aby znaleźć najnowszy post dla danego identyfikatora użytkownika (mojego).
Pierwsze użycie techniki pokazanej przez @Eric z GROUP BY
w subquery:
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)
Nawet na EXPLAIN
analiza trwa ponad 16 sekund:
+----+-------------+------------+--------+----------------------------+-------------+---------+--------------+---------+-------------+
| 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)
Teraz Wygeneruj ten sam wynik zapytania używając moja technika z 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)
Analiza EXPLAIN
pokazuje, że obie tabele są w stanie wykorzystać swoje indeksy:
+----+-------------+-------+------+----------------------------+-------------+---------+-------+------+--------------------------------------+
| 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)
Oto DDL dla mojej Posts
tabeli:
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;
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-12-26 20:38:20
upd: 2017-03-31, Wersja 5.7.5 MySQL sprawił, że przełącznik ONLY_FULL_GROUP_BY był domyślnie włączony (stąd nie-deterministyczna Grupa zapytań została wyłączona). Co więcej, zaktualizowali grupę według wdrożenia, a rozwiązanie może nie działać zgodnie z oczekiwaniami nawet przy wyłączonym przełączniku. Trzeba to sprawdzić.
Powyższe rozwiązanie Billa Karwina działa dobrze, gdy liczba elementów w grupach jest raczej mała, ale wydajność zapytania staje się zła, gdy grupy są dość duże, ponieważ rozwiązanie wymaga około n*n/2 + n/2
Tylko IS NULL
porównań.
Wykonałem testy na tablicy InnoDB 18684446
wierszy z 1182
grupami. Tabela zawiera wyniki testów dla testów funkcjonalnych i ma (test_id, request_id)
jako klucz podstawowy. Tak więc test_id
jest grupą i szukałem ostatniej request_id
dla każdej test_id
.
using index
w wyjaśnieniu).
Mam kilka innych rozwiązań, które są oparte na tych samych pomysłach:]}
- jeśli indeks bazowy jest indeksem BTREE (co zwykle ma miejsce), największa para
(group_id, item_value)
jest ostatnią wartością w każdymgroup_id
, to jest pierwsza dla każdegogroup_id
jeśli przechodzimy przez indeks w porządku malejącym; - jeśli odczytamy wartości, które są objęte indeksem, wartości są odczytywane w kolejności indeksu;
- każdy indeks domyślnie zawiera kolumny klucza podstawowego dołączone do tego (czyli klucz podstawowy znajduje się w indeksie pokrycia). W poniższych rozwiązaniach operuję bezpośrednio na kluczu podstawowym, w Twoim przypadku wystarczy dodać kolumny klucza podstawowego w wyniku.
- w wielu przypadkach znacznie taniej jest zebrać wymagane identyfikatory wierszy w wymaganej kolejności w zapytaniu podrzędnym i dołączyć wynik zapytania podrzędnego do id. Ponieważ dla każdego wiersza w wyniku zapytania podrzędnego MySQL będzie potrzebował jednego pobierania opartego na kluczu podstawowym, podquery będą umieszczane jako pierwsze w join, a wiersze będą wyświetlane w kolejności ID w podquery (jeśli pominiemy jawną kolejność przez dla join)
3 ways MySQL uses indexes to świetny artykuł, aby zrozumieć niektóre szczegóły.
Rozwiązanie 1
[[15]} ten jest niesamowicie szybki, zajmuje około 0,8 SEK na moich 18m + rzędach:SELECT test_id, MAX(request_id), request_id
FROM testresults
GROUP BY test_id DESC;
Jeśli chcesz zmienić kolejność na ASC, umieść ją w zapytaniu podrzędnym, zwróć tylko identyfikatory i użyj tego jako podquery do dołączenia do reszty kolumn:
SELECT test_id, request_id
FROM (
SELECT test_id, MAX(request_id), request_id
FROM testresults
GROUP BY test_id DESC) as ids
ORDER BY test_id;
Ten zajmuje około 1,2 sekundy na moich danych.
Rozwiązanie 2
Oto inne rozwiązanie, które zajmuje około 19 sekund dla mojego stołu:
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
Zwraca również testy w kolejności malejącej. Jest znacznie wolniejszy, ponieważ wykonuje pełne skanowanie indeksów, ale jest tutaj, aby dać ci pomysł, jak wypisać N maksymalnych wierszy dla każdej grupy.
Wadą zapytania jest to, że jego wynik nie może być buforowany przez bufor zapytań.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-03-31 15:08:26
Użyj subquery , aby zwrócić poprawną grupę, ponieważ jesteś w połowie drogi.
Spróbuj tego:
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
Jeśli nie id
chcesz max:
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
W ten sposób unikasz skorelowanych zapytań podrzędnych i / lub porządkowania w swoich zapytaniach podrzędnych, które wydają się być bardzo powolne / nieefektywne.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-08-21 17:14:13
Doszedłem do innego rozwiązania, które polega na pobraniu identyfikatorów dla ostatniego posta w każdej grupie, a następnie wybraniu z tabeli wiadomości, używając wyniku z pierwszego zapytania jako argumentu dlaWHERE x IN
konstruktu:
SELECT id, name, other_columns
FROM messages
WHERE id IN (
SELECT MAX(id)
FROM messages
GROUP BY name
);
Nie wiem, jak to działa w porównaniu z innymi rozwiązaniami, ale działało spektakularnie dla mojej tabeli z 3 + milionami wierszy. (4 sekunda wykonania z 1200 + wyników)
powinno to działać zarówno na MySQL jak i SQL Server.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-02-20 21:46:38
Rozwiązanie przez sub query fiddle Link
select * from messages where id in
(select max(id) from messages group by Name)
Rozwiązanie przez warunek join fiddle link
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
Powodem tego postu jest podanie tylko linku. Ten sam SQL jest już dostępny w innych odpowiedziach.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-12-25 08:36:42
Jeszcze nie testowałem z dużym DB, ale myślę, że może to być szybsze niż łączenie tabel:
SELECT *, Max(Id) FROM messages GROUP BY Name
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-02-14 07:07:11
Oto dwie propozycje. Po pierwsze, jeśli mysql obsługuje ROW_NUMBER (), jest to bardzo proste:
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;
Zakładam, że przez "ostatni" masz na myśli ostatni w kolejności identyfikacyjnej. Jeśli nie, należy odpowiednio zmienić klauzulę ORDER BY okna ROW_NUMBER (). Jeśli funkcja ROW_NUMBER () nie jest dostępna, jest to inne rozwiązanie:
Po drugie, jeśli nie, to często jest to dobry sposób postępowania:
SELECT
Id, Name, OtherColumns
FROM messages
WHERE NOT EXISTS (
SELECT * FROM messages as M2
WHERE M2.Name = messages.Name
AND M2.Id > messages.Id
)
Innymi słowy, wybierz Wiadomości, w których nie ma później-ID wiadomości o tej samej nazwie.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-08-21 17:26:12
Oto moje rozwiązanie:
SELECT
DISTINCT NAME,
MAX(MESSAGES) OVER(PARTITION BY NAME) MESSAGES
FROM MESSAGE;
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-06-08 19:03:49
Oto inny sposób na uzyskanie ostatniego powiązanego rekordu za pomocą {[1] } z order by i SUBSTRING_INDEX
, aby wybrać jeden z rekordów z listy
SELECT
`Id`,
`Name`,
SUBSTRING_INDEX(
GROUP_CONCAT(
`Other_Columns`
ORDER BY `Id` DESC
SEPARATOR '||'
),
'||',
1
) Other_Columns
FROM
messages
GROUP BY `Name`
Powyższe zapytanie grupuje wszystkie Other_Columns
, które są w tej samej grupie Name
i używając ORDER BY id DESC
połączy wszystkie Other_Columns
w określonej grupie w porządku malejącym z podanym separatorem w moim przypadku użyłem ||
, używając SUBSTRING_INDEX
na tej liście wybierze pierwszą
Demo Fiddle
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-03-30 06:01:52
SELECT
column1,
column2
FROM
table_name
WHERE id IN
(SELECT
MAX(id)
FROM
table_name
GROUP BY column1)
ORDER BY column1 ;
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-05-04 11:38:30
Spróbuj tego:
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 )
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-07-15 13:47:27
Stąd też można podziwiać widoki.
Http://sqlfiddle.com/#! 9 / ef42b/9
PIERWSZE ROZWIĄZANIE
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);
DRUGIE ROZWIĄZANIE
SELECT * FROM (SELECT * FROM Demo_User ORDER BY ID DESC) AS T GROUP BY NAME ;
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-09-28 09:07:12
Czy możemy użyć tej metody do usunięcia duplikatów w tabeli? Zestaw wyników jest w zasadzie zbiorem unikalnych rekordów, więc gdybyśmy mogli usunąć wszystkie rekordy Nie w zestawie wyników, nie mielibyśmy żadnych duplikatów? Próbowałem tego, ale mySQL dał błąd 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)
Czy istnieje sposób, aby zapisać wyjście do zmiennej temp, a następnie usunąć z NOT in (zmienna temp)? @ Bill dzięki za bardzo przydatne rozwiązanie.
EDIT: chyba znalazłem rozwiązanie:
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);
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-10-08 01:57:49
Poniższe zapytanie będzie działać dobrze, jak na twoje pytanie.
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;
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-11-18 20:21:00
Hi @ Vijay Dev jeśli twoja tabela messages zawiera Id, który jest automatycznym przyrostem klucza podstawowego, to aby pobrać najnowszy rekord na podstawie klucza podstawowego, Twoje zapytanie powinno wyglądać następująco:
SELECT m1.* FROM messages m1 INNER JOIN (SELECT max(Id) as lastmsgId FROM messages GROUP BY Name) m2 ON m1.Id=m2.lastmsgId
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-10-21 14:08:16
Jeśli chcesz, aby ostatni wiersz dla każdego Name
, możesz nadać numer wiersza każdej grupie wierszy przez Name
i kolejność przez Id
w porządku malejącym.
Zapytanie
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;
SQL
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-11-19 04:36:11
Podejście ze znaczną prędkością jest następujące.
SELECT *
FROM messages a
WHERE Id = (SELECT MAX(Id) FROM messages WHERE a.Name = Name)
Wynik
Id Name Other_Columns
3 A A_data_3
5 B B_data_2
6 C C_data_1
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-03-10 20:33:11
A może tak:
SELECT DISTINCT ON (name) *
FROM messages
ORDER BY name, id DESC;
Miałem podobny problem (na PostgreSQL tough) i na tabeli rekordów 1M. To rozwiązanie trwa 1.7 s vs 44s produkowane przez ten z lewym złączem. W moim przypadku musiałem filtrować corrispondant twojego pola name względem wartości NULL, co skutkowało jeszcze lepszymi wynikami o 0.2 sek
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-11-30 10:50:40
Oczywiście istnieje wiele różnych sposobów uzyskania tych samych wyników, twoje pytanie wydaje się być skutecznym sposobem uzyskania ostatnich wyników w każdej grupie w MySQL. Jeśli pracujesz z ogromną ilością danych i zakładając, że używasz InnoDB nawet z najnowszymi wersjami MySQL (takimi jak 5.7.21 i 8.0.4-RC), może to nie być skuteczny sposób na zrobienie tego.
Czasami musimy to zrobić z tabelami zawierającymi nawet ponad 60 milionów wierszy.
Dla tych przykłady użyję danych z tylko około 1,5 miliona wierszy, w których zapytania będą musiały znaleźć wyniki dla wszystkich grup w danych. W naszych rzeczywistych przypadkach często musielibyśmy zwrócić dane z około 2000 grup (które hipotetycznie nie wymagałyby zbadania bardzo dużo danych).
Użyję następujących tabel:
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));
Tabela temperatury jest wypełniona około 1,5 miliona przypadkowych rekordów i 100 różnych grup. Selected_group jest wypełniona tych 100 grup (w naszych przypadkach byłoby to zwykle mniej niż 20% dla wszystkich grup).
Ponieważ te dane są losowe, oznacza to, że wiele wierszy może mieć te same rekordy czasu. Chcemy uzyskać listę wszystkich wybranych grup w kolejności groupID z ostatnim zarejestrowanym znacznikiem czasu dla każdej grupy, a jeśli ta sama grupa ma więcej niż jeden pasujący wiersz, taki jak ten, to ostatni pasujący identyfikator tych wierszy.
Jeśli hipotetycznie MySQL miał funkcję last (), która zwracała wartości z ostatniego wiersza w klauzuli special ORDER BY możemy po prostu zrobić:
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;
Który musiałby zbadać tylko kilka 100 wierszy w tym przypadku, ponieważ nie używa żadnej z normalnych funkcji GROUP BY. Wykonałoby się to w ciągu 0 sekund, a tym samym byłoby wysoce wydajne. Zauważ, że normalnie w MySQL widzimy klauzulę ORDER BY następującą po klauzuli GROUP BY, jednak ta klauzula ORDER BY jest używana do określenia kolejności dla funkcji last (), jeśli była po grupie by wtedy to zamawiaj grupy. Jeśli nie ma klauzuli GROUP BY, to ostatnie wartości będą takie same we wszystkich zwracanych wierszach.
Jednak MySQL tego nie ma, więc spójrzmy na różne pomysły na to, co ma i udowodnijmy, że żadna z nich nie jest wydajna.
Przykład 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
);
To zbadało 3,009,254 wierszy i zajęło ~0,859 sekundy na 5.7.21 i nieco dłużej na 8.0.4-rc
Przykład 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;
To zbadało 1,505,331 wierszy i trwało ~1.25 sekund na 5.7.21 i nieco dłużej na 8.0.4-rc
Przykład 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;
To zbadało 3,009,685 wierszy i zajęło ~1,95 sekundy na 5.7.21 i nieco dłużej na 8.0.4-rc
Przykład 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
)
);
To zbadało 6,137,810 wierszy i zajęło ~2,2 sekundy na 5.7.21 i nieco dłużej na 8.0.4-rc
Przykład 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;
To zbadało 6,017,808 wierszy i zajęło ~4,2 sekundy na 8.0.4-rc
Przykład 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;
To zbadało 6,017,908 wierszy i zajęło ~17.5 sekund na 8.0.4-rc
Przykład 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;
To trwało wieczność, więc musiałem go zabić.Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-04-30 06:20:59
Jeśli wydajność jest naprawdę Twoim zmartwieniem, możesz wprowadzić nową kolumnę w tabeli o nazwie IsLastInGroup
Typu BIT.
Ustaw na true w kolumnach, które są ostatnie i utrzymuj je z każdym wierszem insert/update / delete. Pisanie będzie wolniejsze, ale skorzystasz na czytaniu. To zależy od Twojego przypadku użycia i polecam go tylko wtedy, gdy jesteś skoncentrowany na czytaniu.
Więc twoje zapytanie będzie wyglądało następująco:
SELECT * FROM Messages WHERE IsLastInGroup = 1
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-05-02 15:05:59
select * from messages group by name desc
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-06-18 14:21:07