Rufen Sie die neueste Zeile ab, die in MySQL nach einer Spalte gruppiert ist


7

Mein Problem scheint eine viel einfachere Lösung zu haben als das, was ich mir ausgedacht habe. Beginnend mit diesem Datensatz:

log_table

+--------+-----------+------------------+---------+
| log_id | entity_id |       date       | comment |
+--------+-----------+------------------+---------+
|      1 | A         | 2012-10-23 07:50 | foo     |
|      2 | B         | 2012-10-23 07:59 | bar     |
|      3 | B         | 2012-10-23 08:11 | baz     |
|      4 | A         | 2012-10-23 08:23 | bat     |
+--------+-----------+------------------+---------+

Angenommen, ich wollte das späteste Datum der Protokolleinträge für jede Entität erhalten, damit das Ergebnis wie folgt aussieht:

Results:
+-----------+------------------+--------------+
| entity_id |  last_log_date   | last_comment |
+-----------+------------------+--------------+
| B         | 2012-10-23 08:11 | baz          |
| A         | 2012-10-23 08:23 | bat          |
+-----------+------------------+--------------+

Ich verwende derzeit MySQL, das ungefähr so ​​aussieht:

SELECT
  `entity_id`,
  `date` AS last_log_date,
  `comment` AS last_comment
FROM (
  SELECT *
  FROM `log_table`
  ORDER BY `date` DESC, log_id ASC
) AS `ordered_log`
GROUP BY `entity_id`

Das funktioniert gut, aber es scheint mir nicht sehr effizient zu sein. Es muss doch einen besseren Weg geben, dies zu tun.

Antworten:


4

Erstellen Sie eine Unterabfrage, die Schlüssel aus dem log_tablemit dem maximalen Datum pro Entität sammelt . Führen Sie dann einen INNER JOIN dieser Unterabfrage zurück zum log_table.

SELECT
    B.entity_id,B.last_log_date,B.last_comment
FROM
(
    SELECT entity_id,MAX(last_log_date) last_log_date
    FROM log_table GROUP BY entity_id
) A INNER JOIN B USING (entity_id,last_log_date);

Versuche es !!!

Sie können dies wirklich beschleunigen, wenn Sie einen solchen zusammengesetzten Index haben

ALTER TABLE log_table ADD INDEX entity_date_ndx (entity_id,last_log_date);

Indizes in jeder einzelnen Spalte können zu Indexzusammenführungen führen. Dieser zusammengesetzte Index umgeht das.

Bitte versuchen Sie es LEFT JOINstattdessenINNER JOIN

SELECT
    B.entity_id,B.last_log_date,B.last_comment
FROM
(
    SELECT entity_id,MAX(last_log_date) last_log_date
    FROM log_table GROUP BY entity_id
) A LEFT JOIN B USING (entity_id,last_log_date);

Ok, diese Abfrage scheint effizienter zu sein. In einer Tabelle mit 87.283 verschiedenen entity_id- und 1.309.252 Datensätzen dauert meine Abfrage zu lange (mehr als 50 Sekunden, bis ich die Abfrage beendet habe), aber Ihre Abfrage dauert etwas mehr als 11 Sekunden. Gibt es eine Möglichkeit, dies weiter zu beschleunigen? Ich habe bereits Indizes für die entity_idund die dateSpalten. Idealerweise sollte die Abfrage mehr oder weniger sofort ausgeführt werden, wenn möglich ...
Asgrim

@Asgrim Haben Sie zwei separate Indizes für die entity_idund dateSpalten oder einen Index über beide Spalten?
Matts

@matts Gemäß der bearbeiteten Antwort von @ RolandoMySQLDBA habe ich den Spalten einen zusammengesetzten Index hinzugefügt, was keinen Unterschied machte. Das Ausführen nur der Unterabfrage selbst (dh des SELECT entity_id,MAX(last_log_date) last_log_date FROM log_table GROUP BY entity_idTeils) und das ist die Zeit, die zum Ausführen benötigt wird (es dauert immer noch 11 Sekunden). Es scheint mir, dass dies nicht schneller gehen wird?
Asgrim

1
Bitte ändern Sie INNER JOIN in LEFT JOIN, um zu sehen, dass die Reihenfolge der Unterabfragen erhalten bleibt und schneller ist.
RolandoMySQLDBA

@RolandoMySQLDBA Das verbessert es zwar leicht, aber das Ausführen nur der Unterabfrage (dh SELECT entity_id, MAX(`date`) last_log_date FROM log_table GROUP BY entity_id) dauert allein 8 Sekunden. Was ich in meinem letzten Kommentar gefragt habe, ist, ob es eine Möglichkeit gibt, die Leistung nur dieser Abfrage erheblich zu steigern - das würde höchstwahrscheinlich alle Probleme lösen ...
Asgrim

3

Die Unterabfrage funktioniert; So würden Sie es ohne Unterabfrage machen:

SELECT
  `entity_id`,
  SUBSTRING_INDEX(GROUP_CONCAT(`date` ORDER BY `date` DESC), ',', 1) AS last_log_date,
  SUBSTRING_INDEX(GROUP_CONCAT(`comment` ORDER BY `date` DESC), ',', 1) AS last_comment
FROM `log_table`
GROUP BY `entity_id`

Die obige Abfrage wird verwendet GROUP_CONCAT, um eine lange Verkettung von Werten pro Gruppe zu generieren, die dann analysiert wird, um das erste Token über zu extrahieren SUBSTRING_INDEX.

Sie könnten es auf hervorragende Weise lösen, wenn nur MySQL Fensterfunktionen (auch als analytische Funktionen bezeichnet) unterstützt. Es tut es nicht und wir bleiben mit Hacks herum GROUP_CONCAT.


Danke - eine andere Antwort, die auch funktioniert hat, aber genauso langsam war wie die andere. Es scheint auch ein bisschen hackiger zu sein, aber nett zu denken :)
Asgrim

Welche Schlüssel haben Sie auf diesem Tisch? Ein KEY (entity_id, date) sollte für meine Abfrage gut geeignet sein.
Shlomi Noach

Wie ich in den Kommentaren in der Antwort von @ RolandoMySQLDBA beschrieben habe, machte das Hinzufügen eines zusammengesetzten Schlüssels keinen Unterschied. Das Problem am Ende war die große Datenmenge im TEXT-Feld in der Kommentarspalte, was bedeutet, dass zu viel Festplatten gesucht wurde. Ich habe die Unterabfrage einem GROUP_CONCAT-Hack vorgezogen, das ist alles.
Asgrim
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.