MySQL では、すべての行が特定の条件を満たすデータをどのように選択できますか? たとえば、従業員がいつ出勤したかを示すテーブルがあり、3 つのフィールドがあるとします。
CREATE TABLE ArrivalTimes
(UserID INT
,Day DATE
,ArrivalTime TIME
);
一度も遅刻したことがない (午前 9 時以前に到着した) 従業員のすべてのユーザー ID を選択したいのですが、これを行うにはどうすればよいですか?
@jjclarkson と @davethegr8 からの回答は近いですが、集計関数を WHERE 句に入れることはできません。WHERE 句は行ごとに評価されます。
MAX()
グループごとに式を評価する必要があるため、HAVING
句を使用する必要があります。
これを試して:
SELECT UserID
FROM ArrivalTimes
GROUP BY UserID
HAVING MAX(ArrivalTime) <= '09:00:00';
@MBCook コメントはHAVING
遅くなる可能性があります。おっしゃる通り、これは望ましい結果を得る絶対的な最速の方法ではないかもしれません。しかし、HAVING
解決策は最も明確です。明快さと保守性よりもパフォーマンスの優先度が低い状況があります。
解決策として、EXPLAIN の出力 (MySQL 5.1.30 で) を調べましたHAVING
。インデックスは使用されておらず、追加のメモには " Using temporary; Using filesort
," と書かれていましたが、これは通常、パフォーマンスが低下することを意味します。
次のクエリを検討してください。
SELECT DISTINCT a1.UserID
FROM ArrivalTimes a1
LEFT OUTER JOIN ArrivalTimes a2
ON (a1.UserID = a2.UserID AND a2.ArrivalTime > '09:00:00')
WHERE a2.UserID IS NULL;
これにより、インデックスを使用して次のような最適化プランが生成されUserID
ます。
Using index; Using temporary
"Using where; Distinct
"最後に、次のクエリは、インデックスを最も効果的に使用し、一時テーブルやファイル ソートを使用しないように見える最適化プランを生成します。
SELECT DISTINCT a1.UserID
FROM ArrivalTimes a1
WHERE NOT EXISTS (SELECT * FROM ArrivalTimes a2
WHERE a1.UserID = a2.UserID
AND a2.ArrivalTime > '09:00:00');
Using where; Using index
"Using where
"これにより、最高のパフォーマンスが得られる可能性が最も高くなります。確かに、テスト テーブルには 4 行しかないので、これは代表的なテストではありません。
これは良い考えでしたが、うまくいきません。
SELECT UserID FROM ArrivalTimes WHERE MAX(ArrivalTime) <= '09:00:00' GROUP BY UserID
このクエリを使用すると、「グループ関数の使用が無効です」というエラーが表示されます。
COUNT、MAX、MIN、AVG、SUM などの集計関数は、定義上、セット (またはレコードのグループ) に対して関数を実行するため、MAX(ArrivalTime) は次の形式である必要があります。
GROUP BY UserID HAVING MAX(ArrivalTime) <= '09:00:00'
上記の@Bill Karwinからの回答を参照してください。
SELECT userID, MAX(ArrivalTime) as latest
FROM ArrivalTimes
WHERE latest <= '9:00:00'
GROUP BY userID
このクエリの結果をさらに3つの方法で取得できます1.Group-By関数を使用する2.サブクエリを使用する3.結合を使用する......など
SELECT userID, MAX(ArrivalTime) as latest FROM ArrivalTimes WHERE latest <= '9:00:00'
select * from user a where '09:00:00'
= all( b.UserID = a.ID の ArrivalTime b から ArrivalTime を選択);
自己内部結合を使用して取得することもできます
ビル・カーウィンは次のように提案しています。
これを試して:
SELECT UserID
FROM ArrivalTimes
GROUP BY UserID
HAVING MAX(ArrivalTime) <= '09:00:00';
HAVING ソリューションの EXPLAIN 出力 (MySQL 5.1.30 で) を確認しました。インデックスは使用されておらず、追加のメモには「一時的な使用; ファイルソートの使用」とあり、通常はパフォーマンスが低下することを意味します。
ArrivalTimes.UserId が外部キーであるユーザー テーブルがあることを考えると、以下はさらに明確であると思います。これにより、遅刻しないすべてのユーザーが選択されます。
select * from user a
where '09:00:00'
>= all( select ArrivalTime from ArrivalTime b where b.UserID = a.ID);
これにより、遅刻したことがあるユーザーが選択されます。
select * from user a
where '09:00:00'
< any( select ArrivalTime from ArrivalTime b where b.UserID = a.ID);
これは、私たちの英語/自然言語仕様にさらに厳密に準拠しているため、より明確です。
そして、それは a group by
;の非効率性を回避します。MySql 5.0.51 では、Bill のように一時またはファイルソートは必要ありません。
(定数時間値をゼロで埋める必要があることに注意してください。したがって、'09:00:00'
;'9:00:00'
は失敗します。)