Abrufen des letzten Datensatzes in jeder Gruppe - MySQL


955

Es gibt eine Tabelle messages, die die folgenden Daten enthält:

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

Wenn ich eine Abfrage ausführe select * from messages group by name, erhalte ich folgendes Ergebnis:

1    A       A_data_1
4    B       B_data_1
6    C       C_data_1

Welche Abfrage gibt das folgende Ergebnis zurück?

3    A       A_data_3
5    B       B_data_2
6    C       C_data_1

Das heißt, der letzte Datensatz in jeder Gruppe sollte zurückgegeben werden.

Derzeit ist dies die Abfrage, die ich verwende:

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

Das sieht aber sehr ineffizient aus. Gibt es noch andere Möglichkeiten, um das gleiche Ergebnis zu erzielen?


2
Eine effizientere Lösung finden Sie in der akzeptierten Antwort unter stackoverflow.com/questions/1379565/…
eyaler


7
Warum können Sie nicht einfach DESC hinzufügen, dh * aus der Nachrichtengruppe nach Namen DESC auswählen
Kim Prince


2
@KimPrince Die Antwort, die Sie vorschlagen, scheint nicht das zu tun, was erwartet wird! Ich habe gerade Ihre Methode ausprobiert und es dauerte die ERSTE Zeile für jede Gruppe und bestellte DESC. Es dauert NICHT die letzte Reihe jeder Gruppe
Ayrat

Antworten:


968

MySQL 8.0 unterstützt jetzt Fensterfunktionen, wie fast alle gängigen SQL-Implementierungen. Mit dieser Standardsyntax können wir Abfragen mit den größten n pro Gruppe schreiben:

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;

Unten ist die ursprüngliche Antwort, die ich 2009 auf diese Frage geschrieben habe:


Ich schreibe die Lösung so:

SELECT m1.*
FROM messages m1 LEFT JOIN messages m2
 ON (m1.name = m2.name AND m1.id < m2.id)
WHERE m2.id IS NULL;

In Bezug auf die Leistung kann die eine oder andere Lösung je nach Art Ihrer Daten besser sein. Sie sollten also beide Abfragen testen und die verwenden, die angesichts Ihrer Datenbank eine bessere Leistung bietet.

Zum Beispiel habe ich eine Kopie des StackOverflow August-Datendumps . Ich werde das für das Benchmarking verwenden. Die PostsTabelle enthält 1.114.357 Zeilen . Dies läuft unter MySQL 5.0.75 auf meinem Macbook Pro 2.40GHz.

Ich werde eine Abfrage schreiben, um den neuesten Beitrag für eine bestimmte Benutzer-ID (meine) zu finden.

Verwenden Sie zuerst die von @Eric gezeigte Technik mit der GROUP BYin einer Unterabfrage:

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)

Sogar die EXPLAINAnalyse dauert über 16 Sekunden:

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

Erstellen Sie nun dasselbe Abfrageergebnis mit meiner Technik mit 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)

Die EXPLAINAnalyse zeigt, dass beide Tabellen ihre Indizes verwenden können:

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

Hier ist die DDL für meine PostsTabelle:

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
"Ja wirklich?" Was passiert, wenn Sie eine Menge Einträge haben? Wenn Sie beispielsweise mit einer internen Versionskontrolle arbeiten und eine Menge Versionen pro Datei haben, ist das Join-Ergebnis enorm. Haben Sie jemals die Unterabfragemethode mit dieser verglichen? Ich bin ziemlich neugierig zu wissen, wer gewinnen würde, aber nicht neugierig genug, dich nicht zuerst zu fragen.
Eric

2
Habe ein paar Tests gemacht. Auf einer kleinen Tabelle (~ 300.000 Datensätze, ~ 190.000 Gruppen, also keine massiven Gruppen oder ähnliches) wurden die Abfragen gebunden (jeweils 8 Sekunden).
Eric

1
@ BillKarwin: Siehe meta.stackexchange.com/questions/123017 , insbesondere die Kommentare unter der Antwort von Adam Rackis. Lassen Sie mich wissen, wenn Sie Ihre Antwort auf die neue Frage zurückfordern möchten.
Robert Harvey

3
@ Tim, nein, <=hilft nicht, wenn Sie eine nicht eindeutige Spalte haben. Sie müssen eine eindeutige Spalte als Tiebreaker verwenden.
Bill Karwin

