Pobieranie ostatniego rekordu w każdej grupie - MySQL


952

Istnieje tabela, messagesktó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 w postaci:

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 używam tego zapytania:

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

Ale to wygląda bardzo nieefektywnie. Jakieś inne sposoby na osiągnięcie tego samego rezultatu?


2
zobacz zaakceptowaną odpowiedź na stackoverflow.com/questions/1379565/... w celu uzyskania bardziej wydajnego rozwiązania
eyaler


7
Dlaczego nie możesz po prostu dodać DESC, tzn. Wybrać * z grupy wiadomości według nazwy DESC
Kim Prince


2
@KimPrince Wygląda na to, że sugerowana przez ciebie odpowiedź nie spełnia oczekiwań! Właśnie wypróbowałem twoją metodę i zajęło PIERWSZY wiersz dla każdej grupy i zamówiłem DESC. NIE zajmuje ostatniego rzędu każdej grupy
Ayrat

Odpowiedzi:


966

MySQL 8.0 obsługuje teraz funkcje okienkowania, jak prawie wszystkie popularne implementacje SQL. Dzięki tej standardowej składni możemy pisać zapytania typu „n-na-grupę”:

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 r .:


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. Powinieneś więc przetestować oba zapytania i użyć tego, które jest lepsze pod względem wydajności, biorąc pod uwagę bazę danych.

Na przykład mam kopię zrzutu danych StackOverflow August . Użyję tego do testów porównawczych. Tabela zawiera 1114357 wierszy Posts. Działa to na MySQL 5.0.75 na moim Macbooku Pro 2.40GHz.

Napiszę zapytanie, aby znaleźć najnowszy post dla danego identyfikatora użytkownika (mojego).

Najpierw użyj techniki pokazanej przez @Eric z GROUP BYpodzapytaniem:

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 EXPLAINanaliza zajmuje 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 przy użyciu mojej techniki 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)

Do EXPLAINanalizy wynika, że obie tabele są w stanie używać ich 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 Poststabeli:

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
Naprawdę? Co się stanie, jeśli masz mnóstwo wpisów? Na przykład, jeśli pracujesz z wewnętrzną kontrolą wersji, powiedzmy, i masz mnóstwo wersji na plik, ten wynik łączenia byłby ogromny. Czy kiedykolwiek porównywałeś tę metodę z podzapytaniem? Jestem ciekawy, kto by wygrał, ale nie jestem na tyle ciekawy, by nie zapytać cię najpierw.
Eric,

2
Zrobiłem testy. Na małym stoliku (~ 300 tys. Rekordów, ~ 190 tys. Grup, a więc nie masywnych grup itp.) Zapytania wiązały się (po 8 sekund).
Eric

1
@BillKarwin: Patrz meta.stackexchange.com/questions/123017 , zwłaszcza komentarze poniżej odpowiedzi Adama Rackisa. Daj mi znać, jeśli chcesz odzyskać odpowiedź na nowe pytanie.
Robert Harvey

3
@Tim, nie, <=nie pomoże, jeśli masz nieunikalną kolumnę. Musisz użyć unikalnej kolumny jako remisu.
Bill Karwin

2
Wydajność spada wykładniczo wraz ze wzrostem liczby wierszy lub gdy grupy stają się większe. Na przykład grupa składająca się z 5 dat da 4 + 3 + 2 + 1 + 1 = 11 wierszy poprzez lewe łączenie, z którego jeden wiersz jest filtrowany na końcu. Wydajność łączenia z pogrupowanymi wynikami jest prawie liniowa. Twoje testy wyglądają na wadliwe.
Salman A

145

UPD: 31.03.2017, wersja 5.7.5 MySQL włącza domyślnie przełącznik ONLY_FULL_GROUP_BY (dlatego niedeterministyczne zapytania GROUP BY zostały wyłączone). Ponadto zaktualizowali implementację GROUP BY i rozwiązanie może już 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 n*n/2 + n/2tylko okołoIS NULL porównań.

Testy wykonałem na tabeli 18684446wierszy InnoDB z 1182grupami. Tabela zawiera wyniki testów dla testów funkcjonalnych i ma (test_id, request_id)jako klucz podstawowy. Tak więc test_idjest grupa i szukałem ostatniego request_iddla każdego test_id.

