Gruplanmış her SQL sonucu grubu için maksimum değerde kayıtlar alma


229

Her gruplanmış küme için maksimum değeri içeren satırları nasıl alırsınız?

Bu soruda aşırı karmaşık bazı varyasyonlar gördüm ve hiçbiri iyi bir cevaba sahip değildi. Mümkün olan en basit örneği bir araya getirmeye çalıştım:

Kişi, grup ve yaş sütunlarıyla aşağıdaki gibi bir tablo verildiğinde, her gruptaki en yaşlı kişiyi nasıl elde edersiniz? (Grup içindeki bir kravat ilk alfabetik sonucu vermelidir)

Person | Group | Age
---
Bob  | 1     | 32  
Jill | 1     | 34  
Shawn| 1     | 42  
Jake | 2     | 29  
Paul | 2     | 36  
Laura| 2     | 39  

İstenen sonuç kümesi:

Shawn | 1     | 42    
Laura | 2     | 39  

3
Dikkat: Kabul Edildi Cevabı yazıldığı sırada 2012'de çalıştı. Ancak, Yorumlar'da verildiği gibi artık birden fazla nedenden dolayı çalışmıyor.
Rick James

Yanıtlar:


132

MySQL'de bunu yapmanın çok basit bir yolu var:

select * 
from (select * from mytable order by `Group`, age desc, Person) x
group by `Group`

Bu çalışır çünkü mysql'de gruplara göre olmayan sütunları toplama izniniz yoktur , bu durumda mysql sadece ilk satırı döndürür . Çözüm, önce verileri, her bir grup için istediğiniz satır ilk olacak şekilde, ardından değerini istediğiniz sütunlara göre gruplandırmaktır.

max()Vb. Bulmaya çalışan karmaşık alt sorgulardan ve aynı maksimum değere sahip birden fazla satır olduğunda (diğer yanıtların yaptığı gibi) birden fazla satır döndürme sorunlarından kaçının

Not: Bu yalnızca mysql çözümdür. Bildiğim diğer tüm veritabanları "birleştirilmiş sütunlar grup yan tümcesi ile grupta listelenmez" veya benzeri bir SQL sözdizimi hatası atar. Bu çözüm belgelenmemiş davranış kullandığından , daha dikkatli olan, MySQL'in gelecekteki bir sürümünün bu davranışı değiştirmesi durumunda çalışmaya devam ettiğini iddia etmek için bir test eklemek isteyebilir .

Sürüm 5.7 güncellemesi:

Sürüm 5.7'den beri, sql-modeayar ONLY_FULL_GROUP_BYvarsayılan olarak içerir , bu nedenle bu işi yapmak için bu seçeneğe sahip olmamanız gerekir (sunucunun bu ayarı kaldırması için seçenek dosyasını düzenleyin).


66
"mysql sadece ilk satırı döndürür." - belki de böyle çalışır ama garanti edilmez. Dokümantasyon diyor ki: "Sunucu aynı oldukları sürece, seçilen değerler belirsiz olan, her gruptan herhangi bir değeri seçmekte serbesttir." . Sunucu, SELECTcümlede görünen ve bir toplama işlevi kullanılarak hesaplanmayan her sütun veya ifade için satırları değil, değerleri (aynı satırdan olması gerekmez) seçer .
axiac

