Mengambil catatan terakhir di setiap grup - MySQL


958

Ada tabel messagesyang berisi data seperti yang ditunjukkan di bawah ini:

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

Jika saya menjalankan kueri select * from messages group by name, saya akan mendapatkan hasilnya sebagai:

1    A       A_data_1
4    B       B_data_1
6    C       C_data_1

Permintaan apa yang akan mengembalikan hasil berikut?

3    A       A_data_3
5    B       B_data_2
6    C       C_data_1

Artinya, catatan terakhir di setiap kelompok harus dikembalikan.

Saat ini, ini adalah permintaan yang saya gunakan:

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

Tapi ini terlihat sangat tidak efisien. Adakah cara lain untuk mencapai hasil yang sama?


2
lihat jawaban yang diterima di stackoverflow.com/questions/1379565/… untuk solusi yang lebih efisien
eyaler


7
Mengapa Anda tidak bisa menambahkan DESC, yaitu pilih * dari grup pesan dengan nama DESC
Kim Prince


2
@ KimPrince Sepertinya jawaban yang Anda sarankan tidak melakukan apa yang diharapkan! Saya baru saja mencoba metode Anda dan butuh baris PERTAMA untuk setiap grup dan memesan DESC. TIDAK mengambil baris terakhir dari setiap grup
Ayrat

Jawaban:


970

MySQL 8.0 sekarang mendukung fungsi windowing, seperti hampir semua implementasi SQL yang populer. Dengan sintaks standar ini, kita dapat menulis kueri terbesar-n-per-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;

Di bawah ini adalah jawaban asli yang saya tulis untuk pertanyaan ini pada tahun 2009:


Saya menulis solusinya dengan cara ini:

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

Mengenai kinerja, satu solusi atau yang lain bisa lebih baik, tergantung pada sifat data Anda. Jadi, Anda harus menguji kedua kueri dan menggunakan salah satu yang lebih baik dalam kinerja mengingat database Anda.

Sebagai contoh, saya memiliki salinan dump data Agustus StackOverflow . Saya akan menggunakannya untuk pembandingan. Ada 1.114.357 baris dalam Poststabel. Ini berjalan pada MySQL 5.0.75 di Macbook Pro 2.40GHz saya.

Saya akan menulis kueri untuk menemukan posting terbaru untuk ID pengguna yang diberikan (milik saya).

Pertama menggunakan teknik yang ditunjukkan oleh @Eric dengan di GROUP BYdalam subquery:

SELECT p1.postid
FROM Posts p1
INNER JOIN (SELECT pi.owneruserid, MAX(pi.postid) AS maxpostid
            FROM Posts pi GROUP BY pi.owneruserid) p2
  ON (p1.postid = p2.maxpostid)
WHERE p1.owneruserid = 20860;

1 row in set (1 min 17.89 sec)

Bahkan EXPLAINanalisisnya memakan waktu lebih dari 16 detik:

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

Sekarang hasilkan permintaan yang sama menggunakan teknik saya dengan 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)

The EXPLAINanalisis menunjukkan bahwa kedua tabel dapat menggunakan indeks mereka:

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

Inilah DDL untuk Poststabel saya :

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
Betulkah? Apa yang terjadi jika Anda memiliki banyak entri? Misalnya, jika Anda bekerja dengan kontrol versi in-house, katakanlah, dan Anda memiliki banyak versi per file, hasil gabungan itu akan sangat besar. Apakah Anda pernah membandingkan metode subquery dengan yang ini? Saya cukup penasaran untuk mengetahui mana yang akan menang, tetapi tidak cukup penasaran untuk tidak bertanya kepada Anda terlebih dahulu.
Eric

2
Melakukan beberapa pengujian. Di atas meja kecil (~ catatan 300k, ~ grup 190k, jadi bukan grup besar atau apa pun), kueri diikat (masing-masing 8 detik).
Eric

1
@BillKarwin: Lihat meta.stackexchange.com/questions/123017 , terutama komentar di bawah jawaban Adam Rackis. Beri tahu saya jika Anda ingin mendapatkan kembali jawaban Anda atas pertanyaan baru.
Robert Harvey

