รับบันทึกที่มี <สิ่งใด ๆ > สูงสุด / น้อยที่สุดต่อกลุ่ม


88

ต้องทำอย่างไร?

ชื่อเดิมของคำถามนี้คือ "การใช้อันดับ (@Rank: = @Rank + 1) ในคิวรีที่ซับซ้อนกับเคียวรีย่อยจะใช้ได้หรือไม่ " เพราะฉันกำลังมองหาวิธีแก้ปัญหาโดยใช้อันดับ แต่ตอนนี้ฉันเห็นว่าวิธีแก้ปัญหาที่บิลโพสต์คือ ดีขึ้นมาก

คำถามเดิม:

ฉันกำลังพยายามเขียนข้อความค้นหาที่จะบันทึกครั้งสุดท้ายจากแต่ละกลุ่มตามลำดับที่กำหนดไว้:

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

@Rank := @Rank + 1โดยปกตินิพจน์จะใช้สำหรับอันดับ แต่สำหรับฉันแล้วมันดูน่าสงสัยเมื่อใช้ใน 2 เคียวรีย่อย แต่เริ่มต้นเพียงครั้งเดียว จะได้ผลไหม

และอย่างที่สองจะใช้ได้กับแบบสอบถามย่อยหนึ่งรายการที่มีการประเมินหลายครั้งหรือไม่ เช่นเดียวกับการสืบค้นย่อยในโดยที่ (หรือมี) อนุประโยค (วิธีอื่นในการเขียนด้านบน):

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

ขอบคุณล่วงหน้า!


2
คำถามขั้นสูงที่นี่stackoverflow.com/questions/9841093/…
TMS

คำตอบ:


174

คุณต้องการได้แถวที่มีสูงสุดOrderFieldต่อกลุ่มหรือไม่? ฉันจะทำแบบนี้:

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:หากมีเรกคอร์ดที่มี OrderField เดียวกันภายในกลุ่มเดียวกันมากขึ้นและคุณต้องการหนึ่งในนั้นคุณอาจต้องการขยายเงื่อนไข:

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

สิ้นสุดการแก้ไข)

ในคำอื่น ๆ กลับแถวt1ที่ไม่มีแถวอื่น ๆt2ที่มีอยู่ด้วยกันและมากขึ้นGroupId OrderFieldเมื่อt2.*เป็นโมฆะหมายความว่าการรวมภายนอกด้านซ้ายไม่พบการจับคู่ดังกล่าวดังนั้นจึงt1มีค่ามากที่สุดOrderFieldในกลุ่ม

ไม่มีอันดับไม่มีแบบสอบถามย่อย นี้ควรทำงานได้อย่างรวดเร็วและเพิ่มประสิทธิภาพการเข้าถึงการ T2 กับ "การใช้ดัชนี" (GroupId, OrderField)ถ้าคุณมีดัชนีสารประกอบบน


เกี่ยวกับประสิทธิภาพให้ดูคำตอบของฉันที่จะดึงข้อมูลระเบียนสุดท้ายในแต่ละกลุ่ม ฉันลองใช้วิธีการสืบค้นย่อยและวิธีการเข้าร่วมโดยใช้การถ่ายโอนข้อมูล Stack Overflow ความแตกต่างที่น่าทึ่งคือวิธีการเข้าร่วมทำงานได้เร็วขึ้น 278 เท่าในการทดสอบของฉัน

สิ่งสำคัญคือคุณต้องมีดัชนีที่ถูกต้องเพื่อให้ได้ผลลัพธ์ที่ดีที่สุด!

เกี่ยวกับวิธีการของคุณโดยใช้ตัวแปร @Rank มันจะไม่ทำงานตามที่คุณเขียนไว้เนื่องจากค่าของ @Rank จะไม่รีเซ็ตเป็นศูนย์หลังจากที่แบบสอบถามประมวลผลตารางแรกแล้ว ฉันจะแสดงตัวอย่าง

ฉันแทรกข้อมูลจำลองโดยมีฟิลด์พิเศษที่เป็นโมฆะยกเว้นในแถวที่เรารู้ว่ามีค่ามากที่สุดต่อกลุ่ม:

select * from `Table`;

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

เราสามารถแสดงให้เห็นว่าอันดับเพิ่มขึ้นเป็นสามสำหรับกลุ่มแรกและหกสำหรับกลุ่มที่สองและเคียวรีภายในจะส่งกลับสิ่งเหล่านี้อย่างถูกต้อง:

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