Rozwiązanie Billa działa już od kilku godzin na moim telefonie Dell E4310 i nie wiem, kiedy to się skończy, mimo że działa na indeksie zasięgu (stąd using index w EXPLAIN).

Mam kilka innych rozwiązań opartych na tych samych pomysłach:

  • jeśli indeks bazowy to indeks BTREE (co zwykle ma miejsce), największą (group_id, item_value)parą jest ostatnia z nich group_id, czyli pierwsza dla każdegogroup_id jeśli przejdziemy przez indeks w kolejności malejącej;
  • jeśli odczytujemy wartości objęte indeksem, wartości są odczytywane w kolejności indeksu;
  • każdy indeks domyślnie zawiera dołączone do niego kolumny klucza podstawowego (tzn. klucz podstawowy znajduje się w indeksie zasięgu). W poniższych rozwiązaniach działam bezpośrednio na kluczu podstawowym, w twoim przypadku wystarczy po prostu dodać kolumny klucza podstawowego w wyniku.
  • w wielu przypadkach o wiele tańsze jest zebranie wymaganych identyfikatorów wierszy w wymaganej kolejności w podzapytaniu i dołączenie wyniku podzapytania na id. Ponieważ dla każdego wiersza wyniku podkwerendy MySQL będzie wymagało pojedynczego pobrania na podstawie klucza podstawowego, podkwerenda zostanie umieszczona jako pierwsza w sprzężeniu, a wiersze będą wyprowadzane w kolejności identyfikatorów w podkwerendie (jeśli pominiemy jawne ORDER BY do przyłączenia)

3 sposoby, w jakie MySQL używa indeksów, to świetny artykuł, aby zrozumieć niektóre szczegóły.

Rozwiązanie 1

Ten jest niesamowicie szybki, zajmuje mi około 0,8 sekundy w moich rzędach ponad 18 milionów:

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

Jeśli chcesz zmienić kolejność na ASC, umieść ją w podzapytaniu, zwróć tylko identyfikatory i użyj tego jako podzapytania, aby dołączyć do reszty kolumn:

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;

To zajmuje około 1,2 sekundy 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 o wiele wolniejszy, ponieważ wykonuje pełne skanowanie indeksu, ale jest tutaj, aby dać ci wyobrażenie, jak wyprowadzać N max wierszy dla każdej grupy.

Wadą zapytania jest to, że jego wynik nie może być buforowany przez pamięć podręczną zapytania.


Link do zrzutu swoich tabel, aby ludzie mogli przetestować go na swoich platformach.
Pacerier

3
Rozwiązanie 1 nie działa, nie można wybrać request_id bez grupowania według klauzuli,
giò

2
@ giò, to odpowiedź ma 5 lat. Do czasu MySQL 5.7.5 ONLY_FULL_GROUP_BY był domyślnie wyłączony, a to rozwiązanie działało po wyjęciu z pudełka dev.mysql.com/doc/relnotes/mysql/5.7/en/… . Teraz nie jestem pewien, czy rozwiązanie nadal działa po wyłączeniu trybu, ponieważ zmieniono implementację GROUP BY.
newtover

Jeśli chciałbyś ASC w pierwszym rozwiązaniu, czy zadziałałoby, jeśli ustawisz MAX na MIN?
Jin

@JinIzzraeel, domyślnie masz MIN u góry każdej grupy (jest to kolejność indeksu obejmującego): SELECT test_id, request_id FROM testresults GROUP BY test_id;zwraca minimalny identyfikator żądania dla każdego identyfikatora testu.
nowość

101

Użyj swojego podzapytania aby zwrócić prawidłowe grupowanie, 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 maksymalnie:

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 podkwerend i / lub porządkowania w swoich podkwerendach, które zwykle są bardzo wolne / nieefektywne.


1
Zwróć uwagę na rozwiązanie other_col: jeśli ta kolumna nie jest unikalna, możesz odzyskać wiele rekordów z tym samym name, jeśli są one powiązane max(other_col). Znalazłem ten post, który opisuje rozwiązanie dla moich potrzeb, w którym potrzebuję dokładnie jednego rekordu na name.
Eric Simonton,