3
@Tim, tidak, <=tidak akan membantu jika Anda memiliki kolom yang tidak unik. Anda harus menggunakan kolom unik sebagai tiebreak.
Bill Karwin

2
Kinerja menurun secara eksponensial ketika jumlah baris meningkat atau ketika kelompok menjadi lebih besar. Misalnya grup yang terdiri dari 5 tanggal akan menghasilkan 4 + 3 + 2 + 1 + 1 = 11 baris melalui gabungan kiri yang satu barisnya difilter pada akhirnya. Kinerja bergabung dengan hasil yang dikelompokkan hampir linier. Tes Anda terlihat cacat.
Salman A

148

UPD: 2017-03-31, versi 5.7.5 dari MySQL membuat sakelar ONLY_FULL_GROUP_BY diaktifkan secara default (karenanya, permintaan GROUP BY non-deterministik menjadi dinonaktifkan). Selain itu, mereka memperbarui implementasi GROUP BY dan solusinya mungkin tidak berfungsi seperti yang diharapkan bahkan dengan saklar yang dinonaktifkan. Orang perlu memeriksa.

Solusi Bill Karwin di atas bekerja dengan baik ketika jumlah item dalam kelompok agak kecil, tapi kinerja query menjadi buruk ketika kelompok-kelompok yang agak besar, karena solusinya membutuhkan sekitar n*n/2 + n/2hanya IS NULLperbandingan.

Saya membuat tes pada tabel InnoDB 18684446baris dengan 1182kelompok. Tabel berisi hasil tes untuk tes fungsional dan memiliki (test_id, request_id)sebagai kunci utama. Jadi, test_idadalah grup dan saya sedang mencari yang terakhir request_iduntuk masing-masing test_id.

Solusi Bill telah berjalan selama beberapa jam di Dell e4310 saya dan saya tidak tahu kapan akan selesai meskipun beroperasi pada indeks cakupan (maka using indexdalam EXPLAIN).

Saya punya beberapa solusi lain yang didasarkan pada ide yang sama:

  • jika indeks yang mendasarinya adalah indeks BTREE (yang biasanya merupakan kasus), (group_id, item_value)pasangan terbesar adalah nilai terakhir dalam masing-masing group_id, itu adalah yang pertama untuk masing-masing group_idjika kita berjalan melalui indeks dalam urutan menurun;
  • jika kita membaca nilai-nilai yang dicakup oleh indeks, nilai-nilai tersebut dibaca dalam urutan indeks;
  • setiap indeks secara implisit mengandung kolom kunci utama yang ditambahkan ke itu (itu adalah kunci utama dalam indeks cakupan). Dalam solusi di bawah ini saya beroperasi langsung pada kunci utama, dalam kasus Anda, Anda hanya perlu menambahkan kolom kunci utama dalam hasilnya.
  • dalam banyak kasus itu jauh lebih murah untuk mengumpulkan id baris yang diperlukan dalam urutan yang diperlukan dalam subquery dan bergabung dengan hasil subquery pada id. Karena untuk setiap baris dalam hasil subquery, MySQL akan membutuhkan pengambilan tunggal berdasarkan kunci primer, subquery akan diletakkan pertama dalam gabungan dan baris akan menjadi output dalam urutan id di subquery (jika kita menghilangkan ORDER eksplisit oleh untuk bergabung)

3 cara MySQL menggunakan indeks adalah artikel yang bagus untuk memahami beberapa detail.

Solusi 1

Yang ini sangat cepat, butuh sekitar 0,8 detik pada baris 18M + saya:

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

Jika Anda ingin mengubah urutan menjadi ASC, masukkan ke dalam subquery, kembalikan id saja dan gunakan itu sebagai subquery untuk bergabung ke seluruh kolom:

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;

Yang ini membutuhkan sekitar 1,2 detik pada data saya.

Solusi 2

Berikut ini solusi lain yang membutuhkan waktu sekitar 19 detik untuk meja saya:

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

