Recupero dell'ultimo record in ciascun gruppo: MySQL


958

C'è una tabella messagesche contiene i dati come mostrato di seguito:

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

Se eseguo una query select * from messages group by name, otterrò il risultato come:

1    A       A_data_1
4    B       B_data_1
6    C       C_data_1

Quale query restituirà il seguente risultato?

3    A       A_data_3
5    B       B_data_2
6    C       C_data_1

Cioè, l'ultimo record in ciascun gruppo dovrebbe essere restituito.

Al momento, questa è la query che utilizzo:

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

Ma questo sembra altamente inefficiente. Altri modi per ottenere lo stesso risultato?


2
vedere la risposta accettata in stackoverflow.com/questions/1379565/… per una soluzione più efficiente
eyaler


7
Perché non puoi semplicemente aggiungere DESC, cioè selezionare * dal gruppo di messaggi per nome DESC
Kim Prince


2
@KimPrince Sembra che la risposta che stai suggerendo non faccia quello che ci si aspetta! Ho appena provato il tuo metodo e ci è voluto PRIMA riga per ogni gruppo e ho ordinato DESC. NON occupa l'ultima riga di ciascun gruppo
Ayrat

Risposte:


970

MySQL 8.0 ora supporta le funzioni di windowing, come quasi tutte le più diffuse implementazioni SQL. Con questa sintassi standard, possiamo scrivere query di tipo n per gruppo:

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;

Di seguito è la risposta originale che ho scritto per questa domanda nel 2009:


Scrivo la soluzione in questo modo:

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

Per quanto riguarda le prestazioni, una soluzione o l'altra può essere migliore, a seconda della natura dei dati. Pertanto, è necessario testare entrambe le query e utilizzare quella con prestazioni migliori rispetto al database.

Ad esempio, ho una copia di dump di dati StackOverflow di agosto . Lo userò per il benchmarking. Ci sono 1.114.357 righe nella Poststabella. Questo è in esecuzione su MySQL 5.0.75 sul mio Macbook Pro 2.40GHz.

Scriverò una query per trovare il post più recente per un determinato ID utente (il mio).

Innanzitutto utilizzando la tecnica mostrata da @Eric con ilGROUP BY in una sottoquery:

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)

Anche l' EXPLAINanalisi richiede oltre 16 secondi:

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

Ora produce lo stesso risultato della query usando la mia tecnica conLEFT 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)

Il EXPLAIN analisi mostra che entrambe le tabelle sono in grado di utilizzare i loro indici:

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

Ecco il DDL per il mio Poststavolo:

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
Veramente? Cosa succede se hai un sacco di voci? Ad esempio, se stai lavorando con un controllo interno della versione, diciamo, e hai un sacco di versioni per file, il risultato del join sarebbe enorme. Hai mai confrontato il metodo di subquery con questo? Sono abbastanza curioso di sapere quale vincerebbe, ma non abbastanza curioso da non chiedertelo prima.
Eric,

2
Ho fatto dei test. Su un tavolino (~ 300k record, ~ 190k gruppi, quindi gruppi non massicci o altro), le query sono legate (8 secondi ciascuna).
Eric,

1
@BillKarwin: vedi meta.stackexchange.com/questions/123017 , in particolare i commenti sotto la risposta di Adam Rackis. Fammi sapere se vuoi richiedere la tua risposta sulla nuova domanda.
Robert Harvey,

3
@Tim, no, <=non ti aiuterà se hai una colonna non univoca. È necessario utilizzare una colonna unica come tiebreaker.
Bill Karwin,

2
Le prestazioni diminuiscono in modo esponenziale all'aumentare del numero di righe o quando i gruppi diventano più grandi. Ad esempio un gruppo composto da 5 date produrrà 4 + 3 + 2 + 1 + 1 = 11 righe tramite join sinistro da cui una riga viene filtrata alla fine. Le prestazioni di unione con risultati raggruppati sono quasi lineari. I tuoi test sembrano imperfetti.
Salman A

148

UPD: 2017-03-31, la versione 5.7.5 di MySQL ha reso l'opzione ONLY_FULL_GROUP_BY abilitata per impostazione predefinita (quindi, le query GROUP BY non deterministiche sono state disabilitate). Inoltre, hanno aggiornato l'implementazione GROUP BY e la soluzione potrebbe non funzionare più come previsto anche con l'opzione disabilitata. Uno deve controllare.

