Her gruptaki son kaydı alma - MySQL


954

messagesAşağıda gösterildiği gibi veri içeren bir tablo vardır:

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

Bir sorgu çalıştırırsanız select * from messages group by name, sonucu şöyle alırım:

1    A       A_data_1
4    B       B_data_1
6    C       C_data_1

Hangi sorgu aşağıdaki sonucu döndürür?

3    A       A_data_3
5    B       B_data_2
6    C       C_data_1

Yani, her gruptaki son kayıt döndürülmelidir.

Şu anda, kullandığım sorgu budur:

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

Ancak bu oldukça verimsiz görünüyor. Aynı sonucu elde etmenin başka yolları var mı?


2
daha verimli bir çözüm için stackoverflow.com/questions/1379565/… 'de kabul edilen cevaba bakınız
eyaler


7
Neden sadece DESC'yi ekleyemiyorsunuz, yani mesajlar grubundan * DESC
Kim Prince


2
@KimPrince Öyle görünüyor ki cevap beklediğiniz şeyi yapmaz! Sadece yönteminizi denedim ve her grup için İLK satır aldı ve DESC sipariş etti. Her grubun son sırasını
ALMAZ

Yanıtlar:


967

MySQL 8.0 artık neredeyse tüm popüler SQL uygulamaları gibi pencereleme işlevlerini destekliyor. Bu standart sözdizimi ile grup başına en büyük n-sorgu yazabiliriz:

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;

2009'da bu soru için yazdığım orijinal cevap aşağıdadır:


Çözümü şu şekilde yazıyorum:

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

Performansla ilgili olarak, verilerinizin niteliğine bağlı olarak bir çözüm veya diğeri daha iyi olabilir. Bu nedenle, her iki sorguyu da test etmeli ve veritabanınızda verilen performansta daha iyi olanı kullanmalısınız.

Örneğin, StackOverflow Ağustos veri dökümü bir kopyası var . Ben kıyaslama için kullanacağım. Tabloda 1.114.357 satır vardır Posts. Bu, Macbook Pro 2.40GHz'de MySQL 5.0.75 üzerinde çalışıyor .

Belirli bir kullanıcı kimliği (benim) için en son yayını bulmak için bir sorgu yazacağım.

İlk olarak bir alt sorguda @Eric tarafından gösterilen tekniği kullanarak GROUP BY:

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)

EXPLAINAnaliz bile 16 saniyeden fazla sürüyor:

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

Şimdi ile benim tekniği kullanarak aynı sorgu sonucu üretmek 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)

EXPLAINHer iki tablo da endeksleri kullanabilirler analiz gösterileri:

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

İşte Postsmasam için DDL :

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
Gerçekten mi? Bir ton girişiniz varsa ne olur? Örneğin, bir şirket içi sürüm denetimiyle çalışıyorsanız, örneğin dosya başına tonlarca sürümünüz varsa, bu birleştirme sonucu çok büyük olur. Alt sorgu yöntemini hiç bu yöntemle karşılaştırdınız mı? Hangisinin kazanacağını merak ediyorum, ama önce size sormaya yetecek kadar meraklı değilim.
Eric

2
Bazı testler yaptım. Küçük bir tabloda (~ 300k kayıtları, ~ 190k grupları, bu nedenle büyük gruplar veya herhangi bir şey değil), sorgular bağlandı (her biri 8 saniye).
Eric

1
@ BillKarwin: Bkz. Meta.stackexchange.com/questions/123017 , özellikle Adam Rackis'in cevabının altındaki yorumlar. Yeni soruya yanıtınızı geri almak istiyorsanız bana bildirin.
Robert Harvey

3
@Zaman, hayır, <=benzersiz olmayan bir sütununuz varsa yardımcı olmaz. Tiebreaker olarak benzersiz bir sütun kullanmalısınız.
Bill Karwin