Ini mengembalikan tes dalam urutan juga. Ini jauh lebih lambat karena melakukan pemindaian indeks penuh tetapi ada di sini untuk memberi Anda ide bagaimana untuk menghasilkan baris N max untuk setiap kelompok.

Kerugian dari kueri adalah bahwa hasilnya tidak dapat di-cache oleh cache kueri.


Harap tautkan ke dump tabel Anda sehingga orang dapat mengujinya di platform mereka.
Pacerier

3
Solusi 1 tidak dapat bekerja, Anda tidak dapat memilih request_id tanpa harus dalam kelompok dengan klausa,
gi

2
@ giò, ini jawabannya 5 tahun. Hingga MySQL 5.7.5 ONLY_FULL_GROUP_BY dinonaktifkan secara default dan solusi ini berhasil di luar kotak dev.mysql.com/doc/relnotes/mysql/5.7/en/… . Sekarang saya tidak yakin apakah solusinya masih berfungsi ketika Anda menonaktifkan mode, karena implementasi GROUP BY telah diubah.
ditemukan

Jika Anda ingin ASC dalam solusi pertama, apakah akan berhasil jika Anda mengubah MAX menjadi MIN?
Jin

@JinIzzraeel, Anda memiliki MIN secara default di bagian atas setiap grup (ini adalah urutan indeks yang meliputi): SELECT test_id, request_id FROM testresults GROUP BY test_id;akan mengembalikan request_id minimum untuk setiap test_id.
mulai

102

Gunakan subquery Anda untuk mengembalikan pengelompokan yang benar, karena Anda berada di tengah jalan.

Coba ini:

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

Jika tidak, idAnda ingin maks:

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

Dengan cara ini, Anda menghindari subqueries yang berkorelasi dan / atau memesan di subqueries Anda, yang cenderung sangat lambat / tidak efisien.


1
Perhatikan peringatan untuk solusi dengan other_col: jika kolom itu tidak unik, Anda mungkin mendapatkan beberapa catatan kembali dengan yang sama name, jika mereka mengikat untuk max(other_col). Saya menemukan posting ini yang menjelaskan solusi untuk kebutuhan saya, di mana saya membutuhkan tepat satu catatan per name.
Eric Simonton

Dalam beberapa situasi Anda hanya dapat menggunakan solusi ini tetapi hanya yang diterima.
tom10271

Dalam pengalaman saya, itu mengelompokkan seluruh tabel pesan sialan yang cenderung lambat / tidak efisien! Dengan kata lain, perhatikan bahwa subquery memerlukan pemindaian tabel penuh, dan lakukan pengelompokan untuk mem-boot ... kecuali pengoptimal Anda melakukan sesuatu yang bukan milik saya. Jadi solusi ini sangat bergantung pada memegang seluruh tabel dalam memori.
Timo

Mereka akan mendapat manfaat dari INDEX(name, id)danINDEX(name, other_col)
Rick James

55

Saya sampai pada solusi yang berbeda, yaitu mendapatkan ID untuk posting terakhir dalam setiap grup, lalu pilih dari tabel pesan menggunakan hasil dari kueri pertama sebagai argumen untuk WHERE x INkonstruk:

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

Saya tidak tahu bagaimana kinerjanya dibandingkan dengan beberapa solusi lain, tetapi ini bekerja secara spektakuler untuk meja saya dengan 3+ juta baris. (Eksekusi 4 detik dengan 1200+ hasil)

Ini harus berfungsi baik pada MySQL dan SQL Server.


Pastikan Anda memiliki indeks (nama, id).
Samuel Åslund

1
Jauh lebih baik yang bergabung dengan diri sendiri
anwerj

Saya belajar sesuatu dari Anda yang merupakan pekerjaan yang baik dan permintaan ini lebih cepat
Humphrey

33

Solusi oleh Sub query Link biola

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

Solusi Dengan bergabung dengan tautan biola kondisi

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

Alasan untuk posting ini adalah untuk memberikan tautan biola saja. SQL yang sama sudah disediakan di jawaban lain.


