Grup başına en yüksek / en küçük <ne olursa olsun> kayıtları alın


88

Bu nasıl yapılır?

Bu sorunun eski başlığı " alt sorgularla karmaşık sorguda rank (@Rank: = @Rank + 1) kullanmak - işe yarayacak mı? " İdi çünkü dereceleri kullanarak çözüm arıyordum, ancak şimdi Bill tarafından gönderilen çözümün çok daha iyi.

Orijinal soru:

Belirli bir sıra verildiğinde her gruptan son kaydı alacak bir sorgu oluşturmaya çalışıyorum:

SET @Rank=0;

select s.*
from (select GroupId, max(Rank) AS MaxRank
      from (select GroupId, @Rank := @Rank + 1 AS Rank 
            from Table
            order by OrderField
            ) as t
      group by GroupId) as t 
  join (
      select *, @Rank := @Rank + 1 AS Rank
      from Table
      order by OrderField
      ) as s 
  on t.GroupId = s.GroupId and t.MaxRank = s.Rank
order by OrderField

İfade @Rank := @Rank + 1normalde sıralama için kullanılır, ancak benim için 2 alt sorguda kullanıldığında şüpheli görünüyor, ancak yalnızca bir kez başlatıldı. Bu şekilde çalışacak mı?

İkincisi, birden çok kez değerlendirilen bir alt sorgu ile çalışacak mı? Where (veya having) cümlesindeki alt sorgu gibi (yukarıdakileri yazmanın başka bir yolu):

SET @Rank=0;

select Table.*, @Rank := @Rank + 1 AS Rank
from Table
having Rank = (select max(Rank) AS MaxRank
              from (select GroupId, @Rank := @Rank + 1 AS Rank 
                    from Table as t0
                    order by OrderField
                    ) as t
              where t.GroupId = table.GroupId
             )
order by OrderField

Şimdiden teşekkürler!


2
daha gelişmiş soru burada stackoverflow.com/questions/9841093/…
TMS

Yanıtlar:


174

Yani OrderFieldgrup başına en yüksek olan sırayı mı almak istiyorsunuz ? Bunu şu şekilde yapardım:

SELECT t1.*
FROM `Table` AS t1
LEFT OUTER JOIN `Table` AS t2
  ON t1.GroupId = t2.GroupId AND t1.OrderField < t2.OrderField
WHERE t2.GroupId IS NULL
ORDER BY t1.OrderField; // not needed! (note by Tomas)

( Tomas DÜZENLEME: Aynı grup içinde aynı OrderField ile daha fazla kayıt varsa ve bunlardan tam olarak birine ihtiyacınız varsa, koşulu genişletmek isteyebilirsiniz:

SELECT t1.*
FROM `Table` AS t1
LEFT OUTER JOIN `Table` AS t2
  ON t1.GroupId = t2.GroupId 
        AND (t1.OrderField < t2.OrderField 
         OR (t1.OrderField = t2.OrderField AND t1.Id < t2.Id))
WHERE t2.GroupId IS NULL

düzenlemenin sonu.)

Başka bir deyişle, aynı ve daha büyük t1olan başka hiçbir satırı olmayan satırı t2döndür . NULL olduğunda , sol dış birleşim böyle bir eşleşme bulamadı ve bu nedenle grup içinde en büyük değere sahip olduğu anlamına gelir .GroupIdOrderFieldt2.*t1OrderField

Derece yok, alt sorgu yok. Bu, hızlı çalışmalı ve üzerinde bir bileşik indeksiniz varsa "İndeksi kullanarak" t2'ye erişimi optimize etmelidir (GroupId, OrderField).


Performansla ilgili olarak, her gruptaki son kaydı alma konusundaki cevabıma bakın . Stack Overflow veri dökümünü kullanarak bir alt sorgu yöntemi ve birleştirme yöntemini denedim. Aradaki fark dikkat çekicidir: Birleştirme yöntemi benim testimde 278 kat daha hızlı çalıştı.

En iyi sonuçları almak için doğru dizine sahip olmanız önemlidir!

@Rank değişkenini kullanan yönteminizle ilgili olarak, yazdığınız şekilde çalışmayacaktır, çünkü sorgu ilk tabloyu işledikten sonra @Rank değerleri sıfırlanmayacaktır. Sana bir örnek göstereceğim.

