GROUP BY deyiminde görünmeli veya toplama işlevinde kullanılmalıdır


276

Bu arayan 'makerar' gibi bir tablo var

 cname  | wmname |          avg           
--------+-------------+------------------------
 canada | zoro   |     2.0000000000000000
 spain  | luffy  | 1.00000000000000000000
 spain  | usopp  |     5.0000000000000000

Ve her cname için maksimum ort. Seçmek istiyorum.

SELECT cname, wmname, MAX(avg)  FROM makerar GROUP BY cname;

ama bir hata alırım,

ERROR:  column "makerar.wmname" must appear in the GROUP BY clause or be used in an   aggregate function 
LINE 1: SELECT cname, wmname, MAX(avg)  FROM makerar GROUP BY cname;

bu yüzden bunu yaparım

SELECT cname, wmname, MAX(avg)  FROM makerar GROUP BY cname, wmname;

ancak bu amaçlanan sonuçları vermeyecektir ve aşağıdaki yanlış çıktı gösterilmektedir.

 cname  | wmname |          max           
--------+--------+------------------------
 canada | zoro   |     2.0000000000000000
 spain  | luffy  | 1.00000000000000000000
 spain  | usopp  |     5.0000000000000000

Gerçek Sonuçlar

 cname  | wmname |          max           
--------+--------+------------------------
 canada | zoro   |     2.0000000000000000
 spain  | usopp  |     5.0000000000000000

Bu sorunu nasıl düzeltebilirim?

Not: Bu tablo, önceki bir işlemden oluşturulan bir GÖRÜNÜMdür.



Anlamıyorum. Neden wmname="usopp"bekleniyor, örneğin değil wmname="luffy"?
AndreKR

Yanıtlar:


226

Evet, bu yaygın bir toplama sorunudur. SQL3'ten (1999) önce , seçilen alanlar GROUP BY[*] yan tümcesinde görünmelidir .

Bu soruna geçici bir çözüm bulmak için, bir alt sorgudaki toplamı hesaplamanız ve ardından göstermeniz gereken ek sütunları almak için kendisiyle birleştirmeniz gerekir:

SELECT m.cname, m.wmname, t.mx
FROM (
    SELECT cname, MAX(avg) AS mx
    FROM makerar
    GROUP BY cname
    ) t JOIN makerar m ON m.cname = t.cname AND t.mx = m.avg
;

 cname  | wmname |          mx           
--------+--------+------------------------
 canada | zoro   |     2.0000000000000000
 spain  | usopp  |     5.0000000000000000

Ancak daha basit görünen pencere işlevlerini de kullanabilirsiniz:

SELECT cname, wmname, MAX(avg) OVER (PARTITION BY cname) AS mx
FROM makerar
;

Bu yöntemle ilgili tek şey, tüm kayıtları göstermesidir (pencere işlevleri gruplanmaz). Ancak , her satırdaki ülke için doğru (yani maks. cnameDüzey) gösterecektir MAX, bu yüzden size kalmış:

 cname  | wmname |          mx           
--------+--------+------------------------
 canada | zoro   |     2.0000000000000000
 spain  | luffy  |     5.0000000000000000
 spain  | usopp  |     5.0000000000000000

(cname, wmname)Maksimum değere uyan tek tupleri göstermek için tartışmasız daha az zarif olan çözüm :

SELECT DISTINCT /* distinct here matters, because maybe there are various tuples for the same max value */
    m.cname, m.wmname, t.avg AS mx
FROM (
    SELECT cname, wmname, avg, ROW_NUMBER() OVER (PARTITION BY avg DESC) AS rn 
    FROM makerar
) t JOIN makerar m ON m.cname = t.cname AND m.wmname = t.wmname AND t.rn = 1
;


 cname  | wmname |          mx           
--------+--------+------------------------
 canada | zoro   |     2.0000000000000000
 spain  | usopp  |     5.0000000000000000