2
Sıra sayısı arttıkça veya gruplar büyüdükçe performans katlanarak azalır. Örneğin, 5 tarihten oluşan bir grup, sol birleşim yoluyla 4 + 3 + 2 + 1 + 1 = 11 satır verir ve bunların sonunda bir satır filtrelenir. Gruplandırılmış sonuçlarla birleştirme performansı neredeyse doğrusaldır. Testleriniz hatalı görünüyor.
Salman A

147

UPD: 2017-03-31, MySQL'in 5.7.5 sürümü ONLY_FULL_GROUP_BY anahtarını varsayılan olarak etkinleştirdi (dolayısıyla deterministik olmayan GROUP BY sorguları devre dışı bırakıldı). Ayrıca, GROUP BY uygulamasını güncellediler ve çözüm, devre dışı bırakma anahtarıyla bile beklendiği gibi çalışmayabilir. Kontrol etmek gerekiyor.

Bill Karwin'in yukarıdaki çözümü, gruplar içindeki öğe sayısı oldukça küçük olduğunda iyi çalışır, ancak çözüm n*n/2 + n/2sadece IS NULLkarşılaştırmalar gerektirdiğinden, gruplar oldukça büyük olduğunda sorgunun performansı kötüleşir .

Testlerimi gruplar 18684446içeren bir InnoDB satır tablosunda yaptım 1182. Tablo, işlevsel testler için test sonuçları içerir (test_id, request_id)ve birincil anahtar olarak vardır. Böylece, test_idbir grup ve request_idher biri için sonuncusu arıyordum test_id.