16
Bu davranış, MySQL 5.7.5 üzerinde değişti ve varsayılan olarak, SELECTyan tümcesindeki sütunlar işlevsel olarak GROUP BYsütunlara bağlı olmadığından bu sorguyu reddeder . Kabul etmek üzere yapılandırılırsa (`` ONLY_FULL_GROUP_BY` devre dışı bırakılır), önceki sürümler gibi çalışır (yani, bu sütunların değerleri belirsizdir).
axiac

17
Bu cevabın bu kadar çok oy aldığı için şaşırdım Yanlış ve kötü. Bu sorgunun çalışması garanti edilmez. Bir alt sorgudaki veriler, fıkra sırasına rağmen sırasız bir kümedir. MySQL gerçekten kayıtları şimdi sipariş edebilir ve bu siparişi saklayabilir, ancak gelecekteki bir sürümde yapmayı durdurduysa herhangi bir kuralı ihlal etmeyebilir . Daha sonra GROUP BYbir kayda yoğunlaşır, ancak tüm alanlar keyfi olarak kayıtlardan seçilecektir. Bu olabilir MySQL şu anda sadece her zaman ilk satırı seçmesi olabilir, ama böylesi daha iyi başka herhangi bir satır veya hatta değerlerini almak olabilir farklı bir gelecek sürümünde satır.
Thorsten Kettner

9
Tamam, burada aynı fikirde değiliz. Şu anda işe yarayan ve umarım bunu kapsayacak bazı testlere dayanan belgesiz özellikler kullanmıyorum. Şu anki uygulamanın size belgelerin belirsiz değerlere sahip olabileceğinizi açıkça belirttiği ilk kaydı aldığından şanslı olduğunuzu biliyorsunuz, ancak yine de kullanıyorsunuz. Bazı basit oturum veya veritabanı ayarları bunu her zaman değiştirebilir. Bunu çok riskli olarak değerlendiririm.
Thorsten Kettner

3
Bu cevap yanlış görünüyor. Başına doc , sunucu Ayrıca, her gruptan değerlerin seçimi ORDER BY yan tümcesi eklenerek etkisinde olamaz her gruptan ... dan herhangi bir değeri seçmekte serbesttir. Sonuç kümesi sıralaması, değerler seçildikten sonra gerçekleşir ve ORDER BY, sunucunun seçtiği her grupta hangi değeri etkilemez.
Tgr

296

Doğru çözüm:

SELECT o.*
FROM `Persons` o                    # 'o' from 'oldest person in group'
  LEFT JOIN `Persons` b             # 'b' from 'bigger age'
      ON o.Group = b.Group AND o.Age < b.Age
WHERE b.Age is NULL                 # bigger age not found

Nasıl çalışır:

Her satırı, sütunda aynı değere ve sütunda daha büyük bir değere sahip otüm satırlarla eşleştirir . Sütunda kendi grubunun maksimum değerine sahip olmayan herhangi bir satır, bir veya daha fazla satırla eşleşir .bGroupAgeoAgeb

LEFT JOINO tam bir sıra (kendi grubunda yalnız kişileri dahil) grubundaki en eski kişiyi eşleşmesi yapar NULLden s b( 'grubunda büyük yaş').
Kullanılması INNER JOINbu satırların eşleşmemesini sağlar ve yok sayılır.

WHEREFıkra sahip satırları tutar NULLçıkarılan alanlarda s b. Onlar her gruptan en yaşlı kişilerdir.

Diğer okumalar

Bu çözüm ve diğerleri SQL Antipatterns: Veritabanı Programlamanın Tuzaklarından Kaçınma kitabında açıklanmıştır


43
BTW o.Age = b.Age, örneğin grup 2'deki Paul Laura gibi 39'da ise , aynı grup için iki veya daha fazla satır döndürebilir . Ancak böyle bir davranış istemezsek yapabiliriz:ON o.Group = b.Group AND (o.Age < b.Age or (o.Age = b.Age and o.id < b.id))
Todor

8
İnanılmaz! 20M kayıtları için "naif" algoritmadan 50 kat daha hızlıdır (max () ile bir alt
sorguya katıl

3
@Todor yorumlarıyla mükemmel çalışır. Ben daha fazla sorgu koşulları varsa onlar FROM ve SOL JOIN eklenmelidir eklemek istiyorum. GİBİ
BİR ŞEYDEN

1
@AlainZelink, orijinal "axiac cevabı gerekli olmayan alt sorguları tanıtmamak için bu" daha fazla sorgu koşulları "nihai WHERE koşul listesinde daha iyi koymak değil mi?
tarifeler

5
Bu çözüm işe yaradı; ancak, aynı kimliği paylaşan 10.000'den fazla satırla denendiğinde yavaş sorgu günlüğünde bildirilmeye başlandı. Dizine eklenen sütuna KATILDI. Nadir bir vaka, ama söz etmeye değer.
chaseisabelle

50

Sen çeker bir alt sorgu karşı katılabilir MAX(Group)ve Age. Bu yöntem çoğu RDBMS'de taşınabilir.

SELECT t1.*
FROM yourTable t1
INNER JOIN
(
    SELECT `Group`, MAX(Age) AS max_age
    FROM yourTable
    GROUP BY `Group`
) t2
    ON t1.`Group` = t2.`Group` AND t1.Age = t2.max_age;

Michael, bunun için teşekkürler - ama Bohemian'ın yorumlarına göre, bağlarda birden fazla satır döndürme sorununa bir cevabınız var mı?
Yarin

1
@Yarin Örneğin 2 Group = 2, Age = 20alt satır olsaydı, alt sorgu bunlardan birini döndürürdü, ancak birleştirme ONcümlesi her ikisiyle de eşleşirdi , böylece diğer sütunlar için farklı değerlere rağmen aynı grup / yaşla birlikte 2 satır alırsınız, bir yerine.
Michael Berkowski

Yalnızca Bohemians MySQL yoluna gitmediğimiz sürece sonuçları grup başına bir ile sınırlamanın imkansız olduğunu mu söylüyoruz?
Yarin

@Yarin imkansız değil, sadece ek sütunlar varsa daha fazla iş gerektirir - muhtemelen grup / yaş çifti gibi her biri için maksimum ilişkili kimliği çekmek için başka bir iç içe alt sorgu, daha sonra kimliğe dayalı olarak satırın geri kalanını almak için buna katılın.
Michael Berkowski

Bu kabul edilen cevap olmalıdır (şu anda kabul edilen cevap diğer birçok RDBMS'de başarısız olacaktır ve aslında MySQL'in birçok sürümünde bile başarısız olacaktır).
Tim Biegeleisen

28

SQLite (ve muhtemelen MySQL) için basit çözümüm:

SELECT *, MAX(age) FROM mytable GROUP BY `Group`;

Ancak PostgreSQL ve diğer bazı platformlarda çalışmaz.

PostgreSQL'de DISTINCT ON yan tümcesini kullanabilirsiniz :

SELECT DISTINCT ON ("group") * FROM "mytable" ORDER BY "group", "age" DESC;

@ Bohemian üzgünüm, bunu biliyorum, bu sadece MySQL-sadece toplanmamış sütunlar içerdiği için
Cec

2
@IgorKulagin - Postgres'te çalışmıyor - Hata iletisi: "mytable.id" sütunu GROUP BY deyiminde görünmeli veya bir toplama işlevinde kullanılmalıdır
Yarin

13
MySQL sorgusu yalnızca birçok durumda yanlışlıkla çalışabilir. "SELECT *" ait MAX'a (yaşa) karşılık gelmeyen bilgileri döndürebilir. Bu cevap yanlış. Bu muhtemelen SQLite için de geçerlidir.
Albert Hendriks

2
Ancak bu, gruplandırılmış sütunu ve maks sütunu seçmemiz gereken duruma uygundur. Bu, sonuçlanacağı yukarıdaki gereksinime uymuyor ('Bob', 1, 42), ancak beklenen sonuç ('Shawn', 1, 42)
Ram Babu S

1
Postgres için iyi
Karol Gasienica

4

Sıralama yöntemi kullanma.

SELECT @rn :=  CASE WHEN @prev_grp <> groupa THEN 1 ELSE @rn+1 END AS rn,  
   @prev_grp :=groupa,
   person,age,groupa  
FROM   users,(SELECT @rn := 0) r        
HAVING rn=1
ORDER  BY groupa,age DESC,person

sel - açıklamaya ihtiyacım var - Daha önce hiç görmedim :=- bu nedir?
Yarin


Bu konuya girmem gerekecek - Bence cevap senaryomuzu çok karmaşık hale getiriyor, ama bana yeni bir şey öğrettiğiniz için teşekkürler ..
Yarin

3

MySQL'in row_number işlevi olup olmadığından emin değilim. Eğer öyleyse, istediğiniz sonucu elde etmek için kullanabilirsiniz. SQL Server'da aşağıdakine benzer bir şey yapabilirsiniz:

CREATE TABLE p
(
 person NVARCHAR(10),
 gp INT,
 age INT
);
GO
INSERT  INTO p
VALUES  ('Bob', 1, 32);
INSERT  INTO p
VALUES  ('Jill', 1, 34);
INSERT  INTO p
VALUES  ('Shawn', 1, 42);
INSERT  INTO p
VALUES  ('Jake', 2, 29);
INSERT  INTO p
VALUES  ('Paul', 2, 36);
INSERT  INTO p
VALUES  ('Laura', 2, 39);
GO

SELECT  t.person, t.gp, t.age
FROM    (
         SELECT *,
                ROW_NUMBER() OVER (PARTITION BY gp ORDER BY age DESC) row
         FROM   p
        ) t
WHERE   t.row = 1;

1
8.0'dan beri.
Ilja Everilä

2

axiac'ın çözümü sonunda benim için en iyi çözümdü. Ancak ek bir karmaşıklık vardı: hesaplanan bir "maksimum değer", iki sütundan türetilmiş.

Aynı örneği kullanalım: Her gruptaki en yaşlı kişiyi istiyorum. Aynı derecede yaşlı insanlar varsa, en uzun kişiyi alın.

Bu davranışı elde etmek için iki kez sol katılmak zorunda kaldı:

SELECT o1.* WHERE
    (SELECT o.*
    FROM `Persons` o
    LEFT JOIN `Persons` b
    ON o.Group = b.Group AND o.Age < b.Age
    WHERE b.Age is NULL) o1
LEFT JOIN
    (SELECT o.*
    FROM `Persons` o
    LEFT JOIN `Persons` b
    ON o.Group = b.Group AND o.Age < b.Age
    WHERE b.Age is NULL) o2
ON o1.Group = o2.Group AND o1.Height < o2.Height 
WHERE o2.Height is NULL;

Bu yardımcı olur umarım! Sanırım bunu yapmanın daha iyi bir yolu olmalı ...


2

Çözümüm yalnızca tek bir sütun almanız gerektiğinde çalışır, ancak ihtiyaçlarım için performans açısından en iyi çözüm bulundu (yalnızca tek bir sorgu kullanın!):

SELECT SUBSTRING_INDEX(GROUP_CONCAT(column_x ORDER BY column_y),',',1) AS xyz,
   column_z
FROM table_name
GROUP BY column_z;

Sıralı bir concat listesi oluşturmak için GROUP_CONCAT kullanın ve sonra sadece birinciye alt dize.


Group_concat içinde aynı anahtarı sıralayarak birden fazla sütun alabileceğinizi onaylayabilir, ancak her sütun için ayrı bir group_concat / index / substring yazmanız gerekir.
Rasika

Buradaki bonus, group_concat içindeki sıralamaya birden çok sütun ekleyebilmenizdir ve bağları kolayca çözer ve grup başına yalnızca bir kayıt garanti eder. Basit ve verimli bir çözümle aferin!
Rasika

2

Kullanarak basit bir çözümüm var WHERE IN

SELECT a.* FROM `mytable` AS a    
WHERE a.age IN( SELECT MAX(b.age) AS age FROM `mytable` AS b GROUP BY b.group )    
ORDER BY a.group ASC, a.person ASC

1

CTE'leri Kullanma - Ortak Tablo İfadeleri:

WITH MyCTE(MaxPKID, SomeColumn1)
AS(
SELECT MAX(a.MyTablePKID) AS MaxPKID, a.SomeColumn1
FROM MyTable1 a
GROUP BY a.SomeColumn1
  )
SELECT b.MyTablePKID, b.SomeColumn1, b.SomeColumn2 MAX(b.NumEstado)
FROM MyTable1 b
INNER JOIN MyCTE c ON c.MaxPKID = b.MyTablePKID
GROUP BY b.MyTablePKID, b.SomeColumn1, b.SomeColumn2

--Note: MyTablePKID is the PrimaryKey of MyTable

1

Oracle'da aşağıdaki sorgu istenen sonucu verebilir.

SELECT group,person,Age,
  ROWNUMBER() OVER (PARTITION BY group ORDER BY age desc ,person asc) as rankForEachGroup
  FROM tablename where rankForEachGroup=1

0
with CTE as 
(select Person, 
[Group], Age, RN= Row_Number() 
over(partition by [Group] 
order by Age desc) 
from yourtable)`


`select Person, Age from CTE where RN = 1`

0

Ayrıca deneyebilirsiniz

SELECT * FROM mytable WHERE age IN (SELECT MAX(age) FROM mytable GROUP BY `Group`) ;

1
Teşekkürler, ancak bu bir kravat olduğunda bir yaş için birden fazla kayıt döndürür
Yarin

Ayrıca, grup 1'de 39 yaşında olması durumunda bu sorgu yanlış olur. Bu durumda, grup 1'deki maksimum yaş daha yüksek olmasına rağmen bu kişi de seçilir.
Joshua Richardson

0

Ayrılmış sözcük olduğundan Grup sütun adı olarak kullanmak olmaz. Ancak aşağıdaki SQL çalışır.

SELECT a.Person, a.Group, a.Age FROM [TABLE_NAME] a
INNER JOIN 
(
  SELECT `Group`, MAX(Age) AS oldest FROM [TABLE_NAME] 
  GROUP BY `Group`
) b ON a.Group = b.Group AND a.Age = b.oldest

Teşekkürler, ancak bu bir kravat olduğunda bir yaş için birden fazla kayıt döndürür
Yarin

@Yarin hangisinin doğru yaşlı kişi olduğuna nasıl karar verirsiniz? Birden fazla cevap en doğru cevap gibi görünüyor aksi takdirde kullanım sınırı ve düzeni
Duncan


0

tablo adının insanlar olmasına izin ver

select O.*              -- > O for oldest table
from people O , people T
where O.grp = T.grp and 
O.Age = 
(select max(T.age) from people T where O.grp = T.grp
  group by T.grp)
group by O.grp; 

0

Kimliksizden kimlik (ve tüm coulmns) gerekiyorsa

SELECT
    *
FROM
    mytable
WHERE
    id NOT IN (
        SELECT
            A.id
        FROM
            mytable AS A
        JOIN mytable AS B ON A. GROUP = B. GROUP
        AND A.age < B.age
    )

0

Bu nasıl mysql grup başına N max satırları alıyorum

SELECT co.id, co.person, co.country
FROM person co
WHERE (
SELECT COUNT(*)
FROM person ci
WHERE  co.country = ci.country AND co.id < ci.id
) < 1
;

nasıl çalışır:

  • masaya kendi kendine katıl
  • gruplar tarafından yapılır co.country = ci.country
  • Grup başına N elemanı ) < 13 eleman için kontrol edilir -) <3
  • max veya min almak şunlara bağlıdır: co.id < ci.id
    • ortak kimlik <ci.id - maks
    • co.id> ci.id - min

Tam örnek burada:

mysql grup başına n maksimum değer seç

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.