W niektórych sytuacjach możesz użyć tylko tego rozwiązania, ale tylko zaakceptowane.
tom10271,

Z mojego doświadczenia wynika, że grupowanie całej tabeli przeklętych wiadomości jest powolne / nieefektywne! Innymi słowy, zwróć uwagę, że podkwerenda wymaga pełnego skanowania tabeli i wykonuje grupowanie w celu uruchomienia ... chyba że optymalizator robi coś, czego mój nie robi. Zatem to rozwiązanie zależy w dużej mierze od utrzymania całego stołu w pamięci.
Timo,

Ci skorzystaliby INDEX(name, id)iINDEX(name, other_col)
Rick James

55

Doszedłem do innego rozwiązania, które polega na uzyskaniu identyfikatorów ostatniego postu w każdej grupie, a następnie wybranie z tabeli komunikatów przy użyciu wyniku z pierwszego zapytania jako argumentu dla WHERE x INkonstrukcji:

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 niektórymi innymi rozwiązaniami, ale zadziwiająco zadziałało na moim stole z ponad 3 milionami wierszy. (4 sekundy wykonania z wynikami ponad 1200)

Powinno to działać zarówno na MySQL, jak i SQL Server.


Upewnij się tylko, że masz indeks (nazwa, identyfikator).
Samuel Åslund

1
Znacznie lepiej, że ja dołączy
anwerj

Nauczyłem się od ciebie czegoś, co jest dobrą robotą, a to zapytanie jest szybsze
Humphrey

33

Rozwiązanie przez sub kwerendę Fiddle Link

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

Rozwiązanie Łącząc warunek łącze skrzypce

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 do skrzypiec. Ten sam kod SQL jest już podany w innych odpowiedziach.


1
@AlexanderSuraphel mysql5.5 nie jest teraz dostępny w skrzypcach, łącze do skrzypek zostało utworzone przy użyciu tego. Teraz skrzypce dni obsługuje mysql5.6, zmieniłem bazę danych na mysql 5.6 i jestem w stanie zbudować schemat i uruchomić SQL.
Vipin

8

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

Zakłada się, że idjest uporządkowane tak, jak potrzebujesz. W ogólnym przypadku potrzebna jest inna kolumna.
Rick James

6

Oto dwie sugestie. 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 id. Jeśli nie, zmień odpowiednio klauzulę ORDER BY w oknie ROW_NUMBER (). Jeśli ROW_NUMBER () nie jest dostępne, jest to inne rozwiązanie:

Po drugie, jeśli nie, jest to często dobry sposób na kontynuację:

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 wiadomości z późniejszym identyfikatorem o tej samej nazwie.


8
MySQL nie obsługuje ROW_NUMBER () ani CTE.
Bill Karwin,

1
Obsługa MySQL 8.0 (i MariaDB 10.2) ROW_NUMBER()i CTE.
Rick James

6

Nie testowałem jeszcze z dużą DB, ale myślę, że może to być szybsze niż dołączanie do tabel:

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

14
Zwraca to dowolne dane. Innymi słowy zwrócone kolumny mogą nie pochodzić z rekordu o wartości MAX (Id).
zaszkodzi

Przydatne do wyboru maksymalnego Id z zestawu rekordów z warunkiem GDZIE: „WYBIERZ Max (Id) Z Prod. GDZIE Pn = '” + Pn + „” „Zwraca maksymalny Id z zestawu rekordów o tym samym Pn.In c # użyj reader.GetString (0), aby uzyskać wynik
Nicola

5

Oto inny sposób na uzyskanie ostatniego powiązanego rekordu za GROUP_CONCATpomocą kolejności według i SUBSTRING_INDEXwybranie jednego 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_Columnsktóre są w tej samej Namegrupie, a użycie ORDER BY id DESCłączy wszystkie Other_Columnsw określonej grupie w malejącej kolejności z dostarczonym separatorem w moim przypadku, którego użyłem ||, używającSUBSTRING_INDEX tej listy spowoduje wybranie pierwszego