2
Die Leistung nimmt exponentiell ab, wenn die Anzahl der Zeilen zunimmt oder wenn Gruppen größer werden. Zum Beispiel ergibt eine Gruppe bestehend aus 5 Daten 4 + 3 + 2 + 1 + 1 = 11 Zeilen über die linke Verknüpfung, aus der am Ende eine Zeile herausgefiltert wird. Die Leistung beim Verbinden mit gruppierten Ergebnissen ist nahezu linear. Ihre Tests sehen fehlerhaft aus.
Salman A

147

UPD: 2017-03-31, die Version 5.7.5 von MySQL hat den Schalter ONLY_FULL_GROUP_BY standardmäßig aktiviert (daher wurden nicht deterministische GROUP BY-Abfragen deaktiviert). Darüber hinaus wurde die GROUP BY-Implementierung aktualisiert, und die Lösung funktioniert möglicherweise nicht mehr wie erwartet, selbst wenn der Switch deaktiviert ist. Man muss überprüfen.

Die obige Lösung von Bill Karwin funktioniert einwandfrei, wenn die Anzahl der Elemente innerhalb von Gruppen eher gering ist. Die Leistung der Abfrage wird jedoch schlecht, wenn die Gruppen ziemlich groß sind, da für die Lösung n*n/2 + n/2nur IS NULLVergleiche erforderlich sind .

Ich habe meine Tests an einer InnoDB-Tabelle mit 18684446Zeilen mit 1182Gruppen durchgeführt. Die Tabelle enthält Testergebnisse für Funktionstests und hat den (test_id, request_id)als Primärschlüssel. Somit test_idist eine Gruppe und ich habe request_idfür jede nach der letzten gesucht test_id.

Bills Lösung läuft bereits seit mehreren Stunden auf meinem Dell E4310 und ich weiß nicht, wann es fertig sein wird, obwohl es mit einem Coverage-Index arbeitet (daher) using index in EXPLAIN).

Ich habe einige andere Lösungen, die auf denselben Ideen basieren:

  • Wenn der zugrunde liegende Index der BTREE-Index ist (was normalerweise der Fall ist), der größte (group_id, item_value) Paar der letzte Wert in jedem group_id, dh der erste für jedengroup_id wenn wir den Index in absteigender Reihenfolge durchlaufen.
  • Wenn wir die Werte lesen, die von einem Index abgedeckt werden, werden die Werte in der Reihenfolge des Index gelesen.
  • Jeder Index enthält implizit angehängte Primärschlüsselspalten (dh der Primärschlüssel befindet sich im Coverage-Index). In den folgenden Lösungen arbeite ich direkt mit dem Primärschlüssel. In Ihrem Fall müssen Sie dem Ergebnis nur Primärschlüsselspalten hinzufügen.
  • In vielen Fällen ist es viel billiger, die erforderlichen Zeilen-IDs in der erforderlichen Reihenfolge in einer Unterabfrage zu sammeln und das Ergebnis der Unterabfrage mit der ID zu verknüpfen. Da MySQL für jede Zeile im Unterabfrageergebnis einen einzelnen Abruf basierend auf dem Primärschlüssel benötigt, wird die Unterabfrage zuerst in den Join eingefügt und die Zeilen werden in der Reihenfolge der IDs in der Unterabfrage ausgegeben (wenn wir explizites ORDER BY weglassen für den Join)

3 Möglichkeiten, wie MySQL Indizes verwendet, sind ein großartiger Artikel, um einige Details zu verstehen.

Lösung 1

Dieser ist unglaublich schnell, es dauert ungefähr 0,8 Sekunden in meinen 18M + Reihen:

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

Wenn Sie die Reihenfolge in ASC ändern möchten, fügen Sie sie in eine Unterabfrage ein, geben Sie nur die IDs zurück und verwenden Sie diese als Unterabfrage, um sie mit den übrigen Spalten zu verbinden:

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;

Dieser dauert ungefähr 1,2 Sekunden für meine Daten.

Lösung 2

Hier ist eine andere Lösung, die für meinen Tisch ungefähr 19 Sekunden dauert:

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

Es werden auch Tests in absteigender Reihenfolge zurückgegeben. Es ist viel langsamer, da es einen vollständigen Index-Scan durchführt, aber es soll Ihnen eine Vorstellung davon geben, wie N max Zeilen für jede Gruppe ausgegeben werden.

Der Nachteil der Abfrage besteht darin, dass ihr Ergebnis nicht vom Abfragecache zwischengespeichert werden kann.


Bitte verlinken Sie auf einen Speicherauszug Ihrer Tabellen, damit die Benutzer ihn auf ihren Plattformen testen können.
Pacerier