Grup başına en büyük olduğunu bildiğimiz satır dışında boş olan fazladan bir alanla bazı sahte veriler ekledim:

select * from `Table`;

+---------+------------+------+
| GroupId | OrderField | foo  |
+---------+------------+------+
|      10 |         10 | NULL |
|      10 |         20 | NULL |
|      10 |         30 | foo  |
|      20 |         40 | NULL |
|      20 |         50 | NULL |
|      20 |         60 | foo  |
+---------+------------+------+

Sıralamanın birinci grup için üçe, ikinci grup için altıya yükseldiğini ve iç sorgunun bunları doğru şekilde döndürdüğünü gösterebiliriz:

select GroupId, max(Rank) AS MaxRank
from (
  select GroupId, @Rank := @Rank + 1 AS Rank
  from `Table`
  order by OrderField) as t
group by GroupId

+---------+---------+
| GroupId | MaxRank |
+---------+---------+
|      10 |       3 |
|      20 |       6 |
+---------+---------+

Şimdi tüm satırların Kartezyen çarpımını zorlamak için birleştirme koşulu olmadan sorguyu çalıştırın ve ayrıca tüm sütunları getirelim:

select s.*, t.*
from (select GroupId, max(Rank) AS MaxRank
      from (select GroupId, @Rank := @Rank + 1 AS Rank 
            from `Table`
            order by OrderField
            ) as t
      group by GroupId) as t 
  join (
      select *, @Rank := @Rank + 1 AS Rank
      from `Table`
      order by OrderField
      ) as s 
  -- on t.GroupId = s.GroupId and t.MaxRank = s.Rank
order by OrderField;

+---------+---------+---------+------------+------+------+
| GroupId | MaxRank | GroupId | OrderField | foo  | Rank |
+---------+---------+---------+------------+------+------+
|      10 |       3 |      10 |         10 | NULL |    7 |
|      20 |       6 |      10 |         10 | NULL |    7 |
|      10 |       3 |      10 |         20 | NULL |    8 |
|      20 |       6 |      10 |         20 | NULL |    8 |
|      20 |       6 |      10 |         30 | foo  |    9 |
|      10 |       3 |      10 |         30 | foo  |    9 |
|      10 |       3 |      20 |         40 | NULL |   10 |
|      20 |       6 |      20 |         40 | NULL |   10 |
|      10 |       3 |      20 |         50 | NULL |   11 |
|      20 |       6 |      20 |         50 | NULL |   11 |
|      20 |       6 |      20 |         60 | foo  |   12 |
|      10 |       3 |      20 |         60 | foo  |   12 |
+---------+---------+---------+------------+------+------+

Yukarıdan, grup başına maksimum sıranın doğru olduğunu görebiliriz, ancak daha sonra, ikinci türetilmiş tabloyu 7'ye ve daha yükseğe işlerken @Rank artmaya devam eder. Dolayısıyla, ikinci türetilmiş tablodaki sıralar, ilk türetilmiş tablodaki sıralarla hiçbir zaman çakışmayacaktır.

İki tabloyu işlerken @Rank'i sıfırlamaya zorlamak için başka bir türetilmiş tablo eklemeniz gerekir (ve optimize edicinin tabloları değerlendirdiği sırayı değiştirmemesini veya bunu önlemek için STRAIGHT_JOIN kullanmasını umarsınız):

select s.*
from (select GroupId, max(Rank) AS MaxRank
      from (select GroupId, @Rank := @Rank + 1 AS Rank 
            from `Table`
            order by OrderField
            ) as t
      group by GroupId) as t 
  join (select @Rank := 0) r -- RESET @Rank TO ZERO HERE
  join (
      select *, @Rank := @Rank + 1 AS Rank
      from `Table`
      order by OrderField
      ) as s 
  on t.GroupId = s.GroupId and t.MaxRank = s.Rank
order by OrderField;

+---------+------------+------+------+
| GroupId | OrderField | foo  | Rank |
+---------+------------+------+------+
|      10 |         30 | foo  |    3 |
|      20 |         60 | foo  |    6 |
+---------+------------+------+------+