Bill'in çözümü zaten dell e4310'umda birkaç saat çalışıyor ve bir kapsama endeksinde (dolayısıyla using indexEXPLAIN'te) çalışmasına rağmen ne zaman biteceğini bilmiyorum .

Aynı fikirlere dayanan birkaç çözüm daha var:

  • temel alınan endeks BTREE indeksiyse (genellikle durum budur), en büyük (group_id, item_value)çift ​​her biri içindeki son değerdir group_id, yani group_idindeksten inen sırada yürürsek her biri için ilk değerdir ;
  • bir indeksin kapsadığı değerleri okursak, değerler indeks sırasına göre okunur;
  • her dizin örtük olarak buna eklenen birincil anahtar sütunlarını içerir (birincil anahtar kapsama dizinindedir). Aşağıdaki çözümlerde doğrudan birincil anahtar üzerinde çalışıyorum, siz durumda sadece sonuçta birincil anahtar sütunları eklemeniz gerekir.
  • birçok durumda gerekli satır kimliklerini bir alt sorguda gerekli sırada toplamak ve kimlikteki alt sorgunun sonucuna katılmak çok daha ucuzdur. Alt sorgu sonucundaki her satır için MySQL'in birincil anahtara göre tek bir getirmeye ihtiyacı olacağından, alt sorgu ilk olarak birleştirmeye yerleştirilir ve satırlar alt sorgudaki kimliklerin sırasına göre çıkarılır (açık ORDER BY katılmak için)

MySQL'in indeksleri kullanmasının 3 yolu , bazı ayrıntıları anlamak için harika bir makaledir.

Çözüm 1

Bu inanılmaz derecede hızlı, 18M + satırlarımda yaklaşık 0,8 saniye sürüyor:

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

Sırayı ASC olarak değiştirmek istiyorsanız, bunu bir alt sorguya koyun, yalnızca kimlikleri döndürün ve bunu sütunların geri kalanına katılmak için alt sorgu olarak kullanın:

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;

Bu, verilerimde yaklaşık 1,2 saniye sürüyor.

Çözüm 2

İşte masam için yaklaşık 19 saniye süren başka bir çözüm:

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

Testleri de azalan sırada döndürür. Tam bir indeks taraması yaptığı için çok daha yavaştır, ancak her grup için Nmaks satırlarının nasıl üretileceği hakkında bir fikir vermek için burada.

Sorgunun dezavantajı, sonucunun sorgu önbelleği tarafından önbelleğe alınamamasıdır.


Lütfen kullanıcıların platformlarında test edebilmesi için tablolarınızın bir dökümüne bağlantı verin.
Pacerier

3
Çözüm 1 çalışamıyor, bunu group by yan tümcesine sahip olmadan request_id'i seçemezsiniz,
giò

2
@ giò, bu cevap 5 yaşında. MySQL 5.7.5'e kadar ONLY_FULL_GROUP_BY varsayılan olarak devre dışı bırakıldı ve bu çözüm dev.mysql.com/doc/relnotes/mysql/5.7/en/… kutusundan çıktı . Şimdi modu devre dışı bıraktığınızda çözümün hala çalışıp çalışmadığından emin değilim, çünkü GROUP BY uygulaması değiştirildi.
newtover

İlk çözümde ASC'yi istiyorsanız, MAX'ı MIN'a çevirirseniz işe yarar mı?
Jin

@JinIzzraeel, varsayılan olarak her grubun üst kısmında MIN vardır (kaplama dizininin sırasıdır): SELECT test_id, request_id FROM testresults GROUP BY test_id;her test_id için minimum request_id değerini döndürür.
newtover

101

Alt grubunuzu kullanarak doğru gruplamayı geri gönderin, çünkü orada yarı yoldasınız.

Bunu dene:

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

Değilse id, 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

Bu şekilde, alt sorgularınızda çok yavaş / verimsiz olma eğiliminde olan ilişkili alt sorgulardan ve / veya sipariş vermekten kaçınırsınız.


1
Şu çözümle ilgili bir uyarı not edin other_col: bu sütun benzersiz değilse, birden çok kayıt aynı nameolursa geri dönebilirsiniz max(other_col). İhtiyacım için bir çözüm açıklayan bu gönderiyi buldum , burada tam olarak bir kayda ihtiyacım var name.
Eric Simonton

Bazı durumlarda yalnızca bu çözümü kullanabilirsiniz ancak kabul edilen çözümü kullanabilirsiniz.
tom10271

Deneyimlerime göre, yavaş / verimsiz olma eğilimindeki tüm lanet mesajlar tablosunu gruplandırmak ! Başka bir deyişle, alt sorgunun tam bir tablo taraması gerektirdiğini ve optimize edicinizin benim yapmadığı bir şey yapmadıkça önyükleme yapmak için bir gruplama yaptığını unutmayın. Dolayısıyla bu çözüm büyük ölçüde tüm tabloyu bellekte tutmaya bağlıdır.
Timo

Bunlar faydalanacak INDEX(name, id)veINDEX(name, other_col)
Rick James

55

Her grup içindeki son gönderinin kimliklerini almak için farklı bir çözüme ulaştım, sonra ilk sorgudaki sonucu bir WHERE x INyapı argümanı olarak kullanarak mesajlar tablosundan seçim yaptım :

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

Bunun diğer çözümlerden bazılarına kıyasla nasıl performans gösterdiğini bilmiyorum, ancak 3 milyondan fazla sıra içeren masam için muhteşem çalıştı. (1200+ sonuç ile 4 saniyelik uygulama)

Bu hem MySQL hem de SQL Server üzerinde çalışmalıdır.


Sadece bir indeksiniz olduğundan emin olun (isim, id).
Samuel Åslund

1
Kendine
Katılır

Sizden iyi bir iş olan bir şey öğrendim ve bu sorgu daha hızlı
Humphrey

33

Alt sorgu keman Bağlantısı ile çözüm

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

Çözüm Birleştirme koşulu tarafından keman bağlantısı

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

Bu yazının nedeni sadece keman linki vermektir. Aynı SQL diğer yanıtlarda zaten sağlanmıştır.


1
@AlexanderSuraphel mysql5.5 şu anda mevcut değil, bunu kullanarak keman bağlantısı oluşturuldu. Şimdi bir gün keman mysql5.6 destekler, ben mysql 5.6 veritabanı değiştirdi ve ben şema oluşturmak ve sql çalıştırmak mümkün.
Vipin

8

Oldukça hızlı bir yaklaşım aşağıdaki gibidir.

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

Sonuç

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

Bu varsayım id, ihtiyacınız olan şekilde sıralanmıştır. Genel durumda başka bir sütuna ihtiyaç vardır.
Rick James

6

İşte iki öneri. İlk olarak, mysql ROW_NUMBER () 'i destekliyorsa, çok basittir:

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;

"Son" demek istediğim, Kimlik sıralamasında son demek istiyorsun. Değilse, ROW_NUMBER () penceresinin ORDER BY yan tümcesini uygun şekilde değiştirin. ROW_NUMBER () kullanılamıyorsa, bu başka bir çözümdür:

İkincisi, eğer yapmazsa, bu genellikle ilerlemek için iyi bir yoldur:

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

Başka bir deyişle, aynı Ada sahip daha geç id mesajının bulunmadığı mesajları seçin.


8
MySQL, ROW_NUMBER () veya CTE'leri desteklemez.
Bill Karwin

1
MySQL 8.0 (ve MariaDB 10.2) artık destek ROW_NUMBER()ve CTE'leri destekliyor .
Rick James

6

Henüz büyük DB ile test etmedim ama bu tablolara katılmaktan daha hızlı olabileceğini düşünüyorum:

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

14
Bu, rastgele veriler döndürür. Başka bir deyişle, döndürülen sütunlar MAX (Id) ile kayıttan olmayabilir.
zarar

WHERE koşulu olan bir kayıt kümesinden max ID'yi seçmek için kullanışlıdır: "Prod WHERE'DEN Maks. sonucu almak için reader.GetString (0) kullanın
Nicola

5

Sırasıyla GROUP_CONCATsipariş kullanarak en son ilgili kaydı almanın SUBSTRING_INDEXve listeden kayıttan birini seçmenin başka bir yolu

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

Sorgu grup olacağı Üstü tüm Other_Columnsaynı olduklarını Namegrubu ve kullanma ORDER BY id DESCkatılacak tüm Other_Columnsi kullandık benim durumumda sağlanan ayırıcı ile azalan düzende belirli bir grup içinde ||kullanarak SUBSTRING_INDEXbu liste üzerinde ilkini seçecektir

Keman Demosu


Unutmayın group_concat_max_lensınırlar kaç satırları işleyebilir.
Rick James

5

Açıkçası, aynı sonuçları almanın birçok farklı yolu vardır, sorunuz MySQL'deki her grupta son sonuçları almanın etkili bir yolu gibi görünüyor. Çok miktarda veriyle çalışıyorsanız ve InnoDB'yi MySQL'in en son sürümleriyle (5.7.21 ve 8.0.4-rc gibi) bile kullandığınızı varsayarsanız, bunu yapmanın etkili bir yolu olmayabilir.

Bazen bunu 60 milyondan fazla sıra içeren tablolarla yapmamız gerekir.

Bu örnekler için, sorguların verilerdeki tüm gruplar için sonuç bulması gereken yalnızca yaklaşık 1,5 milyon satır içeren verileri kullanacağım. Gerçek vakalarımızda genellikle yaklaşık 2.000 gruptan veri geri döndürmemiz gerekir (bu varsayımsal olarak verilerin çok fazla incelenmesini gerektirmez).

Aşağıdaki tabloları kullanacağım:

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

Sıcaklık tablosu yaklaşık 1,5 milyon rasgele kayıt ve 100 farklı grupla doldurulur. Seçilen_grup bu 100 grupla doldurulur (bizim durumumuzda bu normalde tüm gruplar için% 20'den az olacaktır).

Bu veriler rasgele olduğu için, birden çok satırın aynı kaydedilmiş zaman damgalarına sahip olabileceği anlamına gelir. İstediğimiz, her grup için son kaydedilmişTimestamp değerine sahip grup kimliği sırasına göre seçilen tüm grupların bir listesini almak ve aynı grupta bunun gibi birden fazla eşleşen satır varsa, bu satırların son eşleşen kimliği varsa.

Varsayımsal olarak MySQL, özel bir ORDER BY deyiminde son satırdaki değerleri döndüren bir last () işlevine sahipse, bunu yapabiliriz:

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;

normal GROUP BY işlevlerinden hiçbirini kullanmadığı için bu durumda yalnızca birkaç 100 satırı incelemesi gerekir. Bu 0 saniye içinde yürütülür ve bu nedenle yüksek verimli olur. Normalde MySQL'de GROUP BY yan tümcesini izleyen ORDER BY yan tümcesi görecektik, ancak bu ORDER BY yan tümcesi son () işlevi için ORDER öğesini belirlemek için kullanılır, GROUP BY öğesinden sonra olsaydı GRUPLAR sipariş ederdi. GROUP BY deyimi yoksa, döndürülen tüm satırlarda son değerler aynı olur.

Ancak MySQL buna sahip değildir, bu yüzden sahip olduklarına dair farklı fikirlere bakalım ve bunların hiçbirinin etkili olmadığını kanıtlayalım.

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

Bu, 3.009.254 satırı inceledi ve 5.7.21'de ~ 0.859 saniye ve 8.0.4-rc'de biraz daha uzun sürdü

ÖRNEK 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;

Bu, 1.505.331 satırı inceledi ve 5.7.21'de ~ 1.25 saniye ve 8.0.4-rc'de biraz daha uzun sürdü

ÖRNEK 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;

Bu, 3.009.685 satırı inceledi ve 5.7.21'de ~ 1.95 saniye ve 8.0.4-rc'de biraz daha uzun sürdü

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

Bu, 6.137.810 satırı inceledi ve 5.7.21'de ~ 2.2 saniye ve 8.0.4-rc'de biraz daha uzun sürdü

Örnek 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;

Bu, 6.017.808 satırı inceledi ve 8.0.4-rc'de ~ 4.2 saniye sürdü.

ÖRNEK 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;

Bu, 6.017.908 satırı inceledi ve 8.0.4-rc'de ~ 17.5 saniye sürdü

ÖRNEK 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;

Bu sonsuza dek sürüyordu, bu yüzden onu öldürmek zorunda kaldım.


Bu farklı bir problem. Ve çözüm büyük bir UNION ALL sorgusu.
Paul Spiegel

@PaulSpiegel Sanırım TÜM büyük sendika hakkında şaka yapıyorsun. Seçilen grupların tümünü önceden bilmek gerekeceği ve inanılmaz derecede büyük bir sorgu olacak 2.000 seçilmiş grupla, yukarıdaki en hızlı örnekten bile daha kötü performans göstereceğinden, hayır, bu bir çözüm.
Yoseph

Kesinlikle ciddiyim. Bunu geçmişte birkaç yüz grupla test ettim. Büyük gruplardaki bağları ele almanız gerektiğinde, UNION ALL, MySQL'de en uygun yürütme planını zorlamanın tek yoludur. SELECT DISTINCT(groupID)hızlıdır ve size böyle bir sorgu oluşturmak için ihtiyacınız olan tüm verileri verecektir. max_allowed_packetMySQL 5.7'de varsayılan olarak 4MB olan sorgu boyutunu aşmadığı sürece iyi olmalısınız .
Paul Spiegel

5

bir Grup By kayıtlarındaki son kaydı elde ederken MySQL'i nasıl kullanabileceğinize bakacağız. Örneğin, bu sonuç kümesi varsa.

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

Her kategoride Başlık 3, Başlık 5 ve Başlık 6 olan son gönderiyi almak istiyorum. Kategoriye göre gönderileri almak için MySQL Group By klavyesini kullanacaksınız.

select * from posts group by category_id

Ancak bu sorgudan aldığımız sonuçlar.

id category_id post_title

1 1 Title 1

4 2 Title 4

6 3 Title 6

Grubuna göre her zaman sonuç kümesindeki gruptaki ilk kaydı döndürür.

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

Bu, her grupta en yüksek kimliğe sahip yayınları döndürür.

id category_id post_title

3 1 Title 3

5 2 Title 5

6 3 Title 6

Referans Buraya Tıklayın


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

Cevabınızı biraz ayrıntılandırabilir misiniz? Sorgunuz neden Vijays orijinal sorgusuna tercih ediliyor?
Janfoeh

4

İşte benim çözümüm:

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

Bu, ad başına en son mesajı döndürmez. Ve bu sadece aşırı karmaşık bir versiyonudur SELECT NAME, MAX(MESSAGES) MESSAGES FROM MESSAGE GROUP BY NAME.
Paul Spiegel

Ayrıca, bu formülasyon büyük ölçüde verimsizdir.
Rick James

3

Bunu dene:

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

Hi @Vijay Dev, tablo iletileriniz otomatik olarak birincil anahtar olan artırmayı içeren kimlik içeriyorsa , sorgunuzun aşağıdaki gibi okuması gereken birincil anahtarda en son kayıt esasını almak için:

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

Bu bulduğum en hızlı
CORSAIR

3

Buradan da görüntü alabilirsiniz.

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

İLK ÇÖZÜM

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

İKİNCİ ÇÖZÜM

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

**

Merhaba, bu sorgu yardımcı olabilir:

**

SELECT 
  *
FROM 
  message 

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

2

Bir tablodaki kopyaları silmek için bu yöntemi kullanabilmemizin bir yolu var mı? Sonuç kümesi temel olarak benzersiz kayıtların bir koleksiyonudur, bu nedenle sonuç kümesinde olmayan tüm kayıtları silebilirsek, etkili bir şekilde kopyalarımız olmaz mı? Bunu denedim ama mySQL 1093 hatası verdi.

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)

Çıktıyı geçici bir değişkene kaydetmenin ve sonra NOT IN'den (geçici değişken) silmenin bir yolu var mı? @Çok yararlı bir çözüm için teşekkür ederiz.

EDIT: Ben çözüm bulundu düşünüyorum:

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

Aşağıdaki sorgu, sorunuza göre iyi çalışacaktır.

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

Her biri için son satırı istiyorsanız, Nameher satır grubuna bir sıra numarası Nameve sırasına göre Idazalan sırada verebilirsiniz .

SORGU

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 Keman


2

Buna ne dersin:

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

Ben benzer bir sorun vardı (postgresql zor) ve 1M kayıtlar tablosunda. Bu çözüm, LEFT JOIN ile üretilen 1.7s vs 44s alır. Benim durumumda, ad alanınızın corrispondant'ını NULL değerlere göre filtrelemek zorunda kaldım , bu da 0,2 sn daha iyi performans gösterdi


1

Performans gerçekten endişenizse IsLastInGroup, BIT tipi adlı tabloya yeni bir sütun ekleyebilirsiniz .

Son sütunlarda true olarak ayarlayın ve her satır ekleme / güncelleme / silme işlemiyle koruyun. Yazmalar daha yavaş olacaktır, ancak okumalardan yararlanacaksınız. Kullanım durumunuza bağlıdır ve yalnızca okuma odaklıysanız öneriyorum.

Sorgunuz şöyle görünecek:

SELECT * FROM Messages WHERE IsLastInGroup = 1

Moodle'daki bazı tablolarda bunun gibi bir bayrak sütunu vardır.
Lawrence


0

Sayımla gruplayabilir ve grubun son öğesini aşağıdaki gibi alabilirsiniz:

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

0

Oracle sorgusunun altındaki umut yardımcı olabilir:

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

Başka bir yaklaşım :

Her bir programla birlikte maksimum m2_price ile uygunluğu bulun (1 programda n özellik):

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
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.