প্রতি গ্রুপে সর্বাধিক / ক্ষুদ্রতম <যাই হোক> সঙ্গে রেকর্ড পান


88

কিভাবে যে কি?

এই প্রশ্নের পূর্বের শিরোনামটি ছিল " সাবকিউরিয়াসহ জটিল প্রশ্নে র‌্যাঙ্ক (@ র্যাঙ্ক: = @ র‌্যাঙ্ক + 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

আগাম ধন্যবাদ!


4
এখানে আরও উন্নত প্রশ্ন stackoverflow.com/questions/9841093/…
টিএমএস

উত্তর:


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

কোনও পদ নেই, কোন উপশক্তি নেই। এটি দ্রুত চালানো উচিত এবং আপনার কোনও যৌগ সূচক চালু থাকলে "ইনডেক্স" ব্যবহার করে টি 2-এ অ্যাক্সেসের অনুকূলিতকরণ করা উচিত (GroupId, OrderField)


পারফরম্যান্স সম্পর্কে, প্রতিটি গ্রুপের সর্বশেষ রেকর্ড পুনরুদ্ধার করার জন্য আমার উত্তর দেখুন । স্ট্যাক ওভারফ্লো ডেটা ডাম্প ব্যবহার করে আমি সাব-কোয়েরি পদ্ধতি এবং যোগদানের পদ্ধতিটি চেষ্টা করেছি। পার্থক্যটি লক্ষণীয়: যোগদানের পদ্ধতিটি আমার পরীক্ষায় 278 গুণ বেশি দ্রুত গতিতে চলেছিল।

সেরা ফলাফল পাওয়ার জন্য আপনার সঠিক সূচীটি থাকা গুরুত্বপূর্ণ!

@ র্যাঙ্ক ভেরিয়েবলটি ব্যবহার করে আপনার পদ্ধতি সম্পর্কে, আপনি যেমনটি লিখেছেন তেমন কার্যকর হবে না, কারণ প্রশ্নের প্রথম টেবিলটি প্রক্রিয়া করার পরে @ র্যাঙ্কের মানগুলি শূন্যে পুনরায় সেট হবে না। আমি আপনাকে একটি উদাহরণ দেখাব।

আমি কিছু ডামি ডেটা sertedোকালাম, একটি অতিরিক্ত ক্ষেত্রের সাথে শূন্য যা আমরা জানি যে সারিটি প্রতি গ্রুপে সর্বাধিক:

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 থেকে উচ্চতর বৃদ্ধি পেতে থাকবে। সুতরাং দ্বিতীয় উত্পন্ন টেবিল থেকে র‌্যাঙ্কগুলি কখনই প্রথম উত্পন্ন টেবিল থেকে র‌্যাঙ্কের সাথে কখনই ওভারল্যাপ করবে না।

দুটি টেবিল প্রক্রিয়াকরণের মধ্যে @ র্যাঙ্ককে শূন্যে পুনরায় সেট করতে জোর করতে আপনাকে আর একটি উত্সযুক্ত টেবিল যুক্ত করতে হবে (এবং আশা করি অপ্টিমাইজারটি যে সারণীর মূল্যায়ন করে তাতে ক্রমটি পরিবর্তন হয় না, বা তা রোধ করতে স্ট্রাইটাইএটT_ জয়ন ব্যবহার করুন):

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

আপনি সম্ভবত তাদের ব্লগে দাবী করা লোকেরা পড়বেন যে "এসকিউএলকে ধীরে ধীরে যোগ দেয়", তবে এটি আজেবাজে কথা। খারাপ অপ্টিমাইজেশন এসকিউএলকে ধীর করে তোলে।


এটি বেশ কার্যকর হিসাবে প্রমাণিত হতে পারে (ওপি'র জন্যও), তবে দুঃখের বিষয়, জিজ্ঞাসিত দুটি প্রশ্নের কোনওটিরই উত্তর দেয় না।
অ্যান্ড্রি এম

ধন্যবাদ বিল, কীভাবে পদক্ষেপগুলি এড়ানো যায় এটি একটি ভাল ধারণা, তবে ... যোগদানটি ধীর হবে না? আমার প্রশ্নের তুলনায় জোড় (যেখানে ক্লজ সীমাবদ্ধতা ছাড়াই) অনেক বড় আকারের হবে। যাইহোক, ধারণা জন্য ধন্যবাদ! তবে আমি মূল প্রশ্নেও আকর্ষণীয় হব, অর্থাৎ যদি র‌্যাঙ্কগুলি এভাবে কাজ করে।
টিএমএস

দুর্দান্ত উত্তরের জন্য ধন্যবাদ, বিল। তবে, আমি যদি প্রতিটি উপকোয়ারের জন্য একটি ব্যবহার করি @Rank1এবং @Rank2? এটা কি সমস্যার সমাধান করবে? এটি কি আপনার সমাধানের চেয়ে দ্রুত হবে?
টিএমএস

ব্যবহার করে @Rank1এবং @Rank2কোন পার্থক্য করা হবে।
বিল কারভিন

4
মহান সমাধানের জন্য ধন্যবাদ। আমি দীর্ঘসময় এই সমস্যাটি নিয়ে লড়াই করে যাচ্ছিলাম। অন্যান্য ক্ষেত্রগুলির জন্য যারা ফিল্টার যুক্ত করতে চান তাদের জন্য যেমন "foo" আপনাকে ... AND t1.foo = t2.fooপরে যোগ শর্তে যুক্ত করতে হবে পরে সঠিক ফলাফল পেতেWHERE ... AND foo='bar'
মালিকানাধীন
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.