MySQLのGROUP BY
に問題があります。
私のデータベース設定:
client_visit
- id
- member_id
- status_type_id (type_of_visit table)
- visit_starts_at
- visit_ends_at
member
- id
schedule_event
- id
- member_id
- starts_at
- ends_at
type_of_visit
- id
- type (TYPE_BOOKED, TYPE_PRESENT etc)
この質問の目的:member
は、特定の時間にクラスを教えるか、アクティビティ(schedule_event
)を主導します。 client
がこのクラスまたはアクティビティにサインアップします。
例えば:
クライアントA、B、Cの本の訪問とそれらの訪問はclient_visit
とschedule_event_id
で構成されるmember_id
テーブルに移動するため、どのクラスとどのメンバーが教えているか、アクティビティを行っているかがわかります。
ここで、特定のメンバーがクライアントがサインアップしたイベントの指導/指導に費やした合計時間を知りたい(client_visit
type_of_visit
列は「Booked」または「Present」と同等)。メンバーID 82をテストケースとして使用します。
メンバーID 82には2つの異なるクラスに4つのクライアントがあり、各クラスに2時間15分(8100秒)かかった場合、合計時間は16200秒になるはずです。
これが最初のクエリです。
SELECT cv.member_id AS `member_id`,
sch.id AS `scheduleId`,
cv.visit_starts_at AS `visitStartsAt`,
TIMESTAMPDIFF(SECOND, sch.starts_at, sch.ends_at) AS `totalTime`
FROM `schedule_event` AS `sch`
LEFT JOIN `client_visit` AS `cv` ON cv.schedule_event_id = sch.id
INNER JOIN `type_of_visit` AS `tov` ON tov.id = cv.status_type_id
WHERE (tov.type = 'TYPE_BOOKED' OR tov.type = 'TYPE_PRESENT') and cv.member_id = 82
これは、最初のクラスのクライアントと2番目のクラスのクライアントを示しています。各クラスに1つずつ、2つの行が必要です。だから、私はこれを追加します:
GROUP BY sch.id
ここまでは順調ですね、
このメンバーには2つのスケジュールIDがあることを知っているので、これらを1つにまとめるようにグループを変更しました。
GROUP BY sch.id AND cv.member_id
私は最初にsch.id
(上の画像に既に表示されている結果)とcv.member_id
(2つの行を取得したので、マージ後は1つになるはずです)に基づいてマージされると思います。
結果は(GROUP_CONCATを追加してscheduleIdを変更したので、両方のスケジュールIDがそこにあることがわかります):
ここで、2つのスケジュールIDをまとめたように、2つのスケジュールされたクラスの時間を合計します。
ここでクエリを変更します。
SUM(TIMESTAMPDIFF(SECOND, sch.starts_at, sch.ends_at)) AS `totalTime`
私は32400を手に入れました!何らかの理由で、SUMは一意の2だけではなく、4行すべてを表示しています。
最終結果は
+-----------+------------+
| member_id | total_time |
+-----------+------------+
| 82 | 16200 |
+-----------+------------+
他のすべての列は必要ありません。何が起こっているのかを確認するためにそれらを作成しました
どうしましたか?
ウィレム・レンゼマが言ったように、GROUP BY
の仕組みを誤解しました。彼の言ったことを理解していないようですので、少し違った言い方をしてみましょう。
論理的に十分なGROUP BY
は、結果セットの行をグループ化するために使用されます。通常、行のグループ化に使用する列のリストを提供します。 GROUP BY sch.id, cv.member_id
は、これらの2つの列の一意の値のセットを識別し、それらの値によって結果セットの行をグループ化するようにSQLに指示します。あなたのケースでは、これらの2つの値には2つの一意の値のペアがあります:
cv.member_id
= 82、sch.id
= 17101cv.member_id
= 82、sch.id
= 17153したがって、2つの行グループが得られます。3つは最初の値のペアで、もう1つは2番目のペアです。
GROUP BY
句に列を追加すると、never結果としてグループが少なくなります-新しい列がすべての行で同じである(同じ数のグループがある場合)、または新しい列が異なる値を持ち、1つ以上の時間の元のグループのいくつかの行からいくつかの行を形成します(この場合、より多くのグループがあります)。
また、(Willemによって指摘されたように)構文エラーがあります。 GROUP BY
リストの列はカンマで区切られます。 GROUP BY sch.id AND cv.member_id
では、sch.id AND cv.member_id
、またはsch.id
とcv.member_id
の両方をブール値であるかのように処理した結果によって、グループ化しています。どちらも0ではないため、ブール値に変換すると、両方が1(真)に評価され、(true AND true)
の組み合わせは真になります。したがって、4行の1つのグループで終了します。
一歩下がって、あなたが実際に何をしようとしているのか(それがどのように見えるか)を考えてみましょう。特定のmember_id
について、「予約済み」または「現在」タイプのアクティビティに関与した合計時間を求めます。
合計時間はschedule_event
テーブルから計算されることに注意してください。また、特定のmember_id
を同じschedule_event
に複数回関連付けることができることに注意してください。したがって、合計時間を取得するには、schedule_event
が関連付けられている個別のmember_id
行を特定し、それらの一意の値の時間を合計する必要があります。
その場合、次に進む最も簡単な方法は、サブクエリを使用してschedule_events
のリストを取得し、member_id
が関連付けられているリストを取得して、それらの異なるイベントの合計時間を合計することです。
これを行うクエリは次のとおりです。
SELECT `member_id`
,SUM(`totalTime`) as `totalTime`
FROM (
SELECT DISTINCT
cv.member_id AS `member_id`,
sch.id AS `scheduleId`,
TIMESTAMPDIFF(SECOND, sch.starts_at, sch.ends_at) AS `totalTime`
FROM
`schedule_event` AS `sch`
INNER JOIN `client_visit` AS `cv` ON cv.schedule_event_id = sch.id
INNER JOIN `type_of_visit` AS `tov` ON tov.id = cv.status_type_id
WHERE
(tov.type = 'TYPE_BOOKED' OR tov.type = 'TYPE_PRESENT')
AND cv.member_id = 82
) sq
GROUP BY `member_id`;
サブクエリ(想像的にsq
とラベル付けされている)は、基本的には元のクエリです。 LEFT JOIN
と訪問のタイプの両方を識別するためにINNER JOIN
レコードが必要なため、client_visit
をmember_id
に変更しました。ただし、SUM
のtotalTime
を削除しました。この時点では、各schedule_event
にかかる時間を知りたいだけです。私はDISTINCT
も追加しました-このschedule_event
がこのmember_id
とともに何回現れるかは関係ありません。合計時間は、1回、3回、または207回表示されても同じです。
schedule_event
が接続されているmember_id
データを特定したら、これらすべてのschedule_event
行の合計時間を求めます。したがって、サブクエリの結果を取得し、それらをmember_id
でグループ化し(複数のmember_id
値でこれを引き戻す必要がある場合に備えて)、それぞれの計算時間を合計しますschedule_event
行。
Joanoloが問題のdbfiddleを設定するのに苦労したので、私は彼の作業を取り、最後にこのクエリを追加したので、結果が希望どおりだったことがわかります。更新されたdbfiddleリンクは here です。
これがGROUP BY
が実際にどのように機能するかを明確にするのに役立つことを願っています。
GROUP BYの機能について誤解していると思います。当然のことですが、MySQLのマニュアルではGROUP BYの機能が実際に明示されていないため、最初に学習したときに自分で問題が発生しました。特別な動作であり、実際の定義ではありません)。
私(オンザフライ)の定義:
GROUP BYはSELECT結果を圧縮して、GROUP BY句で指定された列の値の組み合わせごとに1行だけが返されるようにします。その意味では、DISTINCTに似ていますが、SELECTステートメントの代わりにGROUP BYの列で機能します。
非MySQLランドでは、GROUP BY句で指定した列と、必要な任意の 集計関数 のみをSELECTできます。 SUMを含むこれらの集計関数は、行ごとに動作し、現在「非表示」になっている追加行についてのみ結果を報告します。
ご覧のとおり、これがクエリが実際に実行していることです(または実行する予定ですが、ypercubeがコメントで指摘しているように、不正確な例を示したと思います)。指定されたsch.id
について、現在非表示になっているすべての追加行を合計し、それらの合計を報告しています。
各sch.id
の個別の値のみの合計が必要な場合は、必要な情報を取得するために別の方法で行う必要があります。
単純ではない理由の1つは、MySQLが合計に含めたい行がわからないことです。例(8100)ではすべて同じである可能性がありますが、その保証はありません。
MySQLではGROUP BY句で指定されていない列も集計関数でもない列を選択できるため、基本的に「ランダム」に1つを選択して表示します。実際にはランダムではありませんが、非決定的であり、常に同じ結果を与えるように見えても、同じクエリとデータに対していつでも変更できます。
したがって、先に進む前に、各sch.id
のどの行に合計する値が含まれるかをどのように決定するかを決定する必要があります。
値が常に同じであることがわかっている場合、単純な(必ずしも最適化されているわけではありませんが)ソリューションとして、元のGROUP BYクエリを別のクエリでラップし(元のクエリをサブクエリにする)、外部クエリでSUM関数を使用します。 GROUP BY句なし。サブクエリは重複を削除し、外部クエリは重複排除された行の合計を合計します。