ตอนนี้เรียกใช้แบบสอบถามโดยไม่มีเงื่อนไขการรวมเพื่อบังคับให้ผลิตภัณฑ์คาร์ทีเซียนของทุกแถวและเรายังดึงคอลัมน์ทั้งหมด:

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

เราสามารถเห็นได้จากด้านบนว่าอันดับสูงสุดต่อกลุ่มนั้นถูกต้อง แต่ @Rank ยังคงเพิ่มขึ้นเมื่อประมวลผลตารางที่ได้รับที่สองเป็น 7 และสูงกว่า ดังนั้นอันดับจากตารางที่ได้รับที่สองจะไม่ทับซ้อนกับอันดับจากตารางแรกที่ได้มาเลย

คุณต้องเพิ่มตารางที่ได้รับมาอีกเพื่อบังคับให้ @Rank รีเซ็ตเป็นศูนย์ระหว่างการประมวลผลตารางทั้งสอง (และหวังว่าเครื่องมือเพิ่มประสิทธิภาพจะไม่เปลี่ยนลำดับในการประเมินตารางมิฉะนั้นจะใช้ STRAIGHT_JOIN เพื่อป้องกันสิ่งนั้น):

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

แต่การเพิ่มประสิทธิภาพของแบบสอบถามนี้แย่มาก ไม่สามารถใช้ดัชนีใด ๆ สร้างตารางชั่วคราวสองตารางเรียงลำดับอย่างยากลำบากและยังใช้บัฟเฟอร์การรวมเนื่องจากไม่สามารถใช้ดัชนีเมื่อเข้าร่วมตารางชั่วคราวได้เช่นกัน นี่คือตัวอย่างผลลัพธ์จาก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                  |
+----+-------------+------------+--------+---------------+------+---------+------+------+---------------------------------+

ในขณะที่วิธีการแก้ปัญหาของฉันโดยใช้การรวมภายนอกด้านซ้ายจะปรับให้เหมาะสมดีกว่า ไม่ใช้ตารางอุณหภูมิและแม้แต่รายงาน"Using index"ซึ่งหมายความว่าสามารถแก้ไขการรวมโดยใช้เพียงดัชนีโดยไม่ต้องสัมผัสข้อมูล

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

คุณอาจจะอ่านคนที่อ้างสิทธิ์ในบล็อกของพวกเขาว่า "join make SQL slow" แต่นั่นเป็นเรื่องไร้สาระ การเพิ่มประสิทธิภาพที่ไม่ดีทำให้ SQL ช้า


สิ่งนี้อาจพิสูจน์ได้ว่ามีประโยชน์มาก (สำหรับ OP ด้วย) แต่น่าเศร้าที่ไม่ได้ตอบคำถามทั้งสองข้อ
Andriy M

ขอบคุณ Bill นั่นเป็นความคิดที่ดีในการหลีกเลี่ยงอันดับ แต่ ... การเข้าร่วมจะไม่ช้าไปหรือ? การเข้าร่วม (โดยไม่มีข้อ จำกัด ของส่วนคำสั่งที่ไหน) จะมีขนาดใหญ่กว่าในแบบสอบถามของฉันมาก อย่างไรก็ตามขอบคุณสำหรับความคิด! แต่ฉันก็น่าสนใจเช่นกันในคำถามเดิมคือถ้าอันดับจะทำงานในลักษณะนี้
TMS

ขอบคุณสำหรับคำตอบที่ยอดเยี่ยม Bill อย่างไรก็ตามจะเกิดอะไรขึ้นถ้าฉันใช้@Rank1และ@Rank2หนึ่งสำหรับแต่ละคำถามย่อย จะแก้ไขปัญหาได้หรือไม่? จะเร็วกว่าโซลูชันของคุณหรือไม่?
TMS

ใช้@Rank1และ@Rank2จะไม่สร้างความแตกต่าง
Bill Karwin

2
ขอบคุณสำหรับวิธีแก้ปัญหาที่ยอดเยี่ยม ฉันต่อสู้กับปัญหานั้นมานาน สำหรับผู้ที่ต้องการเพิ่มตัวกรองสำหรับช่องอื่น ๆ เช่น "foo" คุณต้องเพิ่มลงในเงื่อนไขการเข้าร่วม... AND t1.foo = t2.fooเพื่อให้ได้ผลลัพธ์ที่ถูกต้องในภายหลังWHERE ... AND foo='bar'
เจ้าของ
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.