Fiddle Demo


Pamiętaj, że group_concat_max_lenogranicza to liczbę wierszy, które możesz obsłużyć.
Rick James

5

Oczywiste jest, że istnieje wiele różnych sposobów uzyskiwania takich samych wyników, wydaje się, że Twoim pytaniem jest skuteczny sposób uzyskania ostatnich wyników w każdej grupie w MySQL. Jeśli pracujesz z ogromną ilością danych i zakładasz, że używasz InnoDB nawet z najnowszymi wersjami MySQL (takimi jak 5.7.21 i 8.0.4-rc), może nie być skutecznego sposobu na zrobienie tego.

Czasami musimy to zrobić w przypadku tabel zawierających nawet ponad 60 milionów wierszy.

W tych przykładach wykorzystam dane zawierające 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 (co hipotetycznie nie wymagałoby badania bardzo dużej ilości 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 temperatur zawiera około 1,5 miliona losowych rekordów i 100 różnych grup. Grupa selected_group jest wypełniona tymi 100 grupami (w naszych przypadkach byłoby to zwykle mniej niż 20% dla wszystkich grup).

Ponieważ dane te są losowe, oznacza to, że wiele wierszy może mieć takie same zarejestrowane znaczniki 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, to ostatni pasujący identyfikator tych wierszy.

Gdyby hipotetycznie MySQL miał funkcję last (), która zwracała wartości z ostatniego wiersza w specjalnej klauzuli ORDER BY, moglibyśmy 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 w tym przypadku musiałby zbadać tylko kilka 100 wierszy, ponieważ nie używa żadnej z normalnych funkcji GROUP BY. Wykonałoby się to w 0 sekund, a zatem byłoby bardzo wydajne. Zauważ, że normalnie w MySQL zobaczylibyśmy klauzulę ORDER BY następującą po klauzuli GROUP BY, jednak ta klauzula ORDER BY służy do określenia ORDER dla funkcji last (), gdyby była po GROUP BY, wówczas zamawiałaby GRUPY. Jeśli nie ma klauzuli GROUP BY, ostatnie wartości będą takie same we wszystkich zwróconych wierszach.

Jednak MySQL tego nie ma, więc przyjrzyjmy się różnym pomysłom na to, co ma i udowodnij, że żadne z nich nie jest wydajne.

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
);

Przebadano 3 009 254 wierszy i zajęło ~ 0,859 sekund 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;

Przebadano 15050331 rzędów i zajęło ~ 1,25 sekundy 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;

Przebadano 3 009 685 wierszy i zajęło ~ 1,95 sekundy na 57,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
    )
);

Przebadano 6 137 810 wierszy i zajęło ~ 2,2 sekundy na 57,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 6017808 rzędów 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 6017908 rzędów i zajęło ~ 17,5 sekundy 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;

Ten trwał wiecznie, więc musiałem go zabić.


To inny problem. Rozwiązaniem jest ogromne zapytanie UNION ALL.
Paul Spiegel,

@PaulSpiegel Chyba żartujesz z ogromnego UNION ALL. Poza tym, że trzeba znać wszystkie wybrane grupy z wyprzedzeniem, a przy 2000 wybranych grupach byłoby to niezwykle duże zapytanie, działałoby nawet gorzej niż najszybszy przykład powyżej, więc nie, to nie byłby rozwiązanie.
Yoseph,

Jestem absolutnie poważny. Testowałem to w przeszłości z kilkoma setkami grup. Gdy potrzebujesz obsługi więzi w dużych grupach, UNION ALL jest jedynym sposobem na MySQL, aby wymusić optymalny plan wykonania. SELECT DISTINCT(groupID)jest szybki i da ci wszystkie dane potrzebne do zbudowania takiego zapytania. Rozmiar zapytania powinien być w porządku, o ile nie przekracza on max_allowed_packet, co domyślnie wynosi 4 MB w MySQL 5.7.
Paul Spiegel,

5

przyjrzymy się, jak możesz użyć MySQL do uzyskania ostatniego rekordu w grupie według rekordów. Na przykład, jeśli masz ten zestaw wyników postów.

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