3
Lösung 1 kann nicht funktionieren, Sie können request_id nicht auswählen, ohne dass dies in group by-Klausel enthalten ist
giò

2
@ Giò, diese Antwort ist 5 Jahre alt. Bis MySQL 5.7.5 war ONLY_FULL_GROUP_BY standardmäßig deaktiviert und diese Lösung sofort einsatzbereit war dev.mysql.com/doc/relnotes/mysql/5.7/en/… . Jetzt bin ich mir nicht sicher, ob die Lösung noch funktioniert, wenn Sie den Modus deaktivieren, da die Implementierung von GROUP BY geändert wurde.
Newtover

Wenn Sie ASC in der ersten Lösung wollten, würde es funktionieren, wenn Sie MAX auf MIN umstellen?
Jin

@JinIzzraeel, Sie haben standardmäßig MIN am Anfang jeder Gruppe (dies ist die Reihenfolge des Abdeckungsindex): SELECT test_id, request_id FROM testresults GROUP BY test_id;würde die minimale request_id für jede test_id zurückgeben.
Newtover

101

Verwenden Sie Ihre Unterabfrage , um die richtige Gruppierung zurückzugeben, da Sie auf halbem Weg sind.

Versuche dies:

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

Wenn nicht id, möchten Sie das Maximum von:

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

Auf diese Weise vermeiden Sie korrelierte Unterabfragen und / oder Ordnungen in Ihren Unterabfragen, die in der Regel sehr langsam / ineffizient sind.


1
Beachten Sie eine Einschränkung für die Lösung mit other_col: Wenn diese Spalte nicht eindeutig ist, erhalten Sie möglicherweise mehrere Datensätze mit derselben zurück name, wenn sie übereinstimmen max(other_col). Ich habe diesen Beitrag gefunden , der eine Lösung für meine Bedürfnisse beschreibt, bei der ich genau einen Datensatz pro benötige name.
Eric Simonton

In einigen Situationen können Sie nur diese Lösung verwenden, jedoch nur die akzeptierte.
Tom10271

Nach meiner Erfahrung ist es die Gruppierung der gesamten verdammten Nachrichtentabelle , die dazu neigt, langsam / ineffizient zu sein! Mit anderen Worten, beachten Sie, dass die Unterabfrage einen vollständigen Tabellenscan erfordert und eine Gruppierung darauf durchführt, um zu starten ... es sei denn, Ihr Optimierer tut etwas, was meiner nicht tut. Diese Lösung hängt also stark davon ab, ob die gesamte Tabelle im Speicher bleibt.
Timo

Diese würden von INDEX(name, id)undINDEX(name, other_col)
Rick James

55

Ich bin zu einer anderen Lösung gekommen, nämlich die IDs für den letzten Beitrag in jeder Gruppe abzurufen und dann aus der Nachrichtentabelle das Ergebnis der ersten Abfrage als Argument für ein WHERE x INKonstrukt auszuwählen :

SELECT id, name, other_columns
FROM messages
WHERE id IN (
    SELECT MAX(id)
    FROM messages
    GROUP BY name
);

Ich weiß nicht, wie dies im Vergleich zu einigen anderen Lösungen funktioniert, aber es hat für meinen Tisch mit mehr als 3 Millionen Zeilen spektakulär funktioniert. (4 Sekunden Ausführung mit 1200+ Ergebnissen)

Dies sollte sowohl unter MySQL als auch unter SQL Server funktionieren.


Stellen Sie einfach sicher, dass Sie einen Index für (Name, ID) haben.
Samuel Åslund

1
Viel besser, dass sich selbst anschließt
anwerj

Ich habe etwas von Ihnen gelernt, das ein guter Job ist und diese Abfrage ist schneller
Humphrey

33

Lösung durch Unterabfrage Geige Link

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

Lösung Durch Join-Bedingung Geigen-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

Grund für diesen Beitrag ist, nur Geigenlink zu geben. Dasselbe SQL wird bereits in anderen Antworten bereitgestellt.


1
@AlexanderSuraphel mysql5.5 ist derzeit nicht in fiddle verfügbar, der Geigenlink wurde damit erstellt. Jetzt unterstützt eine Tagesgeige mysql5.6, ich habe die Datenbank in mysql 5.6 geändert und kann ein Schema erstellen und die SQL ausführen.
Vipin

8

Ein Ansatz mit beträchtlicher Geschwindigkeit ist wie folgt.

