MySQL "Gruplama Ölçütü" ve "Sıralama Ölçütü"


98

Bir e-posta tablosundan bir dizi satır seçip gönderene göre gruplayabilmek istiyorum. Sorgum şuna benziyor:

SELECT 
    `timestamp`, `fromEmail`, `subject`
FROM `incomingEmails` 
GROUP BY LOWER(`fromEmail`) 
ORDER BY `timestamp` DESC

Sorgu neredeyse istediğim gibi çalışıyor - e-postaya göre gruplanmış kayıtları seçiyor. Sorun, konu ve zaman damgasının belirli bir e-posta adresi için en son kayda karşılık gelmemesidir.

Örneğin, şunu döndürebilir:

fromEmail: john@example.com, subject: hello
fromEmail: mark@example.com, subject: welcome

Veritabanındaki kayıtlar:

fromEmail: john@example.com, subject: hello
fromEmail: john@example.com, subject: programming question
fromEmail: mark@example.com, subject: welcome

"Programlama sorusu" konusu en yeniyse, e-postaları gruplandırırken MySQL'in bu kaydı seçmesini nasıl sağlayabilirim?

Yanıtlar:


142

Basit bir çözüm SİPARİŞ deyimi ile bir subselect sorguyu sarılmasıdır ilk ve GROUP BY uygulanması daha sonra :

SELECT * FROM ( 
    SELECT `timestamp`, `fromEmail`, `subject`
    FROM `incomingEmails` 
    ORDER BY `timestamp` DESC
) AS tmp_table GROUP BY LOWER(`fromEmail`)

Bu, birleşimi kullanmaya benzer ancak çok daha hoş görünüyor.

Bir SELECT'te toplu olmayan sütunları GROUP BY yan tümcesine sahip kullanmak standart değildir. MySQL genellikle bulduğu ilk satırın değerlerini döndürür ve gerisini atar. ORDER BY yan tümceleri, atılanlar için değil, yalnızca döndürülen sütun değeri için geçerli olacaktır.

ÖNEMLİ GÜNCELLEME Pratikte çalışmak için kullanılan toplu olmayan sütunların seçilmesine güvenilmemelidir. Başına MySQL belgelerinde "Bu GROUP BY adlı değil her nonaggregated sütundaki tüm değerleri her grup için aynıdır öncelikle zaman yararlıdır. Sunucusudur herhangi bir değeri seçmekte özgür yüzden, her gruptan onlar değerleri aynı olmadıkça seçilenler belirsizdir . "

İtibariyle 5.7.5 ONLY_FULL_GROUP_BY sorgu hataları neden varsayılan bu yüzden olmayan agrega sütunları (ER_WRONG_FIELD_WITH_GROUP) olarak etkindir

@Mikep'in aşağıda işaret ettiği gibi çözüm, 5.7 ve üzeri ANY_VALUE () kullanmaktır.

Bkz. Http://www.cafewebmaster.com/mysql-order-sort-group https://dev.mysql.com/doc/refman/5.6/en/group-by-handling.html https: //dev.mysql .com / doc / refman / 5.7 / en / group-by-handling.html https://dev.mysql.com/doc/refman/5.7/en/miscellaneous-functions.html#function_any-value


7
Birkaç yıl önce aynı çözümü buldum ve bu harika bir çözüm. tebrikler b7kich. Yine de burada iki sorun var ... GROUP BY büyük / küçük harfe duyarlı değildir, bu nedenle LOWER () gereksizdir ve ikincisi, $ userID doğrudan PHP'den gelen bir değişken gibi görünür, $ userID kullanıcı tarafından sağlanırsa ve zorunlu değilse kodunuz sql injection savunmasız olabilir bir tamsayı olmak.
velcrow

ÖNEMLİ GÜNCELLEME MariaDB için de geçerlidir: mariadb.com/kb/en/mariadb/…
Arthur Shipkowski

1
As of 5.7.5 ONLY_FULL_GROUP_BY is enabled by default, i.e. it's impossible to use non-aggregate columns.SQL modu, yönetici ayrıcalıkları olmadan çalıştırma sırasında değiştirilebilir, bu nedenle ONLY_FULL_GROUP_BY'yi devre dışı bırakmak çok kolaydır. Örneğin: SET SESSION sql_mode = '';. Demo: db-fiddle.com/f/esww483qFQXbXzJmkHZ8VT/3
mikep

1
Veya ONLY_FULL_GROUP_BY etkinleştirilmiş atlamanın başka bir alternatifi ANY_VALUE () kullanmaktır. Daha fazlasını görün dev.mysql.com/doc/refman/8.0/en/…
mikep

Bu cevap YANLIŞ olduğunu
Mark

44

İşte bir yaklaşım:

SELECT cur.textID, cur.fromEmail, cur.subject, 
     cur.timestamp, cur.read
FROM incomingEmails cur
LEFT JOIN incomingEmails next
    on cur.fromEmail = next.fromEmail
    and cur.timestamp < next.timestamp
WHERE next.timestamp is null
and cur.toUserID = '$userID' 
ORDER BY LOWER(cur.fromEmail)

Temel olarak, sonraki satırları arayarak masaya kendi başına katılırsınız. Where cümlesinde, daha sonraki satırların olamayacağını belirtirsiniz. Bu size yalnızca en son satırı verir.

Aynı zaman damgasına sahip birden fazla e-posta olabilirse, bu sorgunun iyileştirilmesi gerekir. E-posta tablosunda artımlı bir kimlik sütunu varsa, JOIN'i şu şekilde değiştirin:

LEFT JOIN incomingEmails next
    on cur.fromEmail = next.fromEmail
    and cur.id < next.id

Bunun textIDbelirsiz olduğunu söyledi = /
John Kurlak

1
Sonra ambuigity'yi kaldırın ve tablo adının önüne cur.textID gibi ekleyin. Cevapta da değişti.
Andomar

Doctrine DQL ile yapılabilecek tek çözüm budur.
VisioN

Birden çok sütun için kendi kendine katılmaya çalışırken bu işe yaramaz. IE, en son e-postayı ve en son kullanıcı adını bulmaya çalışırken ve bu işlemi tek bir sorguda gerçekleştirmek için birden çok kendi kendine bırakılan birleştirmeye ihtiyacınız olduğunda.
Loveen Dyall

Geçmiş ve gelecekteki zaman damgaları / tarihlerle çalışırken, sonuç kümesini gelecekteki olmayan tarihlerle sınırlamak için, LEFT JOINölçütlere başka bir koşul eklemeniz gerekirAND next.timestamp <= UNIX_TIMESTAMP()
Will B.

34

Zaten bir yanıtta işaret edildiği gibi, mevcut yanıt yanlıştır, çünkü GROUP BY keyfi olarak pencereden kaydı seçer.

MySQL 5.6 veya MySQL 5.7 kullanılıyorsa ONLY_FULL_GROUP_BY, doğru (deterministik) sorgu şu şekildedir:

SELECT incomingEmails.*
  FROM (
    SELECT fromEmail, MAX(timestamp) `timestamp`
    FROM incomingEmails
    GROUP BY fromEmail
  ) filtered_incomingEmails
  JOIN incomingEmails USING (fromEmail, timestamp)
GROUP BY fromEmail, timestamp

Sorgunun verimli bir şekilde çalışması için uygun indeksleme gereklidir.

Basitleştirme amacıyla, LOWER()çoğu durumda kullanılmayacak olan öğesini kaldırdığımı unutmayın.


2
Bu doğru cevap olmalı. Web sitemde bununla ilgili bir hata keşfettim. order byDiğer yanıtlar subselect içinde, hiç bir etkisi yoktur.
Jette

1
Aman Tanrım, lütfen bunu kabul edilen cevap yap. Kabul edilen kişi zamanımın 5 saatini boşa harcadı :(
Richard Kersey

29

Sorgunuzu GROUP BY ile şu şekilde sarmalayarak ORDER BY'dan sonra GROUP BY yapın:

SELECT t.* FROM (SELECT * FROM table ORDER BY time DESC) t GROUP BY t.from

1
Öyleyse GROUP BY` otomatik olarak timeen yeniyi time, en yeniyi veya rastgele seçer ?
xrDDDD

1
En yeni zamanı seçer çünkü sıralama yaparız time DESCve ardından grup ilkini (en son) alır.
11101101b

Şimdi eğer sadece mysql 5.1'de VIEWS'deki alt seçimler üzerinde JOINS yapabilirsem. Belki bu özellik daha yeni bir sürümde gelir.
IcarusNM

22

SQL standardına göre, seçim listesinde toplu olmayan sütunlar kullanamazsınız. MySQL bu tür kullanıma izin verir (çok az ONLY_FULL_GROUP_BY modu kullanılır) ancak sonuç tahmin edilemez.

ONLY_FULL_GROUP_BY

Önce E-posta, MIN (okuma) ve ardından ikinci sorgu (veya alt sorgu) - Konu arasından seçim yapmalısınız.


MIN (okuma) minimum "okuma" değerini döndürür. Muhtemelen en son e-postanın "oku" bayrağını arıyor.
Andomar

2

Gösterilenden daha karmaşık sorgular için bu iki yaklaşımla mücadele ettim, çünkü alt sorgulama yaklaşımı, hangi indeksleri koyarsam yapayım korkunç derecede yetersizdi ve Hibernate aracılığıyla dıştaki kendi kendine birleşmeyi elde edemediğim için

Bunu yapmanın en iyi (ve en kolay) yolu, ihtiyaç duyduğunuz alanların birleşimini içerecek şekilde oluşturulmuş bir şeye göre gruplamak ve sonra bunları SELECT yan tümcesinde ifadeler kullanarak çıkarmaktır. Bir MAX () yapmanız gerekiyorsa, MAX () üzerinde olmasını istediğiniz alanın her zaman birleştirilmiş varlığın en önemli ucunda olduğundan emin olun.

Bunu anlamanın anahtarı, sorgunun yalnızca bu diğer alanların Max () değerini karşılayan herhangi bir varlık için değişmez olması durumunda anlamlı olabileceğidir, bu nedenle sıralama açısından birleştirmenin diğer parçaları göz ardı edilebilir. Bunun nasıl yapılacağını bu bağlantının en altında açıklıyor. http://dev.mysql.com/doc/refman/5.0/en/group-by-hidden-columns.html

Alanların birleştirilmesini önceden hesaplamak için bir ekleme / güncelleme olayı (bir tetikleyici gibi) alabilirseniz, onu dizine alabilirsiniz ve sorgu, sanki grup, gerçekte MAX'a ( ). Maksimum birden fazla alan elde etmek için bile kullanabilirsiniz. İç içe kümeler olarak ifade edilen çok boyutlu ağaçlara karşı sorgular yapmak için kullanıyorum.

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.