Chcę być w stanie uzyskać ostatni post w każdej kategorii, którą są tytuł 3, tytuł 5 i tytuł 6. Aby uzyskać posty według kategorii, będziesz używać klawiatury MySQL Group By.

select * from posts group by category_id

Ale wyniki, które otrzymujemy z tego zapytania, są.

id category_id post_title

1 1 Title 1

4 2 Title 4

6 3 Title 6

Grupa według zawsze zwróci pierwszy rekord w grupie z zestawu wyników.

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

Spowoduje to zwrócenie postów o najwyższych identyfikatorach w każdej grupie.

id category_id post_title

3 1 Title 3

5 2 Title 5

6 3 Title 6

Odniesienie Kliknij tutaj


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

Czy mógłbyś rozwinąć nieco swoją odpowiedź? Dlaczego twoje zapytanie jest lepsze niż oryginalne zapytanie Vijays?
janfoeh

4

Oto moje rozwiązanie:

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

To nie zwraca najnowszej wiadomości według nazwy. I to tylko skomplikowana wersja SELECT NAME, MAX(MESSAGES) MESSAGES FROM MESSAGE GROUP BY NAME.
Paul Spiegel,

Ponadto ten preparat jest rażąco nieefektywny.
Rick James

3

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 )  

3

Cześć @Vijay Dev, jeśli wiadomości w tabeli zawierają identyfikator, który jest kluczem podstawowym automatycznego przyrostu, to aby pobrać najnowszą bazę rekordów na kluczu podstawowym, zapytanie powinno brzmieć jak poniżej:

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

Ten najszybszy, jaki znalazłem
CORSAIR,

3

Możesz także zobaczyć widok z tego miejsca.

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 ;

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

3

**

Cześć, to zapytanie może pomóc:

**

SELECT 
  *
FROM 
  message 

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

2

Czy jest jakiś sposób, aby użyć tej metody do usuwania duplikatów w tabeli? Zestaw wyników jest w zasadzie zbiorem unikatowych rekordów, więc jeśli moglibyśmy usunąć wszystkie rekordy spoza zestawu wyników, nie mielibyśmy 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ć dane wyjściowe w zmiennej temp, a następnie usunąć z NOT IN (zmienna temp)? @Bill dzięki za bardzo przydatne rozwiązanie.

EDYCJA: Myślę, że 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);

2

Poniższe zapytanie będzie działało poprawnie zgodnie z Twoim pytaniem.

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

Jeśli chcesz mieć ostatni wiersz dla każdego Name, możesz podać numer wiersza każdej grupie wierszy według Namei uporządkować według Idmalejącej kolejności.

PYTANIE

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 Fiddle


2

Co powiesz na to:

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

Miałem podobny problem (trudny postgresql) i tabelę rekordów 1M. To rozwiązanie zajmuje 1,7 w porównaniu do 44 wyprodukowanych przez tę z LEFT JOIN. W moim przypadku musiałem przefiltrować odpowiedni parametr Twojego pola nazwy względem wartości NULL, co skutkuje jeszcze lepszą wydajnością o 0,2 sekundy


1

Jeśli naprawdę zależy Ci na wydajności, możesz wprowadzić nową kolumnę w tabeli o nazwie IsLastInGroup typu BIT.

Ustaw wartość true w kolumnach, które są ostatnie i zachowaj ją przy każdym wstawianiu / aktualizacji / usuwaniu wiersza. Pisanie będzie wolniejsze, ale zyskasz na czytaniach. To zależy od twojego przypadku użycia i polecam tylko, jeśli jesteś skoncentrowany na czytaniu.

Twoje zapytanie będzie wyglądać następująco:

SELECT * FROM Messages WHERE IsLastInGroup = 1

Niektóre tabele w Moodle mają taką kolumnę flagi jak ta.
Lawrence


0

Możesz grupować, licząc, a także uzyskać ostatni element grupy, taki jak:

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

0

Nadzieja poniżej zapytania Oracle może pomóc:

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

Inne podejście:

Znajdź właściwość o maksymalnej cenie m2_z każdym programem (n właściwości w 1 programie):

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
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.