どうやってするか?
この質問の以前のタイトルは「サブクエリを使用した複雑なクエリでランク(@Rank:= @Rank + 1を使用-動作しますか?」でした。ランクを使用してソリューションを探していたためです。 Billが投稿したソリューションの方がはるかに優れていること。
元の質問:
私はいくつかの定義された順序を与えられた各グループから最後のレコードを取得するクエリを作成しようとしています:
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つのサブクエリで使用されると疑わしく見えますが、初期化されるのは1回だけです。このように機能しますか?
次に、複数回評価される1つのサブクエリで動作しますか? where(またはhaving)句のサブクエリと同様(上記の別の記述方法):
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
前もって感謝します!
グループごとに最高の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を持つレコードがさらにあり、そのうちの1つだけが必要な場合は、調子:
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
編集の終了。)
つまり、同じGroupId
以上のOrderField
を持つ行t1
が存在しない行t2
を返します。 t2.*
がNULLの場合、左外部結合でそのような一致が見つからなかったため、t1
はグループ内でOrderField
の最大値を持ちます。
ランクなし、サブクエリなし。 (GroupId, OrderField)
に複合インデックスがある場合、これは高速で実行され、「インデックスの使用」でt2へのアクセスを最適化する必要があります。
パフォーマンスについては、 各グループの最後のレコードの取得 への私の答えを参照してください。 Stack Overflowデータダンプを使用して、サブクエリメソッドとjoinメソッドを試しました。違いは顕著です:私のテストでは、joinメソッドの実行速度は278倍速くなりました。
最良の結果を得るには、適切なインデックスを持っていることが重要です!
@Rank変数を使用するメソッドについては、クエリが最初のテーブルを処理した後に@Rankの値がゼロにリセットされないため、記述したとおりに機能しません。例を示します。
グループごとに最大であることがわかっている行を除き、nullの追加フィールドを持つダミーデータを挿入しました。
select * from `Table`;
+---------+------------+------+
| GroupId | OrderField | foo |
+---------+------------+------+
| 10 | 10 | NULL |
| 10 | 20 | NULL |
| 10 | 30 | foo |
| 20 | 40 | NULL |
| 20 | 50 | NULL |
| 20 | 60 | foo |
+---------+------------+------+
ランクが最初のグループで3、2番目のグループで6に増加し、内部クエリがこれらを正しく返すことを示すことができます。
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 |
+---------+---------+---------+------------+------+------+
上記から、グループごとの最大ランクが正しいことがわかりますが、2番目の派生テーブルを処理するときに@Rankは7以上に増加し続けます。したがって、2番目の派生テーブルのランクは、最初の派生テーブルのランクとまったく重複しません。
別の派生テーブルを追加して、2つのテーブルの処理の間に@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 |
+---------+------------+------+------+
しかし、このクエリの最適化はひどいです。インデックスを使用できず、2つの一時テーブルを作成し、それらを困難な方法で並べ替え、さらに一時テーブルを結合するときにインデックスを使用できないため、結合バッファも使用します。これは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を結合するとSQLが遅くなる」と主張する人を読むことになるでしょうが、それはナンセンスです。最適化が不十分だと、SQLが遅くなります。