Ancak bu sorgunun optimizasyonu korkunç. Herhangi bir dizin kullanamaz, iki geçici tablo oluşturur, bunları zor bir şekilde sıralar ve hatta bir birleştirme tamponu kullanır çünkü geçici tabloları birleştirirken bir dizin de kullanamaz. Bu, aşağıdakilerden örnek çıktıdır EXPLAIN:

+----+-------------+------------+--------+---------------+------+---------+------+------+---------------------------------+
| id | select_type | table      | type   | possible_keys | key  | key_len | ref  | rows | Extra                           |
+----+-------------+------------+--------+---------------+------+---------+------+------+---------------------------------+
|  1 | PRIMARY     | <derived4> | system | NULL          | NULL | NULL    | NULL |    1 | Using temporary; Using filesort |
|  1 | PRIMARY     | <derived2> | ALL    | NULL          | NULL | NULL    | NULL |    2 |                                 |
|  1 | PRIMARY     | <derived5> | ALL    | NULL          | NULL | NULL    | NULL |    6 | Using where; Using join buffer  |
|  5 | DERIVED     | Table      | ALL    | NULL          | NULL | NULL    | NULL |    6 | Using filesort                  |
|  4 | DERIVED     | NULL       | NULL   | NULL          | NULL | NULL    | NULL | NULL | No tables used                  |
|  2 | DERIVED     | <derived3> | ALL    | NULL          | NULL | NULL    | NULL |    6 | Using temporary; Using filesort |
|  3 | DERIVED     | Table      | ALL    | NULL          | NULL | NULL    | NULL |    6 | Using filesort                  |
+----+-------------+------------+--------+---------------+------+---------+------+------+---------------------------------+

Sol dış birleştirmeyi kullanan çözümüm çok daha iyi optimize ediyor. Hiçbir geçici tablo ve hatta raporlar "Using index"kullanmaz, bu da verilere dokunmadan yalnızca indeksi kullanarak birleştirmeyi çözebileceği anlamına gelir.

+----+-------------+-------+------+---------------+---------+---------+-----------------+------+--------------------------+
| id | select_type | table | type | possible_keys | key     | key_len | ref             | rows | Extra                    |
+----+-------------+-------+------+---------------+---------+---------+-----------------+------+--------------------------+
|  1 | SIMPLE      | t1    | ALL  | NULL          | NULL    | NULL    | NULL            |    6 | Using filesort           |
|  1 | SIMPLE      | t2    | ref  | GroupId       | GroupId | 5       | test.t1.GroupId |    1 | Using where; Using index |
+----+-------------+-------+------+---------------+---------+---------+-----------------+------+--------------------------+

Muhtemelen bloglarında "birleştirme SQL'i yavaşlatan" iddialarda bulunan insanları okuyacaksınız, ama bu çok saçma. Yetersiz optimizasyon, SQL'i yavaşlatır.


Bu oldukça yararlı olabilir (OP için de), ancak ne yazık ki sorulan iki soruyu da yanıtlamıyor.
Andriy M

Teşekkürler Bill, bu rütbelerden nasıl kaçınılacağına dair iyi bir fikir, ama ... birleşme yavaş olmaz mıydı? Birleştirme (where cümlesi sınırlaması olmadan) sorgularımdakinden çok daha büyük boyutta olacaktır. Her neyse, fikir için teşekkürler! Ama asıl soruda da ilginç olabilirim, yani rütbeler bu şekilde çalışırsa.
TMS

Mükemmel cevabın için teşekkürler Bill. Ancak, her alt sorgu için @Rank1ve @Rank2bir tane kullanırsam ne olur ? Bu sorunu çözer mi? Bu sizin çözümünüzden daha hızlı olur mu?
TMS

Kullanılması @Rank1ve @Rank2hiçbir fark yapar.
Bill Karwin

2
Bu harika çözüm için teşekkürler. Bu sorunla uzun zamandır mücadele ediyordum. Diğer alanlar için filtre eklemek isteyen kişiler için, örneğin "foo" ... AND t1.foo = t2.foo, daha sonra doğru sonuçları elde etmek için onları birleştirme koşuluna eklemeniz gerekirWHERE ... AND foo='bar'
olmak
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.