La soluzione di Bill Karwin sopra funziona benissimo quando il conteggio degli articoli all'interno dei gruppi è piuttosto piccolo, ma le prestazioni della query peggiorano quando i gruppi sono piuttosto grandi, poiché la soluzione richiede circa n*n/2 + n/2 solo dei IS NULLconfronti.

Ho effettuato i test su una tabella di 18684446righe InnoDB con 1182gruppi. La tabella contiene i risultati di test per i test funzionali e ha (test_id, request_id)come chiave primaria. Quindi, test_idè un gruppo e stavo cercando l'ultimo request_idper ciascunotest_id .

La soluzione di Bill è già in esecuzione da diverse ore sul mio Dell e4310 e non so quando finirà anche se funziona su un indice di copertura (quindi using index in EXPLAIN).

Ho un paio di altre soluzioni basate sulle stesse idee:

  • se l'indice sottostante è l'indice BTREE (che di solito è il caso), la (group_id, item_value)coppia più grande è l'ultimo valore all'interno di ciascuno group_id, che è il primo per ciascuno group_idse percorriamo l'indice in ordine decrescente;
  • se leggiamo i valori coperti da un indice, i valori vengono letti nell'ordine dell'indice;
  • ogni indice contiene implicitamente colonne di chiavi primarie aggiunte a quella (ovvero la chiave primaria è nell'indice di copertura). Nelle soluzioni seguenti opero direttamente sulla chiave primaria, nel tuo caso, dovrai solo aggiungere colonne di chiave primaria nel risultato.
  • in molti casi è molto più economico raccogliere gli ID di riga richiesti nell'ordine richiesto in una sottoquery e unire il risultato della sottoquery sull'ID. Poiché per ogni riga nel risultato della sottoquery MySQL avrà bisogno di un singolo recupero basato sulla chiave primaria, la sottoquery verrà inserita per prima nel join e le righe verranno emesse nell'ordine degli ID nella sottoquery (se omettiamo esplicitamente ORDER BY per il join)

3 modi in cui MySQL utilizza gli indici è un ottimo articolo per comprendere alcuni dettagli.

Soluzione 1

Questo è incredibilmente veloce, ci vogliono circa 0,8 secondi sulle mie 18M + file:

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

Se si desidera modificare l'ordine in ASC, inserirlo in una sottoquery, restituire solo gli ID e utilizzarlo come sottoquery per unirsi al resto delle colonne:

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;

Questo richiede circa 1,2 secondi sui miei dati.

Soluzione 2

Ecco un'altra soluzione che richiede circa 19 secondi per il mio tavolo:

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

Restituisce anche i test in ordine decrescente. È molto più lento poiché esegue una scansione completa dell'indice ma è qui per darti un'idea di come produrre N max righe per ciascun gruppo.

Lo svantaggio della query è che il suo risultato non può essere memorizzato nella cache della query.


Collegati a un dump dei tuoi tavoli in modo che le persone possano testarlo sulle loro piattaforme.
Pacerier,

3
La soluzione 1 non può funzionare, non è possibile selezionare request_id senza averla raggruppata per clausola,
giò

2
@ giò, questa è la risposta ha 5 anni. Fino a quando MySQL 5.7.5 ONLY_FULL_GROUP_BY è stato disabilitato per impostazione predefinita e questa soluzione ha funzionato immediatamente dev.mysql.com/doc/relnotes/mysql/5.7/it/… . Ora non sono sicuro che la soluzione funzioni ancora quando si disattiva la modalità, poiché l'implementazione di GROUP BY è stata modificata.
novità

Se volessi ASC nella prima soluzione, funzionerebbe se giri MAX in MIN?
Jin

@JinIzzraeel, hai MIN di default nella parte superiore di ogni gruppo (è l'ordine dell'indice di copertura): SELECT test_id, request_id FROM testresults GROUP BY test_id;restituirebbe il request_id minimo per ogni test_id.
Newtover

102

Usa la tua subquery per restituire il raggruppamento corretto, perché sei a metà strada.

Prova questo:

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

In caso contrario, idsi desidera il massimo di:

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

In questo modo, si evitano subquery correlate e / o ordini nelle subquery, che tendono ad essere molto lente / inefficienti.


1
Nota un avvertimento per la soluzione con other_col: se quella colonna non è unica puoi recuperare più record con lo stesso name, se vincolano max(other_col). Ho trovato questo post che descrive una soluzione per le mie esigenze, dove ho bisogno esattamente di un record per name.
Eric Simonton,

In alcune situazioni è possibile utilizzare solo questa soluzione ma solo quella accettata.
tom10271

Nella mia esperienza, sta raggruppando l'intera dannata tabella dei messaggi che tende ad essere lenta / inefficiente! In altre parole, nota che la sottoquery richiede una scansione completa della tabella e fa un raggruppamento su quella per l'avvio ... a meno che il tuo ottimizzatore non stia facendo qualcosa che il mio non è. Quindi questa soluzione dipende fortemente dal mantenere l'intero tavolo in memoria.
Timo,

Quelli ne trarrebbero beneficio INDEX(name, id)eINDEX(name, other_col)
Rick James, il

55

Sono arrivato a una soluzione diversa, che è quella di ottenere gli ID per l'ultimo post all'interno di ciascun gruppo, quindi selezionare dalla tabella dei messaggi utilizzando il risultato della prima query come argomento per un WHERE x INcostrutto:

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

Non so come funzioni rispetto ad alcune delle altre soluzioni, ma ha funzionato in modo spettacolare per la mia tabella con oltre 3 milioni di righe. (4 secondi di esecuzione con oltre 1200 risultati)

Questo dovrebbe funzionare sia su MySQL che su SQL Server.


Assicurati solo di avere un indice su (nome, ID).
Samuel Åslund,

1
Molto meglio che il sé si unisca
anwerj il

Ho imparato qualcosa da te che è un buon lavoro e questa domanda è più veloce
Humphrey,

33

Soluzione per sottointerrogazione fiddle Link

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

Soluzione Mediante collegamento condizioni violino

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

Il motivo di questo post è quello di fornire solo il link violino. Lo stesso SQL è già fornito in altre risposte.


1
@AlexanderSuraphel mysql5.5 non è ora disponibile in fiddle, il link fiddle è stato creato usando quello. Ora un violino di giorni supporta mysql5.6, ho cambiato il database in mysql 5.6 e sono in grado di costruire schemi ed eseguire sql.
Vipin,

8

Un approccio con notevole velocità è il seguente.

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

Risultato

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

Ciò presuppone che idsia ordinato nel modo desiderato. Nel caso generale è necessaria qualche altra colonna.
Rick James,

6

Ecco due suggerimenti. Innanzitutto, se mysql supporta ROW_NUMBER (), è molto semplice:

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;

Suppongo che per "ultimo" intendi l'ultimo nell'ordine ID. In caso contrario, modificare di conseguenza la clausola ORDER BY della finestra ROW_NUMBER (). Se ROW_NUMBER () non è disponibile, questa è un'altra soluzione:

In secondo luogo, in caso contrario, questo è spesso un buon modo di procedere:

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

In altre parole, selezionare i messaggi in cui non è presente un messaggio ID successivo con lo stesso nome.


8
MySQL non supporta ROW_NUMBER () o CTE.
Bill Karwin,

1
MySQL 8.0 (e MariaDB 10.2) ora supportano ROW_NUMBER()e CTE.
Rick James,

6

Non ho ancora testato con DB di grandi dimensioni ma penso che questo potrebbe essere più veloce di unire le tabelle:

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

14
Ciò restituisce dati arbitrari. In altre parole, le colonne restituite potrebbero non provenire dal record con MAX (Id).
danni

Utile per selezionare l'ID massimo da un set di record con condizione WHERE: "SELEZIONA Max (Id) DA Prod WHERE Pn = '" + Pn + "'" Restituisce l'ID massimo da un set di record con lo stesso Pn.In c # usa reader.GetString (0) per ottenere il risultato
Nicola

5

Ecco un altro modo per ottenere l'ultimo record correlato usando GROUP_CONCATcon order by e SUBSTRING_INDEXper selezionare uno dei record dall'elenco

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

La query sopra raggrupperà tutto Other_Columnsciò che è nello stesso Namegruppo e usando ORDER BY id DESCsi unirà tutto Other_Columnsin un gruppo specifico in ordine decrescente con il separatore fornito nel mio caso che ho usato ||, usandoSUBSTRING_INDEX su questo elenco sceglierà il primo

Demo di violino


Tieni presente che group_concat_max_lenlimita il numero di righe che puoi gestire.
Rick James,

5

Chiaramente ci sono molti modi diversi per ottenere gli stessi risultati, la tua domanda sembra essere quale sia un modo efficace per ottenere gli ultimi risultati in ciascun gruppo in MySQL. Se stai lavorando con enormi quantità di dati e supponendo che tu stia utilizzando InnoDB anche con le ultime versioni di MySQL (come 5.7.21 e 8.0.4-rc), potrebbe non esserci un modo efficace per farlo.

A volte dobbiamo farlo con tabelle con anche più di 60 milioni di righe.

Per questi esempi userò i dati con solo circa 1,5 milioni di righe in cui le query dovrebbero trovare risultati per tutti i gruppi nei dati. Nei nostri casi reali avremmo spesso bisogno di restituire dati da circa 2.000 gruppi (che ipoteticamente non richiederebbero di esaminare gran parte dei dati).

Userò le seguenti tabelle:

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

La tabella delle temperature è popolata con circa 1,5 milioni di record casuali e con 100 gruppi diversi. Il gruppo selezionato è popolato con quei 100 gruppi (nei nostri casi questo sarebbe normalmente inferiore al 20% per tutti i gruppi).

Poiché questi dati sono casuali, significa che più righe possono avere gli stessi Timestamp registrati. Ciò che vogliamo è ottenere un elenco di tutti i gruppi selezionati in ordine di ID gruppo con l'ultimo TestTest registrato per ciascun gruppo e se lo stesso gruppo ha più di una riga corrispondente come quella, allora l'ultimo ID corrispondente di quelle righe.

Se ipoteticamente MySQL avesse una funzione last () che restituiva valori dall'ultima riga in una speciale clausola ORDER BY, allora potremmo semplicemente fare:

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;

che dovrebbe solo esaminare alcune 100 righe in questo caso in quanto non utilizza nessuna delle normali funzioni GROUP BY. Ciò verrebbe eseguito in 0 secondi e quindi altamente efficiente. Nota che normalmente in MySQL vedremmo una clausola ORDER BY che segue la clausola GROUP BY, tuttavia questa clausola ORDER BY viene usata per determinare l'ORDINE per l'ultima funzione (), se fosse dopo il GROUP BY, ordinerebbe i GRUPPI. Se non è presente alcuna clausola GROUP BY, gli ultimi valori saranno gli stessi in tutte le righe restituite.

Tuttavia MySQL non ha questo, quindi diamo un'occhiata a diverse idee su ciò che ha e dimostriamo che nessuno di questi è efficiente.

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

Questo ha esaminato 3.009.254 file e ha impiegato ~ 0,859 secondi su 5.7.21 e leggermente più lungo su 8.0.4-rc

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

Questo ha esaminato 1.505.331 file e ha impiegato ~ 1,25 secondi su 5.7.21 e leggermente più lungo su 8.0.4-rc

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

Questo ha esaminato 3.009.685 file e ha impiegato ~ 1,95 secondi su 5.7.21 e leggermente più lungo su 8.0.4-rc

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

Questo ha esaminato 6.137.810 file e ha impiegato ~ 2,2 secondi su 5.7.21 e leggermente più lungo su 8.0.4-rc

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

Questo ha esaminato 6.017.808 righe e ha impiegato ~ 4,2 secondi su 8.0.4-rc

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

Questo ha esaminato 6.017.908 righe e ha impiegato ~ 17,5 secondi su 8.0.4-rc

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

Questo è durato un'eternità, quindi ho dovuto ucciderlo.


Questo è un problema diverso E la soluzione è un'enorme query UNION ALL.
Paul Spiegel,

@PaulSpiegel Immagino tu stia scherzando sul grande UNION ALL. Oltre al fatto che bisognerebbe conoscere in anticipo tutti i gruppi selezionati e che con 2.000 gruppi selezionati che sarebbero una query incredibilmente grande, avrebbe prestazioni persino peggiori dell'esempio più veloce sopra, quindi no, non sarebbe un soluzione.
Yoseph,

Sono assolutamente serio. L'ho provato in passato con un paio di centinaia di gruppi. Quando è necessario gestire i legami in grandi gruppi, UNION ALL è l'unico modo in MySQL per forzare un piano di esecuzione ottimale. SELECT DISTINCT(groupID)è veloce e ti fornirà tutti i dati di cui hai bisogno per costruire una simile query. Dovresti stare bene con la dimensione della query purché non superi max_allowed_packet, il valore predefinito è 4 MB in MySQL 5.7.
Paul Spiegel,

5

vedremo come è possibile utilizzare MySQL per ottenere l'ultimo record in un gruppo di record. Ad esempio se si dispone di questo set di risultati di post.

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

Voglio essere in grado di ottenere l'ultimo post in ogni categoria che sono Titolo 3, Titolo 5 e Titolo 6. Per ottenere i messaggi per categoria utilizzerai il gruppo MySQL dalla tastiera.

select * from posts group by category_id

Ma i risultati che otteniamo da questa query sono.

id category_id post_title

1 1 Title 1

4 2 Title 4

6 3 Title 6

Il gruppo da restituirà sempre il primo record nel gruppo sul set di risultati.

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

Ciò restituirà i post con gli ID più alti in ciascun gruppo.

id category_id post_title

3 1 Title 3

5 2 Title 5

6 3 Title 6

Riferimento Fare clic qui


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

Potresti approfondire un po 'la tua risposta? Perché la tua query è preferibile alla query originale di Vijays?
Janfoeh,

4

Ecco la mia soluzione:

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

Questo non restituisce l'ultimo messaggio per nome. Ed è solo una versione complicata di SELECT NAME, MAX(MESSAGES) MESSAGES FROM MESSAGE GROUP BY NAME.
Paul Spiegel,

Inoltre, questa formulazione è gravemente inefficiente.
Rick James,

3

Prova questo:

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

Ciao @Vijay Dev se i tuoi messaggi di tabella contengono Id che è la chiave primaria di incremento automatico, quindi per recuperare la base di record più recente sulla chiave primaria che la tua query dovrebbe leggere come di seguito:

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

Questo è il più veloce che ho trovato
CORSAIR il

3

Puoi vedere anche da qui.

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

PRIMA SOLUZIONE

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

SECONDA SOLUZIONE

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

**

Ciao, questa query potrebbe aiutare:

**

SELECT 
  *
FROM 
  message 

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

2

Esiste un modo per utilizzare questo metodo per eliminare i duplicati in una tabella? Il set di risultati è fondamentalmente una raccolta di record univoci, quindi se potessimo eliminare tutti i record non presenti nel set di risultati, non avremmo effettivamente duplicati? Ho provato questo, ma mySQL ha dato un errore 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)