[*]: İlginç bir şekilde, spesifik olmayan gruplandırılmış alanların seçilmesine izin vermesine rağmen, büyük motorlar gerçekten hoşlanmıyor gibi görünüyor. Oracle ve SQLServer buna izin vermiyor. Mysql varsayılan olarak izin verirdi, ancak şimdi 5.7'den beri yöneticinin ONLY_FULL_GROUP_BYbu özelliğin desteklenmesi için sunucu yapılandırmasında bu seçeneği ( ) manuel olarak etkinleştirmesi gerekiyor ...


1
Teşekkürler sözdizimi doğrudur, ancak katılırken mx ve avg değerlerini karşılaştırmalısınız
RandomGuy

1
Evet sözdizim doğru ve yinelenenleri ortadan kaldırır, ancak amaçlanan sonuçları elde etmek için sonunda (JOING yazdıktan sonra) m.avg = t.mx gerekir
RandomGuy

1
@Sebas Katılmadan yapılabilir MAX(@ypercube tarafından cevaba bakınız, cevabımda başka bir çözüm daha var) ama bunu yapma şekliniz değil. Beklenen çıktıyı kontrol edin.
zero323

1
@Sebas Çözümünüz yalnızca bir sütun (MAX avgbaşına cname) ekler, ancak sonuç satırlarını kısıtlamaz (OP'nin istediği gibi). Bkz. Gerçek Sonuçlar , sorudaki paragraf olmalıdır .
ypercubeᵀᴹ

1
Torna kapalı ONLY_FULL_GROUP_BY MySQL 5.7 yolu aktive etmez sütunlar dahil edilmeyebilir SQL standart belirtir group by(veya Postgres gibi MySQL davranmak yapar). Sadece MySQL'in rastgele (= "belirsiz") sonuçlar döndürdüğü eski davranışa geri döner.
a_horse_with_no_name

126

Postgres'te özel DISTINCT ON (expression)sözdizimini de kullanabilirsiniz :

SELECT DISTINCT ON (cname) 
    cname, wmname, avg
FROM 
    makerar 
ORDER BY 
    cname, avg DESC ;

5
Biri avg
amenzhinsky

@amenzhinsky Ne demek istiyorsun? Biri sonuç kümesinin bundan farklı bir sıra ile sıralanmasını istiyorsa BY cname?
ypercubeᵀᴹ

@ypercube, Aslında psql önce sıralar ve sonra DISTINCT uygular.
Avg'ye

3
Elbette. Gönderdiğim sorguyu çalıştırmazsanız, farklı sonuçlar alırsınız! Bu, "beklendiği gibi çalışmaz" ile aynı değildir ...
ypercubeᵀᴹ

1
@Batfan thnx. Bu oldukça havalı, kompakt ve yazması kolay olsa da, bu tür sorgular için genellikle en etkili yol olmadığını unutmayın.
ypercubeᵀᴹ

27

Seçimlerde gruplandırılmamış ve birleştirilmemiş alanları belirtmeyle ilgili sorun group by, motorun bu durumda hangi kaydın alanını döndürmesi gerektiğini bilmemesidir. İlk mi? Sonuncu mu? Genellikle doğal olarak toplanan sonuca karşılık gelen ( minve maxistisnalar olan) bir kayıt yoktur.

Ancak, bir geçici çözüm vardır: gerekli alanı da toplayın. Pozgreslerde, bu işe yaramalıdır:

SELECT cname, (array_agg(wmname ORDER BY avg DESC))[1], MAX(avg)
FROM makerar GROUP BY cname;

Bunun avg tarafından sıralanan tüm wnames dizisini oluşturduğunu ve ilk öğeyi döndürdüğünü unutmayın (postgres dizileri 1 tabanlıdır).


İyi bir nokta. Her ne kadar DB'nin her satırdan toplanmamış alanları satırın katkıda bulunduğu toplu sonuca bağlamak için bir dış birleşim yapabilmesi mümkün görünmektedir. Neden bunun için bir seçenekleri olmadığını merak ediyordum. Bu seçeneği bilmememe rağmen :)
Ben Simmons

16
SELECT t1.cname, t1.wmname, t2.max
FROM makerar t1 JOIN (
    SELECT cname, MAX(avg) max
    FROM makerar
    GROUP BY cname ) t2
ON t1.cname = t2.cname AND t1.avg = t2.max;

rank() Pencere fonksiyonunu kullanma :

SELECT cname, wmname, avg
FROM (
    SELECT cname, wmname, avg, rank() 
    OVER (PARTITION BY cname ORDER BY avg DESC)
    FROM makerar) t
WHERE rank = 1;

Not

Her iki grupta birden fazla maksimum değer korunacaktır. Avg max değerine eşit birden fazla kayıt olsa bile, grup başına yalnızca tek bir kayıt istiyorsanız @ ypercube yanıtını kontrol etmelisiniz.


16

Benim için bu bir "ortak toplama sorunu" ile ilgili değil, sadece yanlış bir SQL sorgusu ile ilgili. "Her bir cname için maksimum ort. Seçin" için tek doğru cevap:

SELECT cname, MAX(avg) FROM makerar GROUP BY cname;

Sonuç:

 cname  |      MAX(avg)
--------+---------------------
 canada | 2.0000000000000000
 spain  | 5.0000000000000000

Bu sonuç genel olarak "Her grup için en iyi sonuç nedir?" . İspanya için en iyi sonucun 5, Kanada için en iyi sonucun 2 olduğunu görüyoruz. Bu doğrudur ve bir hata yoktur. Wmname'i de göstermemiz gerekiyorsa , " Sonuç kümesinden wmname'i seçmek için KURAL nedir ?" Hatayı netleştirmek için giriş verilerini biraz değiştirelim:

  cname | wmname |        avg           
--------+--------+-----------------------
 spain  | zoro   |  1.0000000000000000
 spain  | luffy  |  5.0000000000000000
 spain  | usopp  |  5.0000000000000000

Neden bu sorguyu runnig üzerinde bekliyorsunuz Hangi: SELECT cname, wmname, MAX(avg) FROM makerar GROUP BY cname;? Olmalı mı spain+luffyyoksa spain+usopp? Neden? O değil belirlendiği nasıl "daha iyi" seçmek için sorguda wmname birkaç uygunsa sonuç da belirlenmez yüzden. Bu yüzden SQL yorumlayıcısı bir hata döndürür - sorgu doğru değildir.

Diğer bir deyişle, spain"Gruptaki en iyi kim ?" Sorusuna doğru bir cevap yoktur. . Luffy usopp'den daha iyi değildir, çünkü usopp aynı "skor" a sahiptir.


Bu çözüm de benim için çalıştı. Benim ORM de aşağıdaki yanlış sorguyla sonuçlanan, ilişkili birincil anahtar dahil sorgu sorunları vardı : SELECT cname, id, MAX(avg) FROM makerar GROUP BY cname;Bu yanıltıcı hata verdi.
Roberto

1

Bu da işe yarıyor gibi görünüyor

SELECT *
FROM makerar m1
WHERE m1.avg = (SELECT MAX(avg)
                FROM makerar m2
                WHERE m1.cname = m2.cname
               )

0

Son zamanlarda kullanarak saymaya çalışırken bu sorunla karşılaşıyorum case whenve whichve countifadelerinin sırasını değiştirerek sorunu giderir bulundu :

SELECT date(dateday) as pick_day,
COUNT(CASE WHEN (apples = 'TRUE' OR oranges 'TRUE') THEN fruit END)  AS fruit_counter

FROM pickings

GROUP BY 1

Kullanmak yerine - ikincisinde, elma ve portakalların toplu işlevlerde görünmesi gereken hatalar aldım

CASE WHEN ((apples = 'TRUE' OR oranges 'TRUE') THEN COUNT(*) END) END AS fruit_counter

1
whichİfade mi?
Hillary Sanders
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.