SELECT * 
FROM messages a
WHERE Id = (SELECT MAX(Id) FROM messages WHERE a.Name = Name)

Ergebnis

Id  Name    Other_Columns
3   A   A_data_3
5   B   B_data_2
6   C   C_data_1

Dies setzt voraus, iddass die Bestellung so erfolgt, wie Sie sie benötigen. Im allgemeinen Fall wird eine andere Spalte benötigt.
Rick James

6

Hier sind zwei Vorschläge. Erstens ist es sehr einfach, wenn MySQL ROW_NUMBER () unterstützt:

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;

Ich gehe davon aus, dass Sie mit "last" das letzte in der ID-Reihenfolge meinen. Wenn nicht, ändern Sie die ORDER BY-Klausel des Fensters ROW_NUMBER () entsprechend. Wenn ROW_NUMBER () nicht verfügbar ist, ist dies eine andere Lösung:

Zweitens ist dies oft ein guter Weg, um fortzufahren:

SELECT
  Id, Name, OtherColumns
FROM messages
WHERE NOT EXISTS (
  SELECT * FROM messages as M2
  WHERE M2.Name = messages.Name
  AND M2.Id > messages.Id
)

Mit anderen Worten, wählen Sie Nachrichten aus, bei denen keine spätere ID-Nachricht mit demselben Namen vorhanden ist.


8
MySQL unterstützt weder ROW_NUMBER () noch CTEs.
Bill Karwin

1
MySQL 8.0 (und MariaDB 10.2) unterstützen jetzt ROW_NUMBER()und CTEs.
Rick James

6

Ich habe noch nicht mit einer großen Datenbank getestet, aber ich denke, dies könnte schneller sein als das Verbinden von Tabellen:

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

14
Dies gibt beliebige Daten zurück. Mit anderen Worten, die zurückgegebenen Spalten stammen möglicherweise nicht aus dem Datensatz mit MAX (Id).
Schaden

Nützlich, um die maximale ID aus einem Datensatz mit der WHERE-Bedingung auszuwählen: "SELECT Max (Id) FROM Prod WHERE Pn = '" + Pn + "'" Es wird die maximale ID aus einem Satz von Datensätzen mit demselben Pn.In c # zurückgegeben Verwenden Sie reader.GetString (0), um das Ergebnis zu erhalten
Nicola

5

Hier ist eine andere Möglichkeit, den letzten zugehörigen Datensatz GROUP_CONCATmit der Reihenfolge von SUBSTRING_INDEXabzurufen und einen der Datensätze aus der Liste auszuwählen

SELECT 
  `Id`,
  `Name`,
  SUBSTRING_INDEX(
    GROUP_CONCAT(
      `Other_Columns` 
      ORDER BY `Id` DESC 
      SEPARATOR '||'
    ),
    '||',
    1
  ) Other_Columns 
FROM
  messages 
GROUP BY `Name` 

Die obige Abfrage gruppiert alle, die Other_Columnssich in derselben NameGruppe befinden, und verwendet ORDER BY id DESCalle Other_Columnsin einer bestimmten Gruppe in absteigender Reihenfolge mit dem bereitgestellten Trennzeichen, das ich in meinem Fall verwendet habe ||. Wenn Sie SUBSTRING_INDEXüber diese Liste verwenden, wird das erste ausgewählt

Geigen-Demo


Beachten Sie, dass dadurch group_concat_max_lendie Anzahl der Zeilen begrenzt wird, die Sie verarbeiten können.
Rick James

5

Natürlich gibt es viele verschiedene Möglichkeiten, um die gleichen Ergebnisse zu erzielen. Ihre Frage scheint zu sein, wie Sie die letzten Ergebnisse in jeder Gruppe in MySQL effizient erzielen können. Wenn Sie mit großen Datenmengen arbeiten und davon ausgehen, dass Sie InnoDB auch mit den neuesten Versionen von MySQL (wie 5.7.21 und 8.0.4-rc) verwenden, gibt es möglicherweise keine effiziente Möglichkeit, dies zu tun.

Manchmal müssen wir dies mit Tabellen mit sogar mehr als 60 Millionen Zeilen tun.

Für diese Beispiele verwende ich Daten mit nur etwa 1,5 Millionen Zeilen, in denen die Abfragen Ergebnisse für alle Gruppen in den Daten finden müssten. In unseren tatsächlichen Fällen müssten wir häufig Daten von etwa 2.000 Gruppen zurückgeben (was hypothetisch nicht erfordern würde, sehr viele Daten zu untersuchen).