1
@AlexanderSuraphel mysql5.5 tidak tersedia di biola sekarang, tautan biola dibuat menggunakan itu. Sekarang biola hari mendukung mysql5.6, saya mengubah database menjadi mysql 5.6 dan saya dapat membangun skema dan menjalankan sql.
Pembalasan

8

Pendekatan dengan kecepatan tinggi adalah sebagai berikut.

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

Hasil

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

Ini mengasumsikan iddipesan sesuai kebutuhan Anda. Dalam kasus umum diperlukan beberapa kolom lain.
Rick James

6

Berikut ini dua saran. Pertama, jika mysql mendukung ROW_NUMBER (), itu sangat sederhana:

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;

Saya mengasumsikan dengan "terakhir" yang Anda maksud terakhir dalam urutan Id. Jika tidak, ubah klausa ORDER BY dari jendela ROW_NUMBER (). Jika ROW_NUMBER () tidak tersedia, ini adalah solusi lain:

Kedua, jika tidak, ini sering merupakan cara yang baik untuk melanjutkan:

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

Dengan kata lain, pilih pesan di mana tidak ada pesan Id-nanti dengan Nama yang sama.


8
MySQL tidak mendukung ROW_NUMBER () atau CTE.
Bill Karwin

1
MySQL 8.0 (dan MariaDB 10.2) sekarang mendukung ROW_NUMBER()dan CTE.
Rick James

6

Saya belum diuji dengan DB besar tapi saya pikir ini bisa lebih cepat daripada bergabung dengan tabel:

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

14
Ini mengembalikan data sewenang-wenang. Dengan kata lain, ada kolom yang dikembalikan mungkin bukan dari catatan dengan MAX (Id).
membahayakan

Berguna untuk memilih Id maks dari satu set record dengan kondisi WHERE: "SELECT Max (Id) FROM PROD WHERE Pn = '" + Pn + "'" Ini mengembalikan max id dari satu set record dengan Pn yang sama. Di c # gunakan reader.GetString (0) untuk mendapatkan hasilnya
Nicola

5

Berikut adalah cara lain untuk mendapatkan catatan terkait terakhir menggunakan GROUP_CONCATdengan urutan oleh dan SUBSTRING_INDEXuntuk memilih salah satu catatan dari daftar

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

Kueri di atas akan mengelompokkan semua Other_Columnsyang ada dalam Namekelompok yang sama dan menggunakan ORDER BY id DESCakan bergabung dengan semua Other_Columnsdalam grup tertentu dalam urutan menurun dengan pemisah yang disediakan dalam kasus saya yang telah saya gunakan ||, menggunakan SUBSTRING_INDEXlebih dari daftar ini akan memilih yang pertama

Demo biola


Sadarilah bahwa group_concat_max_lenmembatasi berapa banyak baris yang bisa Anda tangani.
Rick James

5

Jelas ada banyak cara berbeda untuk mendapatkan hasil yang sama, pertanyaan Anda sepertinya adalah cara yang efisien untuk mendapatkan hasil terakhir di setiap grup di MySQL. Jika Anda bekerja dengan data dalam jumlah besar dan menganggap Anda menggunakan InnoDB bahkan dengan versi terbaru MySQL (seperti 5.7.21 dan 8.0.4-rc) maka mungkin tidak ada cara yang efisien untuk melakukan ini.

Kita terkadang perlu melakukan ini dengan tabel dengan lebih dari 60 juta baris.

Untuk contoh-contoh ini saya akan menggunakan data dengan hanya sekitar 1,5 juta baris di mana kueri perlu menemukan hasil untuk semua grup dalam data. Dalam kasus kami yang sebenarnya, kami sering perlu mengembalikan data dari sekitar 2.000 kelompok (yang secara hipotetis tidak memerlukan pemeriksaan data yang sangat banyak).

Saya akan menggunakan tabel berikut:

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

Tabel suhu diisi dengan sekitar 1,5 juta catatan acak, dan dengan 100 kelompok berbeda. Terpilih_group diisi dengan 100 grup (dalam kasus kami ini biasanya akan kurang dari 20% untuk semua grup).

