LEFT JOIN 查询返回多个结果

3
下面是我的查询,它检查数据表中每行的ID、日期/时间和关键操作(以及其他数据)。这个表格无法更改,超出了我的控制范围。
查询查找创建操作的发生情况(始终是第一个),提取创建的ID和日期/时间,然后在一行数据中提取其他关键操作(添加信息、预订约会、接受)的日期/时间。
SELECT _create.ID AS ID, 
_create.`datetime` AS Create, 
_inform.`datetime` AS Add_Info,
_bookap.`datetime` AS Book_Appt,
_accept.`datetime` AS Accept,
FROM table AS _create
LEFT JOIN table AS _inform ON (_create.ID = _inform.ID AND _inform.action = 'Add Info')
LEFT JOIN table AS _bookap ON (_create.ID = _bookap.ID AND _bookap.action = 'Book Appt')
LEFT JOIN table AS _accept ON (_create.ID = _accept.ID AND _accept.action = 'Accept')
WHERE _create.action="Create"

所以我得到的东西类似于:
ID  -  Create Date - Inform Date - Bookap Date - Accept Date
1234   01/02/2013    02/02/2013    09/02/2013    10/02/2013 

这个很有效。

但是如果查询发现同一ID的两个事件类型相同,即“预约”时,有时会出现这种情况,它会为该ID拉取两行数据。所以我得到:

ID  -  Create Date - Inform Date - Bookap Date - Accept Date
1234   01/02/2013    02/02/2013    09/02/2013    10/02/2013 
1234   01/02/2013    02/02/2013    15/02/2013    10/02/2013     

我需要它忽略第二次出现并仅针对每个ID返回一行结果。或者,更好的方法是返回一行显示Bookap Date1和Bookap Date2的结果。
你有任何想法吗?
4个回答

3
使用 GROUP BY 将记录分组为一行,然后使用 min() 选择最早的事件日期。
SELECT _create.ID AS ID, 
min(_create.`datetime`) AS Create, 
min(_inform.`datetime`) AS Add_Info,
min(_bookap.`datetime`) AS Book_Appt,
min(_accept.`datetime`) AS Accept,
FROM table AS _create
LEFT JOIN table AS _inform ON (_create.ID = _inform.ID AND _inform.action = 'Add Info')
LEFT JOIN table AS _bookap ON (_create.ID = _bookap.ID AND _bookap.action = 'Book Appt')
LEFT JOIN table AS _accept ON (_create.ID = _accept.ID AND _accept.action = 'Accept')
WHERE _create.action="Create"
GROUP BY _create.ID;

一种快速而不太精确的方法是使用 group_concat() 函数代替上面查询中的 min() ,以显示所有事件日期但仍只返回一行。这将把多个 datetime 放入单个列中,您的应用程序层随后需要解析它们。

谢谢,我明白了。然而,在 GROUP BY 之前,查询需要 8 秒才能返回 30 行(限制为 30)。现在似乎永远运行不完了(已经 3 分钟还在计算……)有什么想法吗? - highfidelity
@highfidelity 运行 EXPLAIN SELECT ... 并向我们展示输出结果。虽然这可能应该是一个单独的问题。 - Martin
根据下面对Mike的评论,ID上的索引缺失了。没有添加这个索引之前,查询现在运行得非常快速。 - highfidelity

0
如果您想在单个行中返回多个Book_Appt值,可以像这样使用GROUP_CONCAT
SELECT _create.ID AS ID, 
_create.`datetime` AS Create, 
_inform.`datetime` AS Add_Info,
GROUP_CONCAT(_bookap.`datetime`) AS Book_Appt,
_accept.`datetime` AS Accept,
FROM table AS _create
LEFT JOIN table AS _inform ON (_create.ID = _inform.ID AND _inform.action = 'Add Info')
LEFT JOIN table AS _bookap ON (_create.ID = _bookap.ID AND _bookap.action = 'Book Appt')
LEFT JOIN table AS _accept ON (_create.ID = _accept.ID AND _accept.action = 'Accept')
WHERE _create.action="Create"
GROUP BY `ID`

在您的情况下,这将返回一个逗号分隔的日期时间值列表。

因此,您的输出可能如下所示:

ID  -  Create Date - Inform Date - Bookap Date      -       Accept Date
1234   01/02/2013    02/02/2013    09/02/2013,15/02/2013    10/02/2013 

谢谢 Mike。这很理想,但是根据我之前对 Asaph 的评论,先前在 8 秒内运行的查询现在只会不断地运行 - 有什么想法吗? - highfidelity
你在_create.ID_create.action上有索引吗? - Mike Brant
糟糕!ID 上缺少索引。现在已经修复好了。感谢您回复我 - 给了我很大的帮助 :) - highfidelity
另外,我应该提到您需要确保在连接条件中使用的任何字段上都有索引,因此 _inform.ID_inform.action_bookap.ID_bookap.action_accept.ID_accept.action - Mike Brant
我在查询的表中将索引放在了ID列上。_inform.ID和_bookap.ID只存在于查询中,所以我需要在它们上面建立索引吗?如果需要,在哪些情况下需要建立索引?如何添加? - highfidelity
@highfidelity 我不明白你所说的“_inform.ID”和“_bookap.ID”“只存在于查询中”的意思。这些不是分别与“_create.ID”相关的表中的列吗?您可以像为“_create.ID”一样在这些列上添加索引(当然,如果此ID值需要成为这些表中的主键或唯一索引而不是标准索引,则除外)。 - Mike Brant

0

当然可以,在WHERE子句之后使用GROUP BY _create.ID


1
谢谢Martin。像其他人一样,你的评论也很有帮助。 - highfidelity

0
针对您的第一个问题(仅返回一行),我建议使用ORDER BY子句对结果集进行排序,然后使用LIMIT 1表达式仅返回一行。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接