Ich werde die folgenden Tabellen verwenden:

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

Die Temperaturtabelle enthält etwa 1,5 Millionen zufällige Datensätze und 100 verschiedene Gruppen. Die selected_group wird mit diesen 100 Gruppen gefüllt (in unseren Fällen wären dies normalerweise weniger als 20% für alle Gruppen).

Da diese Daten zufällig sind, bedeutet dies, dass mehrere Zeilen dieselben aufgezeichneten Zeitstempel haben können. Wir möchten eine Liste aller ausgewählten Gruppen in der Reihenfolge der Gruppen-ID mit dem zuletzt aufgezeichneten Zeitstempel für jede Gruppe erhalten. Wenn dieselbe Gruppe mehr als eine übereinstimmende Zeile hat, dann die letzte übereinstimmende ID dieser Zeilen.

Wenn MySQL hypothetisch eine last () -Funktion hätte, die Werte aus der letzten Zeile in einer speziellen ORDER BY-Klausel zurückgibt, könnten wir einfach Folgendes tun:

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;

In diesem Fall müssten nur einige 100 Zeilen untersucht werden, da keine der normalen GROUP BY-Funktionen verwendet wird. Dies würde in 0 Sekunden ausgeführt und wäre daher hocheffizient. Beachten Sie, dass in MySQL normalerweise eine ORDER BY-Klausel nach der GROUP BY-Klausel angezeigt wird. Diese ORDER BY-Klausel wird jedoch verwendet, um die ORDER für die last () -Funktion zu bestimmen. Wenn sie nach GROUP BY liegt, werden die GROUPS bestellt. Wenn keine GROUP BY-Klausel vorhanden ist, sind die letzten Werte in allen zurückgegebenen Zeilen gleich.

MySQL hat dies jedoch nicht. Schauen wir uns also verschiedene Ideen an und beweisen, dass keines davon effizient ist.

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

Dies untersuchte 3.009.254 Reihen und dauerte am 5.7.21 ~ 0,859 Sekunden und am 8.0.4-rc etwas länger

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

Dies untersuchte 1.505.331 Reihen und dauerte am 5.7.21 ~ 1,25 Sekunden und am 8.0.4-rc etwas länger

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

Dies untersuchte 3.009.685 Reihen und dauerte am 5.7.21 ~ 1,95 Sekunden und am 8.0.4-rc etwas länger

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

Dies untersuchte 6.137.810 Reihen und dauerte am 5.7.21 ~ 2,2 Sekunden und am 8.0.4-rc etwas länger

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

Dies untersuchte 6.017.808 Reihen und dauerte bei 8,0,4-rc ~ 4,2 Sekunden

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

Dies untersuchte 6.017.908 Reihen und dauerte bei 8,0,4-rc ~ 17,5 Sekunden

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

Dieser dauerte ewig, also musste ich ihn töten.


Dies ist ein anderes Problem. Und die Lösung ist eine riesige UNION ALL-Abfrage.
Paul Spiegel

@PaulSpiegel Ich denke, Sie scherzen über die riesige UNION ALL. Abgesehen von der Tatsache, dass man alle ausgewählten Gruppen im Voraus kennen müsste und dass mit 2.000 ausgewählten Gruppen, die eine unglaublich große Abfrage darstellen würden, eine noch schlechtere Leistung erzielt würde als im schnellsten Beispiel oben, also nein, das wäre keine Lösung.
Yoseph

Ich meine es absolut ernst. Ich habe das in der Vergangenheit mit ein paar hundert Gruppen getestet. Wenn Sie Verbindungen in großen Gruppen bearbeiten müssen, ist UNION ALL die einzige Möglichkeit in MySQL, einen optimalen Ausführungsplan zu erzwingen. SELECT DISTINCT(groupID)ist schnell und gibt Ihnen alle Daten, die Sie zum Erstellen einer solchen Abfrage benötigen. Die Abfragegröße sollte in Ordnung sein, solange sie nicht überschritten max_allowed_packetwird. In MySQL 5.7 beträgt der Standardwert 4 MB.
Paul Spiegel

5

Wir werden untersuchen, wie Sie MySQL verwenden können, um den letzten Datensatz in einer Gruppe von Datensätzen abzurufen. Zum Beispiel, wenn Sie diese Ergebnismenge von Beiträgen haben.

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

Ich möchte in der Lage sein, den letzten Beitrag in jeder Kategorie zu erhalten, nämlich Titel 3, Titel 5 und Titel 6. Um die Beiträge nach Kategorie zu erhalten, verwenden Sie die MySQL Group By-Tastatur.

