2012-01-05 12 views
67

के साथ रिकॉर्ड प्राप्त करें यह कैसे करें?उच्चतम/छोटे <whatever> प्रति समूह

इस सवाल के पूर्व शीर्षक था "रैंक का उपयोग कर (@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 subqueries में उपयोग किए जाने पर यह संदिग्ध लगता है, लेकिन केवल एक बार शुरू किया। क्या यह इस तरह से काम करेगा?

और दूसरा, क्या यह एक सबक्वायरी के साथ काम करेगा जिसका मूल्यांकन कई बार किया जाता है? जहां (या होने) खंड में उपरोक्त की तरह (

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 

अग्रिम धन्यवाद!

+1

यहां अधिक उन्नत प्रश्न http://stackoverflow.com/questions/9841093/how-to-writegreatest-n-per-group-type-query-but-with-additional-conditions/9845109#9845109 – TMS

उत्तर

129

तो क्या आप प्रति समूह उच्चतम 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) 

(संपादित करें: मैं इसे इस तरह से करना चाहते हैं, तो एक ही समूह के भीतर एक ही 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 के साथ मौजूद है और अधिक से अधिकलौट आते हैं।। जब t2.* पूर्ण है, तो इसका मतलब है कि बाएं बाहरी जुड़ने में ऐसा कोई मिलान नहीं मिला है, और इसलिए t1 समूह में OrderField का सबसे बड़ा मूल्य है।

कोई रैंक नहीं, कोई सबक्वायरी नहीं। यदि आपके पास (GroupId, OrderField) पर कंपाउंड इंडेक्स है तो इसे "इंडेक्स का उपयोग करना" के साथ टी 2 तक पहुंच को तेज और अनुकूलित करना चाहिए।


प्रदर्शन के संबंध में, मेरा उत्तर Retrieving the last record in each group पर देखें। मैंने स्टैक ओवरफ़्लो डेटा डंप का उपयोग करके एक सबक्वायरी विधि और जॉइन विधि की कोशिश की। अंतर उल्लेखनीय है: मेरे परीक्षण में शामिल विधि 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 | 
+---------+---------+---------+------------+------+------+ 

हम ऊपर से देख सकते हैं कि प्रति समूह अधिकतम रैंक सही है, लेकिन फिर @ रैंक बढ़ता जा रहा है क्योंकि यह दूसरी व्युत्पन्न तालिका को 7 और उससे अधिक तक संसाधित करता है। तो दूसरी व्युत्पन्न तालिका से रैंक पहले व्युत्पन्न तालिका से रैंक के साथ कभी भी ओवरलैप नहीं होंगे।

आपको दो तालिकाओं को प्रोसेस करने के बीच शून्य पर रीसेट करने के लिए @ रैंक को मजबूर करने के लिए एक और व्युत्पन्न तालिका जोड़नी होगी (और उम्मीद है कि ऑप्टिमाइज़र उस क्रम को नहीं बदलता है जिसमें यह तालिका का मूल्यांकन करता है, या फिर 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 | 
+----+-------------+-------+------+---------------+---------+---------+-----------------+------+--------------------------+ 

आप शायद अपने ब्लॉग पर दावा करने वाले लोगों को पढ़ लेंगे कि "एसक्यूएल धीमा कर देता है," लेकिन यह बकवास है। खराब अनुकूलन एसक्यूएल धीमा बनाता है।

+0

यह साबित हो सकता है काफी उपयोगी (ओपी के लिए भी), लेकिन, दुख की बात है, दो प्रश्नों में से कोई भी जवाब नहीं दिया। –

+0

धन्यवाद बिल, यह एक अच्छा विचार है कि रैंक से कैसे बचें, लेकिन ... क्या शामिल नहीं होगा? शामिल हों (जहां खंड सीमा के बिना) मेरे प्रश्नों की तुलना में बहुत बड़ा आकार होगा। वैसे भी, विचार के लिए धन्यवाद! लेकिन मैं मूल प्रश्न में भी दिलचस्प होगा, यानी यदि रैंक इस तरह से काम करेंगे। – TMS

+0

उत्कृष्ट उत्तर के लिए धन्यवाद, बिल। हालांकि, अगर मैंने प्रत्येक उप-वर्ग के लिए '@ रैंक 1' और '@ रैंक 2' का उपयोग किया, तो क्या होगा? क्या इससे समस्या ठीक हो जाएगी? क्या यह आपके समाधान से तेज़ होगा? – TMS

संबंधित मुद्दे