一次分组多个条件

时间:2015-12-12 00:01:28

标签: php mysql sql

我有这样的表:

用户

+----+----------+-------------+                           
| id | name     | other_stuff |                           
+----+----------+-------------+                           
|  1 | John Doe |           x |                           
|  2 | Jane Doe |           y |                           
|  3 | Burt Olm |           z |                           
+----+----------+-------------+                           

地方

+----+------------+-------------+                         
| id | name       | other_stuff |                         
+----+------------+-------------+                         
|  1 | Building A |           x |                         
|  2 | Building B |           y |                         
+----+------------+-------------+                         

主题

+----+------------+-------------+                         
| id | name       | other_stuff |                         
+----+------------+-------------+                         
|  1 |       Math |           x |                         
|  2 |    English |           y |                         
+----+------------+-------------+           

加入表:

PastLectures =发生的讲座

+----+-----------+----------+------------+---------+------------+
| id | id_users  | id_place | id_subjects| length  | date       |
+----+-----------+----------+------------+---------+------------+
|  1 |         1 |        1 |          1 |      60 | 2015-10-25 |
|  2 |         1 |        1 |          1 |     120 | 2015-11-06 |
|  3 |         2 |        2 |          2 |     120 | 2015-11-04 |
|  4 |         2 |        2 |          1 |      60 | 2015-11-10 |
|  5 |         1 |        2 |          1 |      60 | 2015-11-10 |
|  6 |         2 |        2 |          1 |      40 | 2015-11-15 |
|  7 |         1 |        2 |          2 |      30 | 2015-11-15 |
+----+-----------+----------+------------+---------+------------+

我想在给定的月份显示每个用户的所有课程的总和。 SUM应按每个地点主题进行分组。

最终PHP输出的结果应如下所示:

November 2015                                             
+------------+-------------+---------------+-------------+
| Users.name | Places.name | Subjects.name | sum(length) |
+------------+-------------+---------------+-------------+
|   Burt Olm |           - |             - |           - |
|   Jane Doe |  Building B |          Math |         100 |
|          = |           = |       English |         120 |
|   John Doe |  Building A |          Math |         120 |
|          = |  Building B |          Math |          60 |
|          = |           = |       English |          30 |
+------------+-------------+---------------+-------------+

我尝试使用多个GROUP BY(Group by - multiple conditions - MySQL)在纯SQL查询中创建完整输出,但是当我执行 GROUP BY User.id,Places.id 时,它会显示每个用户只有一次(3个结果),无论其他GROUP BY条件如何。

SQL:

SELECT PastLectures.id_users,Users.name AS user,Places.name AS places,Subjects.name AS subjects
  FROM PastLectures
  LEFT JOIN Users ON PastLectures.id_users = Users.id
  LEFT JOIN Places ON PastLectures.id_Places = Places.id
  LEFT JOIN Subjects ON PastLectures.id_Subjects = Subjects.id
  WHERE date >= \''.$monthStart->format('Y-m-d H:i:s').'\' AND date <= \''.$monthEnd->format('Y-m-d H:i:s').'\'
  GROUP BY Users.id,Places.id
  ORDER BY Users.name,Places.name,Subjects.name

但是我不介意解决方案的一部分是用PHP完成的,我只是不知道下一步该做什么。

修改

我还有一个表格时间表,存储定期教授什么哪里。它仅存储表的已使用组合(每个有效组合一次)。

Timetable = lectures that regularly take place
+----+-----------+----------+------------+-------------+
| id | id_users  | id_place | id_subjects| other_stuff |
+----+-----------+----------+------------+-------------+
|  1 |         1 |        1 |          1 |           x |
|  2 |         1 |        2 |          1 |           y |
|  3 |         1 |        2 |          2 |           z |
|  4 |         2 |        2 |          1 |           a |
|  5 |         2 |        2 |          2 |           b |
+----+-----------+----------+------------+-------------+

是否可以仅添加组合在此表中有一行的用户?

在这种情况下,这意味着省略 Burt Olm (时间表中没有id = 3)。但是如果Burt有一个时间表条目但仍然没有PastLectures条目,他会在这里显示样本结果(他应该在那个月进行一次演讲,因为他在时间表中,但没有进行过讲座。)

基于@ Barmar的解决方案,我通过将时间表作为主表并再添加一个LEFT JOIN来满足这些需求,从而更新了最终的SQL。

最终SQL:

SELECT Users.name AS user,Places.name AS places,Subjects.name AS subjects, SUM(PastLectures.length)
  FROM Timetable
  LEFT JOIN PastLectures ON PastLectures.id_users = Timetable.id_users AND PastLectures.id_place = Timetable.id_place AND PastLectures.id_subjects = Timetable.id_subjects
    AND date BETWEEN '2015-11-01 00:00:00' AND '2015-11-30 23:59:59'
  LEFT JOIN Places ON Timetable.id_Place = Places.id
  LEFT JOIN Subjects ON Timetable.id_Subjects = Subjects.id
  LEFT JOIN Users ON Timetable.id_users = Users.id
  GROUP BY Timetable.id,Timetable.id_users,Timetable.id_Place,Timetable.id_Subjects
  ORDER BY Users.name,Places.name,Subjects.name

2 个答案:

答案 0 :(得分:4)

您需要在Subjects.id中加入GROUP BY,以便为每个主题获得单独的结果。

此外,您不应在LEFT JOIN列中使用GROUP BY加入的表格中使用列。如果这样做,所有不匹配的行将组合在一起,因为它们在该列中都有NULL。使用主表中的列。

GROUP BY PastLectures.id_users, PastLectures.id_Place, PastLectures.id_Subjects

DEMO

请注意,演示输出中的Burt Olm没有行,因为他的所有行都被WHERE子句过滤掉了。如果您希望展示所有用户,则应将Users设为主表,而不是PastLectures。加入date时,ON条件需要移入PastLectures条款。

SELECT Users.name AS user,Places.name AS places,Subjects.name AS subjects, SUM(length)
  FROM Users
  LEFT JOIN PastLectures ON PastLectures.id_users = Users.id 
    AND date BETWEEN '2015-11-01 00:00:00' AND '2015-11-30 23:59:59'
  LEFT JOIN Places ON PastLectures.id_Place = Places.id
  LEFT JOIN Subjects ON PastLectures.id_Subjects = Subjects.id
  GROUP BY Users.id, PastLectures.id_Place, PastLectures.id_Subjects
  ORDER BY Users.name,Places.name,Subjects.name

DEMO

答案 1 :(得分:0)

根据标准SQL,除了聚合字段(如sum)之外,您应该选择GROUP BY您选择的所有字段。虽然MySql允许这样做,但是当它可以遵守标准时,最好这样做(谁知道何时需要将代码移植到另一个数据库引擎)。所以写这样的SQL:

  SELECT    PastLectures.id_users,
            Users.name AS user,
            Places.name AS places,
            Subjects.name AS subjects,
            Sum(length)
  FROM      PastLectures
  LEFT JOIN Users       ON PastLectures.id_users = Users.id
  LEFT JOIN Places      ON PastLectures.id_Places = Places.id
  LEFT JOIN Subjects    ON PastLectures.id_Subjects = Subjects.id
  WHERE     date BETWEEN \''.$monthStart->format('Y-m-d H:i:s').'\'
                 AND     \''.$monthEnd->format('Y-m-d H:i:s').'\'
  GROUP BY  PastLectures.id_users,
            Users.name,
            Places.name,
            Subjects.name
  ORDER BY  Users.name,
            Places.name,
            Subjects.name