select * from posts group by category_id

Die Ergebnisse dieser Abfrage sind jedoch.

id category_id post_title

1 1 Title 1

4 2 Title 4

6 3 Title 6

Die Gruppe von gibt immer den ersten Datensatz in der Gruppe in der Ergebnismenge zurück.

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

Dadurch werden die Beiträge mit den höchsten IDs in jeder Gruppe zurückgegeben.

id category_id post_title

3 1 Title 3

5 2 Title 5

6 3 Title 6

Referenz Klicken Sie hier


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

Könnten Sie Ihre Antwort etwas näher erläutern? Warum ist Ihre Anfrage der ursprünglichen Anfrage von Vijays vorzuziehen?
Janfoeh

4

Hier ist meine Lösung:

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

Dies gibt nicht die neueste Nachricht pro Name zurück. Und es ist nur eine überkomplizierte Version von SELECT NAME, MAX(MESSAGES) MESSAGES FROM MESSAGE GROUP BY NAME.
Paul Spiegel

Darüber hinaus ist diese Formulierung äußerst ineffizient.
Rick James

3

Versuche dies:

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

Hallo @Vijay Dev wenn Ihre Tabelle Nachrichten enthalten Id , die dann Primärschlüssel Autoinkrement ist die letzte Aufzeichnung Basis auf dem Primärschlüssel zu holen Ihre Abfrage wie unten sollte lauten:

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

Dies ist die schnellste, die ich gefunden habe
CORSAIR

3

Sie können auch von hier aus sehen.

http://sqlfiddle.com/#!9/ef42b/9

ERSTE LÖSUNG

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

ZWEITE LÖSUNG

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

** **.

Hallo, diese Abfrage könnte helfen:

** **.

SELECT 
  *
FROM 
  message 

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

2

Gibt es eine Möglichkeit, mit dieser Methode Duplikate in einer Tabelle zu löschen? Die Ergebnismenge ist im Grunde eine Sammlung eindeutiger Datensätze. Wenn wir also alle Datensätze löschen könnten, die nicht in der Ergebnismenge enthalten sind, hätten wir effektiv keine Duplikate. Ich habe es versucht, aber mySQL hat einen 1093-Fehler ausgegeben.

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)

Gibt es eine Möglichkeit, die Ausgabe in einer temporären Variablen zu speichern und dann aus NOT IN (temporäre Variable) zu löschen? @ Bill danke für eine sehr nützliche Lösung.

EDIT: Ich glaube, ich habe die Lösung gefunden:

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

Die folgende Abfrage funktioniert gemäß Ihrer Frage einwandfrei.

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

Wenn Sie die letzte Zeile für jede Zeile möchten Name, können Sie jeder Zeilengruppe eine Zeilennummer nach Nameund Idin absteigender Reihenfolge geben.

ABFRAGE

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

Wie wäre es damit:

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

Ich hatte ein ähnliches Problem (auf Postgresql hart) und auf einer 1M-Datensatztabelle. Diese Lösung benötigt 1,7s gegenüber 44s, die von der mit LEFT JOIN erstellt wurden. In meinem Fall hatte ich den corrispondant Ihres filtern Namen Feld gegen NULL - Werte, was zu noch besseren Leistungen um 0,2 Sekunden


1

Wenn die Leistung wirklich Ihr Anliegen ist, können Sie eine neue Spalte in der Tabelle IsLastInGroupvom Typ BIT einfügen.

Setzen Sie es in den letzten Spalten auf true und pflegen Sie es bei jedem Einfügen / Aktualisieren / Löschen von Zeilen. Das Schreiben wird langsamer sein, aber Sie profitieren vom Lesen. Dies hängt von Ihrem Anwendungsfall ab und ich empfehle es nur, wenn Sie sich auf das Lesen konzentrieren.

Ihre Anfrage sieht also folgendermaßen aus:

SELECT * FROM Messages WHERE IsLastInGroup = 1

Einige Tabellen in Moodle haben eine solche Flag-Spalte.
Lawrence


0

Sie können durch Zählen gruppieren und erhalten auch das letzte Element der Gruppe wie:

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

0

Hoffe, dass die folgende Oracle-Abfrage helfen kann:

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

Ein anderer Ansatz :

Finden Sie die Eigenschaft mit dem maximalen m2_Preis für jedes Programm (n Eigenschaften in 1 Programm):

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
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.