Karena data ini acak, ini berarti bahwa beberapa baris dapat memiliki catatanTestestamp yang sama. Yang kami inginkan adalah mendapatkan daftar semua grup yang dipilih dalam urutan groupID denganTimestamp yang direkam terakhir untuk setiap grup, dan jika grup yang sama memiliki lebih dari satu baris yang cocok seperti itu, maka id terakhir yang cocok dari baris tersebut.

Jika secara hipotesis MySQL memiliki fungsi terakhir () yang mengembalikan nilai dari baris terakhir dalam klausa ORDER BY khusus maka kita bisa melakukan:

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;

yang hanya perlu memeriksa beberapa 100 baris dalam kasus ini karena tidak menggunakan fungsi GROUP BY yang normal. Ini akan dieksekusi dalam 0 detik dan karenanya sangat efisien. Perhatikan bahwa biasanya di MySQL kita akan melihat klausa ORDER BY mengikuti klausa GROUP BY namun klausa ORDER BY ini digunakan untuk menentukan ORDER untuk fungsi terakhir (), jika setelah GROUP BY maka akan memesan GROUPS. Jika tidak ada klausa GROUP BY yang hadir maka nilai terakhir akan sama di semua baris yang dikembalikan.

Namun MySQL tidak memiliki ini, jadi mari kita melihat ide-ide berbeda dari apa yang dimilikinya dan membuktikan bahwa tidak ada yang efisien.

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

Ini memeriksa 3.009.254 baris dan mengambil ~ 0,859 detik pada 5.7.21 dan sedikit lebih lama pada 8.0.4-rc

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

Ini memeriksa 1.505.331 baris dan mengambil ~ 1,25 detik pada 5.7.21 dan sedikit lebih lama pada 8.0.4-rc

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

Ini memeriksa 3.009.685 baris dan mengambil ~ 1,95 detik pada 5.7.21 dan sedikit lebih lama pada 8.0.4-rc

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

Ini memeriksa 6.137.810 baris dan memakan waktu ~ 2.2 detik pada 5.7.21 dan sedikit lebih lama pada 8.0.4-rc

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

Ini memeriksa 6.017.808 baris dan memakan waktu ~ 4.2 detik pada 8.0.4-rc

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

Ini memeriksa 6.017.908 baris dan mengambil ~ 17,5 detik pada 8.0.4-rc

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

Yang ini memakan waktu lama jadi saya harus membunuhnya.


Ini masalah yang berbeda. Dan solusinya adalah UNION ALL query besar.
Paul Spiegel

@ PaulSpiegel Saya kira Anda bercanda tentang UNION SEMUA besar. Selain fakta bahwa seseorang perlu mengetahui semua kelompok yang dipilih sebelumnya, dan bahwa dengan 2.000 kelompok yang dipilih akan menjadi permintaan yang sangat besar, itu akan melakukan lebih buruk daripada contoh tercepat di atas, jadi tidak, itu tidak akan menjadi larutan.
Yoseph

Saya benar-benar serius. Saya sudah menguji itu di masa lalu dengan beberapa ratus kelompok. Ketika Anda perlu menangani ikatan dalam kelompok besar, UNION ALL adalah satu-satunya cara di MySQL untuk memaksakan rencana eksekusi yang optimal. SELECT DISTINCT(groupID)cepat dan akan memberi Anda semua data yang Anda butuhkan untuk membangun permintaan seperti itu. Anda harus baik-baik saja dengan ukuran permintaan selama tidak melebihi max_allowed_packet, yang defaultnya adalah 4MB di MySQL 5.7.
Paul Spiegel

5

kita akan melihat bagaimana Anda dapat menggunakan MySQL untuk mendapatkan catatan terakhir dalam catatan Group By. Misalnya jika Anda memiliki set posting hasil ini.

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

Saya ingin bisa mendapatkan posting terakhir di setiap kategori yaitu Judul 3, Judul 5 dan Judul 6. Untuk mendapatkan posting berdasarkan kategori Anda akan menggunakan keyboard MySQL Group By.

