प्रति समूह उच्चतम / सबसे छोटे <जो भी> के साथ रिकॉर्ड प्राप्त करें


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)

( टॉमस द्वारा ईडीआईटी : यदि एक ही समूह के भीतर एक ही ऑर्डरफिल्ड के साथ और अधिक रिकॉर्ड हैं और आपको उनमें से एक की आवश्यकता है, तो आप शर्त को आगे बढ़ाना चाह सकते हैं:

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.*NULL होता है, तो इसका मतलब है कि बायाँ बाहरी जोड़ ऐसा कोई मिलान नहीं पाया गया है, और इसलिए समूह में इसका t1सबसे बड़ा मूल्य है OrderField

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


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

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


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

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

उत्कृष्ट उत्तर के लिए धन्यवाद, बिल। हालांकि, क्या होगा अगर मैं इस्तेमाल किया @Rank1और @Rank2, प्रत्येक उपश्रेणी के लिए एक? क्या इससे समस्या ठीक होगी? क्या यह आपके समाधान से तेज होगा?
टीएमएस

उपयोग करने @Rank1और @Rank2कोई फर्क नहीं पड़ेगा।
बिल करविन

2
उस महान समाधान के लिए धन्यवाद। मैं उस समस्या से लंबे समय से जूझ रहा था। लोग हैं, जो अन्य क्षेत्रों जैसे "foo" फ़िल्टर जोड़ना चाहते हैं के लिए आप उन्हें जोड़ने की जरूरत हालत में शामिल होने के ... AND t1.foo = t2.fooबाद में करने के लिए के लिए सही परिणाम प्राप्तWHERE ... AND foo='bar'
ownking
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.