Esiste un modo per salvare l'output in una variabile temporanea e quindi eliminarlo da NOT IN (variabile temporanea)? @Bill grazie per una soluzione molto utile.

EDIT: Penso di aver trovato la soluzione:

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

La query seguente funzionerà correttamente secondo la tua domanda.

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

Se si desidera l'ultima riga per ciascuno Name, è possibile assegnare un numero di riga a ciascun gruppo di righe per Namee ordina perId decrescente.

QUERY

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

Cosa ne pensi di questo:

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

Ho avuto un problema simile (su Postgresql dura) e su una tabella di record 1M. Questa soluzione richiede 1.7s contro 44s prodotti da quello con LEFT JOIN. Nel mio caso ho dovuto filtrare il corrispondente del campo del tuo nome in base ai valori NULL, ottenendo prestazioni ancora migliori di 0,2 secondi


1

Se la prestazione è davvero la tua preoccupazione, puoi introdurre una nuova colonna nella tabella chiamata IsLastInGroup di tipo BIT.

Impostalo su true nelle colonne che sono le ultime e mantienilo con ogni riga inserisci / aggiorna / elimina. Le scritture saranno più lente, ma trarrai vantaggio dalle letture. Dipende dal tuo caso d'uso e lo consiglio solo se sei focalizzato sulla lettura.

Quindi la tua query sarà simile a:

SELECT * FROM Messages WHERE IsLastInGroup = 1

Alcune tabelle in Moodle hanno una colonna flag come questa.
Lawrence,


0

Puoi raggruppare contando e anche ottenere l'ultimo elemento del gruppo come:

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

0

Spero che sotto la query Oracle possa essere d'aiuto:

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

Un altro approccio:

Trova la proprietà con il massimo m2_price all'interno di ciascun programma (n proprietà in 1 programma):

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
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.