select * from posts group by category_id

Tetapi hasil yang kami dapatkan dari permintaan ini adalah.

id category_id post_title

1 1 Title 1

4 2 Title 4

6 3 Title 6

Grup oleh akan selalu mengembalikan catatan pertama dalam grup pada hasil yang ditetapkan.

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

Ini akan mengembalikan tulisan dengan ID tertinggi di setiap grup.

id category_id post_title

3 1 Title 3

5 2 Title 5

6 3 Title 6

Referensi Klik Di Sini


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

Bisakah Anda sedikit menguraikan jawaban Anda? Mengapa kueri Anda lebih disukai daripada kueri asli Vijays?
janfoeh

4

Inilah solusi saya:

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

Ini tidak mengembalikan pesan terbaru per nama. Dan itu hanya versi terlalu rumit dari SELECT NAME, MAX(MESSAGES) MESSAGES FROM MESSAGE GROUP BY NAME.
Paul Spiegel

Lebih jauh lagi, formulasi ini sangat tidak efisien.
Rick James

3

Coba ini:

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

Hai @Vijay Dev jika pesan tabel Anda berisi Id yang merupakan kunci primer kenaikan otomatis kemudian untuk mengambil basis catatan terbaru pada kunci utama kueri Anda harus membaca seperti di bawah ini:

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

Ini yang tercepat yang saya temukan
CORSAIR

3

Anda dapat melihat dari sini juga.

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

SOLUSI PERTAMA

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

SOLUSI KEDUA

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

**

Hai, pertanyaan ini mungkin membantu:

**

SELECT 
  *
FROM 
  message 

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

2

Apakah ada cara kita bisa menggunakan metode ini untuk menghapus duplikat dalam sebuah tabel? Rangkaian hasil pada dasarnya adalah kumpulan catatan unik, jadi jika kami dapat menghapus semua catatan yang tidak ada dalam rangkaian hasil, kami akan secara efektif tidak memiliki duplikat? Saya mencoba ini tetapi mySQL memberikan kesalahan 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)

Apakah ada cara untuk mungkin menyimpan output ke variabel temp lalu hapus dari NOT IN (variabel temp)? @Bill terima kasih atas solusi yang sangat berguna.

EDIT: Pikirkan saya menemukan solusinya:

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

Kueri di bawah ini akan berfungsi dengan baik sesuai pertanyaan Anda.

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

Jika Anda ingin baris terakhir untuk masing-masing Name, maka Anda dapat memberikan nomor baris untuk setiap grup baris dengan Namedan memesan dengan Idurutan menurun.

PERTANYAAN

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

Bagaimana dengan ini:

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

Saya memiliki masalah yang sama (pada postgresql tangguh) dan pada tabel catatan 1M. Solusi ini mengambil 1,7s vs 44s yang diproduksi oleh yang dengan LEFT JOIN. Dalam kasus saya, saya harus memfilter korrisponden bidang nama Anda terhadap nilai NULL, menghasilkan kinerja yang lebih baik lagi sebesar 0,2 detik


1

Jika kinerja benar-benar menjadi perhatian Anda, Anda dapat memperkenalkan kolom baru pada tabel yang disebut IsLastInGrouptipe BIT.

Setel ke true pada kolom yang terakhir dan pertahankan dengan setiap baris masukkan / perbarui / hapus. Menulis akan lebih lambat, tetapi Anda akan mendapat manfaat saat membaca. Itu tergantung pada kasus penggunaan Anda dan saya sarankan hanya jika Anda fokus membaca

Jadi kueri Anda akan terlihat seperti:

SELECT * FROM Messages WHERE IsLastInGroup = 1

Beberapa tabel di Moodle memiliki kolom bendera seperti ini.
Lawrence


0

Anda dapat mengelompokkan dengan menghitung dan juga mendapatkan item terakhir dari grup seperti:

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

0

Semoga di bawah ini permintaan Oracle dapat membantu:

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

Pendekatan lain:

Temukan propertie dengan harga m2_ max dengan setiap program (n properti dalam 1 program):

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
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.