通过多个分组连接多个表

Posted

技术标签:

【中文标题】通过多个分组连接多个表【英文标题】:Join multiple tables by multiple grouping 【发布时间】:2016-02-27 10:21:32 【问题描述】:

我们有一个传球控制系统,每个传球动作都存储在Event 表中的MSSQL Server 中。我们想根据它们的关系将多个表与Event 表连接起来,如下图所示。但是,我不确定我使用的分组方法是否正确,因为查询需要很多时间。您能否澄清一下哦,如何通过多个分组加入这些表格?这是我使用的JOIN 子句:

SELECT t.CardNo, t.EventTime, t1.EmployeeName, 
    t1.Status, t2.EventCH, t3.DoorName, t4.JobName, t5.DeptName 
FROM Event t 

LEFT JOIN Employee AS t1 ON t.EmployeeID = t1.ID 
LEFT JOIN EventType AS t2 ON t.EventTypeID = t2.ID 
LEFT JOIN Door AS t3 ON t.DoorID = t3.ID 
LEFT JOIN Job AS t4 ON t1.JobID = t4.ID 
LEFT JOIN Department AS t5 ON t1.DepartmentID = t5.ID

ORDER BY t.EventID Desc

更新:以下发布执行计划:

【问题讨论】:

这个查询对我来说是正确的。你有什么问题? 耗时过长。实际上,我在第一秒就看到了结果(通过将滚动条拉到下方,在 SQL Server Management Studio 中的查询下方逐步检索结果)。但是查询需要 29 秒,对于 8 列的 125000 条记录来说似乎很高。您确定查询中有任何逻辑错误吗?我不确定分组顺序是否正确(也许最好将左 3 个表和右 3 个表分组,然后加入这 2 个组)。有什么想法吗? 您在这些表上设置了任何索引吗?鉴于您拥有 100K+ 记录和如此多的连接,这对我来说听起来并非不可能。 EventType - 内部联接,Door - 我猜也有内部联接,Employee+dep+job - 至少是一个带有内部联接的子查询,但如果事件仅在员工上下文中发生- 那么查询中的所有连接都必须是inner。并查看您的实际执行计划 尝试将LEFT替换为INNER,观察执行计划将如何变化。为每个外键列建立索引 - 再次查看执行计划。尝试使用左连接和索引 - 再次查看计划。 【参考方案1】:

您是否尝试过创建两个 CTE 来对连接进行分组?

因此,在一个 CTE 中为 Employee、Department 和 Job 创建一个联接。 对于另一个 CTE,为 Event、Eventtype 和 Door 创建一个连接。然后最后,使用 Employee ID 和 ID 加入两个 CTE。

将连接的表聚合在一起可能比一次性完成连接更快。对了,每张表的唯一性约束如何?

;WITH a AS

(
SELECT
    t1.ID
   ,t1.EmployeeName
   ,t1.Status
   ,t4.JobName
   ,t5.DeptName
FROM
    Employee t1
LEFT JOIN Job t4
    ON t1.JobID = t4.ID
LEFT JOIN Department t5
    ON t1.DepartmentID = t5.ID
)
,b AS
(
SELECT
    t.EmployeeID
   ,t.CardNo
   ,t.EventTime
   ,t2.EventCH
   ,t3.DoorName
FROM
    [Event] t
LEFT JOIN EventType t2
    ON t.EventTypeID = t2.ID
LEFT JOIN Door t3
    ON t.DoorID = t3.ID 
)

SELECT
    *
FROM
    b
LEFT JOIN a
    ON b.EmployeeID = a.ID

【讨论】:

这正是我在上面的评论中提出的问题(“......也许最好将左 3 个表和右 3 个表分组,然后加入这 2 个组”)。那么,您能否根据上述建议通过添加更新后的查询来更新您的答案? 另一方面,我不明白你的意思是“......每个表的唯一约束如何?”。请您再解释一下好吗? 我已经更新了我的评论,试试看 :)。在进行连接时,请始终确保您设置了唯一的列,即确保 ID 在 Employee 表中是唯一的,等等,否则当您连接到另一个表时,您可能会引入重复项。 这正是我所寻找的,通过这种 CTE 方法,查询时间从 30 秒缩短到 4 秒 :) 非常感谢... 很高兴听到:),顺便说一下,刚刚看了代码,b 应该加入到 a 中。我已经给你换了。从您的原始帖子中看到,事件表是您要加入的那个。【参考方案2】:

您问题中的查询不代表查询计划中的查询。特别是有条件

RIGHT(t.cardno, 8) = RIGHT(t1.cardno, 8)

函数的使用排除了计划索引的使用。

我的建议是为每个表添加一个虚拟列,对其进行索引,然后在连接中使用它:

alter table tEvent add cardno8 as RIGHT(cardno, 8);
alter table tEmployee add cardno8 as RIGHT(cardno, 8);

create index idx_tEvent_cardno8 tEvent(cardno8);
create index idx_tEmployee_cardno8 tEmployee(cardno8);

这应该有助于提高性能,如果您将比较更改为:

on t.cardno8 = t1.cardno8

此外,您的查询没有WHERE 子句,因此它(可能)正在处理大量数据。

【讨论】:

实际上,为了简洁起见,我修改了查询,但索引还有另一个问题。通常我应该使用 PK-FK 来加入和 Employee 表,但由于 Event 表中的 EmployeeID 中没有数据,我必须使用 CardNo 字段(也是 RIGHT 方法,因为数字不相等)。另一方面,在尝试创建索引时,我遇到了您的代码错误。所以,我有两个关于这个问题的问题。你能澄清一下吗? 1) 我们是否应该使用索引这种情况,即当我们必须使用除 PK-FK 之外的字段(即 CardNo)来连接表时?如果是这样,你能检查上面的代码吗? 2) 修改table,加入cardno8,这个虚拟列会不会有问题?谢谢...

以上是关于通过多个分组连接多个表的主要内容,如果未能解决你的问题,请参考以下文章

从 laravel 中的多个表中正确输出分组数据

sqlalchemy:如何通过一个查询连接多个表?

MySQL管理和查询数据:连接查询基础

MySQL管理和查询数据:连接查询基础

MySQL管理和查询数据:连接查询基础

MySQL管理和查询数据:连接查询基础