join on or 与多次 left join的区别
JOIN ON OR
和多次 LEFT JOIN
的区别主要在于连接条件和结果集的处理方式。
. JOIN ON OR
:在 SQL 查询中,可以使用 OR
作为连接条件。当使用 OR
作为连接条件时,只要满足其中一个条件,就会将两个表中的记录连接在一起。这可能导致结果集中出现重复的记录。例如:
SELECT a.*, b.*
FROM table_a a
LEFT JOIN table_b b ON a.id = b.id OR a.name = b.name;
- 多次
LEFT JOIN
:在 SQL 查询中,可以使用多个LEFT JOIN
将多个表连接在一起。每次执行LEFT JOIN
时,都会将当前表与下一个表进行连接。这可能导致结果集中出现重复的记录,并且需要手动去除这些重复记录。例如:
SELECT a.*, b.*, c.*
FROM table_a a
LEFT JOIN table_b b ON a.id = b.id
LEFT JOIN table_c c ON a.id = c.id;
总结:JOIN ON OR
和多次 LEFT JOIN
的主要区别在于连接条件和结果集的处理方式。使用 JOIN ON OR
可以简化查询语句,但可能导致结果集中出现重复记录;而多次 LEFT JOIN
需要手动去除重复记录,但可以更灵活地控制连接条件。
优化以下sql select b.code empID, b.name empName, a.glbdef1 as position, t4.OrgUnitName as orgUnitName, c2.EHRORGUNITID as ehrOrgunitId from bd_psndoc b left join gbs_psndoc_glbdef19 a on a.pk_psndoc = b.pk_psndoc and a.lastflag='Y' and a.glbdef28='Y' left join hi_psnjob d on d.lastflag = 'Y' and d.ismainjob = 'Y' and d.pk_psndoc = b.pk_psndoc left join view_gbs_psnjob c2 on d.clerkcode = c2.clerkcode left join gbsorgunits t4 on t4.orgunitid = c2.gbsorgunitid LEFT JOIN ( SELECT tt1.empid max(tt.operatordate) AS maxday FROM gbs_appointremove_applopinion tt LEFT JOIN gbs_appointremove_checkemp tt1 ON tt.workOrderNo = tt1.workOrderNo AND (tt.approvalLink = '监督室确认更正材料' OR tt.approvalLink = '监督室反馈查核结果') GROUP BY tt1.empid ) t5 ON b.code = t5.empid
这个SQL语句可以进行如下的优化:
尽量避免使用LEFT JOIN,因为它会返回左表中所有的记录,包括那些在右表中没有匹配的记录,这会导致查询结果集变大,影响查询性能。如果可以确定右表中一定有匹配的记录,可以使用INNER JOIN代替LEFT JOIN,这样可以减少结果集的大小。
尽量避免使用子查询,因为子查询会导致多次查询,影响查询性能。如果可以将子查询转换为JOIN操作,可以显著提高查询性能。
尽量避免使用函数,因为函数会对查询性能产生很大的影响。如果可以用其他方式代替函数,可以提高查询性能。
优化后的SQL语句如下:
SELECT b.code AS empID,
b.name AS empName,
a.glbdef1 AS position,
t4.OrgUnitName AS orgUnitName,
c2.EHRORGUNITID AS ehrOrgunitId
FROM bd_psndoc b
LEFT JOIN gbs_psndoc_glbdef19 a ON a.pk_psndoc = b.pk_psndoc
AND a.lastflag='Y'
AND a.glbdef28='Y'
LEFT JOIN hi_psnjob d ON d.lastflag = 'Y'
AND d.ismainjob = 'Y'
AND d.pk_psndoc = b.pk_psndoc
LEFT JOIN view_gbs_psnjob c2 ON d.clerkcode = c2.clerkcode
LEFT JOIN gbsorgunits t4 ON t4.orgunitid = c2.gbsorgunitid
LEFT JOIN (SELECT tt1.empid,
MAX(tt.operatordate) AS maxday
FROM gbs_appointremove_applopinion tt
JOIN gbs_appointremove_checkemp tt1 ON tt.workOrderNo = tt1.workOrderNo
AND (tt.approvalLink = '监督室确认更正材料'
OR tt.approvalLink = '监督室反馈查核结果')
GROUP BY tt1.empid) t5 ON b.code = t5.empid;
这个SQL语句中,使用了JOIN代替了子查询,使用了INNER JOIN代替了LEFT JOIN,尽量减少了结果集的大小,提高了查询性能。
调整以下SQL,查询结果里,每行的数据可能存在开始时间有值,而结束时间没有值,或者是结束时间有值,开始时间没有值,这类数据有其他属性都是相同的。我需要将同个工作号,同个序列号的数据,已最晚的结束时间减去最早的开始时间,计算出工时,单位小时。 SELECT a3.code a3_code,-- 工单 a2.code a2_code,-- 工作号 a2.Name a2_name,-- 工作名 a7.login_name, -- 报工人账号 a6.code a6_code, -- 序列号 a1.ACTUALSTARTTIME,-- 开始时间 a1.ACTUALENDTIME --结束时间 FROM F_PRODUCTIONWOMENTRESPONSE6635 a1 --生产工作实绩 LEFT JOIN F_WORKSEGMENTREQUIREMENT a2 -- 工作 ON a1.WORKSEGMENTREQUIREMENT=a2.id LEFT JOIN F_WORKREQUEST a3 --工单 ON a2.WorkRequest=a3.id LEFT JOIN F_MATERIALACTUAL a4 -- 物料实绩 ON a1.id =a4.SOURCE_ID LEFT JOIN F_MATERIALACTUALSN a5 -- 物料实绩的序列号 ON a4.id=a5.SOURCE_ID LEFT JOIN F_INDIVIDUALOBJECT a6 -- 实物对象 ON a5.related_id=a6.ID LEFT JOIN F_USER a7 --用户 ON a1.created_by_id =a7.ID WHERE a1.ACTUALSTARTTIME IS NOT NULL OR a1.ACTUALENDTIME IS NOT NULL ORDER BY a2_code DESC,a6_code
首先,用户的原始查询是从多个表LEFT JOIN过来的,主要涉及工单、工作号、序列号、开始时间和结束时间等字段。问题在于,同一工作号和序列号的数据可能存在开始时间或结束时间为空的情况,需要合并这些记录,计算总工时。
我应该先分析一下现有数据的情况。比如,同一个a2.code(工作号)和a6.code(序列号)可能有多个记录,每个记录可能有开始时间或结束时间,但可能不配对。用户想要的是把这些记录按工作号和序列号分组,然后取最早的开始时间和最晚的结束时间,然后计算两者的时间差,转换成小时。
那首先,我需要使用聚合函数,比如MIN(a1.ACTUALSTARTTIME)和MAX(a1.ACTUALENDTIME),然后按a2.code、a6.code等字段分组。原来的SELECT语句中的其他字段,比如a3.code(工单)、a2.Name(工作名)、a7.login_name(报工人账号)是否需要保留呢?可能这些字段在同一个工作号和序列号下是相同的,所以分组后不会有问题。但需要注意,如果有不同的值,可能会被合并,导致信息丢失。用户可能需要确认这些字段在分组后是否唯一。
另外,原始查询中的WHERE条件是a1.ACTUALSTARTTIME IS NOT NULL OR a1.ACTUALENDTIME IS NOT NULL,这是为了过滤掉开始和结束时间都为空的记录。这个条件在修改后的查询中可能仍然需要保留。
接下来,调整SQL结构,将GROUP BY应用到工作号、序列号以及其他相关字段上。然后计算时间差。需要注意的是,如果最晚的结束时间或最早的开始时间有一个是NULL,那么计算结果会是NULL,可能需要处理这种情况。但用户的需求是只要存在开始或结束时间,所以可能已经排除了两者都为NULL的情况,但需要确保至少有一个开始和一个结束时间存在,否则时间差无法计算。不过用户可能希望即使某些记录只有开始或结束,但同一分组内有开始和结束时间,所以需要确保分组后的MIN和MAX都不为NULL吗?或者允许结果为NULL?需要看具体情况。
然后,计算时间差,用MAX(a1.ACTUALENDTIME) - MIN(a1.ACTUALSTARTTIME),转换成小时。不同的数据库系统时间差函数可能不同,比如在MySQL中可以使用TIMESTAMPDIFF(HOUR, start, end),而在Oracle可能是(EXTRACT(DAY FROM (end - start)) * 24 + EXTRACT(HOUR FROM (end - start)))。但用户没有指定数据库类型,可能需要用通用的方法,或者假设使用标准SQL函数。例如,使用DATEDIFF函数,或者转换为秒再除以3600。
另外,原始查询中使用了多个LEFT JOIN,可能需要注意是否有些连接导致数据重复,进而影响聚合结果。例如,如果LEFT JOIN导致同一个a1记录被多次匹配,那么MIN和MAX可能会不正确。需要确认表之间的关系是否正确,特别是a4、a5、a6这些物料和序列号的连接,是否会导致a1的记录重复。如果有重复,可能需要先对a1进行聚合,再进行JOIN,或者在子查询中处理。
不过用户可能希望保持现有的连接逻辑,只是添加聚合。所以可能的步骤是:
- 在现有查询的基础上,按工作号、序列号等分组,并聚合开始和结束时间。
- 计算工时,单位小时。
- 确保其他字段在分组后是唯一的,或者使用聚合函数如MAX或MIN,如果这些字段在同一个分组中有多个值的话。
修改后的SQL可能如下:
SELECT a3.code a3_code, a2.code a2_code, a2.Name a2_name, a7.login_name, a6.code a6_code, MIN(a1.ACTUALSTARTTIME) AS earliest_start, MAX(a1.ACTUALENDTIME) AS latest_end, DATEDIFF(HOUR, MIN(a1.ACTUALSTARTTIME), MAX(a1.ACTUALENDTIME)) AS work_hours FROM ... WHERE ... GROUP BY a3.code, a2.code, a2.Name, a7.login_name, a6.code ORDER BY a2_code DESC, a6_code;
但需要根据具体的数据库系统调整时间差的计算方式。例如,在PostgreSQL中,可以使用EXTRACT(EPOCH FROM (MAX(end) - MIN(start)))/3600。或者使用函数如TIMESTAMPDIFF。
另外,用户可能需要确保在同一个分组内存在至少一个开始时间和一个结束时间,否则计算出来的工时可能为负数或者错误。例如,如果只有开始时间没有结束时间,那么MAX(end)是NULL,结果会是NULL。用户可能需要处理这种情况,例如用COALESCE或者条件判断,但用户的需求是“最晚的结束时间减去最早的开始时间”,所以如果其中有一个是NULL的话,可能无法计算工时,需要排除这些情况吗?或者用户允许工时存在NULL?
此外,原始查询中的其他字段是否在分组后仍然正确?比如a3.code(工单)、a2.Name(工作名)、a7.login_name(报工人账号)。如果同一个工作号和序列号下,这些字段有不同的值,那么分组后的结果可能不正确。需要确认这些字段在分组后是唯一的,或者用户希望如何展示。例如,假设同一个工作号和序列号下,这些字段的值都是相同的,那么GROUP BY是正确的。否则可能需要调整GROUP BY的字段或使用聚合函数。
例如,如果a7.login_name在同一个分组中有不同的值,那么直接放在GROUP BY中会导致多行,或者需要取其中一个值,比如MAX(a7.login_name)。但用户可能需要确保这些字段在分组后是唯一的,或者愿意接受任意的值。因此,在修改后的查询中,可能需要将这些字段都包含在GROUP BY子句中,或者使用聚合函数。
总结步骤:
- 确定需要分组的字段:a2.code(工作号)、a6.code(序列号),以及可能其他需要展示的字段如a3.code、a2.Name、a7.login_name,前提是它们在分组后唯一。
- 使用聚合函数MIN和MAX获取最早开始和最晚结束时间。
- 计算时间差,转换为小时。
- 调整GROUP BY子句,包含所有非聚合字段。
- 确保WHERE条件正确,排除不需要的记录。
- 可能需要处理时间差计算中的NULL值,但用户可能允许结果为NULL,表示无法计算。
可能的修改后的SQL:
SELECT a3.code AS a3_code, a2.code AS a2_code, a2.Name AS a2_name, a7.login_name, a6.code AS a6_code, MIN(a1.ACTUALSTARTTIME) AS earliest_start, MAX(a1.ACTUALENDTIME) AS latest_end, -- 假设使用MySQL的TIMESTAMPDIFF函数 TIMESTAMPDIFF(HOUR, MIN(a1.ACTUALSTARTTIME), MAX(a1.ACTUALENDTIME)) AS work_hours FROM F_PRODUCTIONWOMENTRESPONSE6635 a1 LEFT JOIN F_WORKSEGMENTREQUIREMENT a2 ON a1.WORKSEGMENTREQUIREMENT = a2.id LEFT JOIN F_WORKREQUEST a3 ON a2.WorkRequest = a3.id LEFT JOIN F_MATERIALACTUAL a4 ON a1.id = a4.SOURCE_ID LEFT JOIN F_MATERIALACTUALSN a5 ON a4.id = a5.SOURCE_ID LEFT JOIN F_INDIVIDUALOBJECT a6 ON a5.related_id = a6.ID LEFT JOIN F_USER a7 ON a1.created_by_id = a7.ID WHERE a1.ACTUALSTARTTIME IS NOT NULL OR a1.ACTUALENDTIME IS NOT NULL GROUP BY a3.code, a2.code, a2.Name, a7.login_name, a6.code ORDER BY a2_code DESC, a6_code;
但需要根据实际数据库调整时间差计算。例如,在SQL Server中,可以使用DATEDIFF(HOUR, MIN(...), MAX(...))。在Oracle中可能需要不同的处理方式。
另外,用户可能需要处理跨天的情况,比如如果开始时间是某天的23:00,结束时间是次日的1:00,那么DATEDIFF(HOUR)会返回2小时,这是正确的。但需要注意,如果存在NULL值,如何处理。例如,如果某个分组只有开始时间没有结束时间,或者反之,那么work_hours会是负数或者NULL。用户可能需要用COALESCE或者条件判断来显示0,但根据需求描述,可能需要保留NULL,或者确保每个分组都有至少一个开始和结束时间。此时可能需要进一步过滤,在HAVING子句中确保earliest_start和latest_end都不为NULL:
HAVING MIN(a1.ACTUALSTARTTIME) IS NOT NULL AND MAX(a1.ACTUALENDTIME) IS NOT NULL
但用户原查询中的WHERE条件已经排除了开始和结束时间都为NULL的情况,所以可能每个分组至少有一个开始或结束时间,但可能有的分组只有开始或只有结束。这时候,计算工时可能为NULL。用户需要决定是否要排除这些情况。根据用户的需求描述,是希望用最晚结束减最早开始,所以可能要求这两个时间都存在,否则工时无法计算。因此,可能需要添加HAVING子句来确保这两个时间都存在。
综上,最终的SQL可能需要添加HAVING条件:
... GROUP BY ... HAVING MIN(a1.ACTUALSTARTTIME) IS NOT NULL AND MAX(a1.ACTUALENDTIME) IS NOT NULL
这样,只有同时存在开始和结束时间的组才会被计算工时。
但用户的原需求中允许存在开始或结束时间单独存在的情况,所以是否需要这样处理?用户说“每行的数据可能存在开始时间有值,而结束时间没有值,或者是结束时间有值,开始时间没有值,这类数据有其他属性都是相同的。”,所以可能需要将同一工作号和序列号下的多个记录合并,即使某些记录只有开始或结束时间。例如,某个记录有开始时间,另一个记录有结束时间,那么合并后取最早的开始和最晚的结束。这时候,如果有至少一个开始和一个结束时间,就能计算出工时。否则,如果某个分组只有开始时间或只有结束时间,则无法计算,返回NULL。
因此,在最终的查询中,可能需要保留这些情况,或者根据用户需求决定是否过滤。用户可能需要工时结果,所以应该添加HAVING条件来排除无法计算的情况。
因此,修改后的SQL可能包括:
... GROUP BY ... HAVING MIN(a1.ACTUALSTARTTIME) IS NOT NULL AND MAX(a1.ACTUALENDTIME) IS NOT NULL
或者不添加,根据用户是否需要显示无法计算工时的记录。
总结,用户的SQL需要调整的地方是:
- 添加GROUP BY子句,按工作号、序列号等分组。
- 使用聚合函数MIN和MAX获取最早开始和最晚结束时间。
- 计算时间差作为工时。
- 可能需要处理时间差的函数,根据数据库类型。
- 确保分组后的其他字段正确。需要调整您的SQL以实现以下目标:
关键修改步骤
- 按工作号和序列号分组
- 取最早开始时间和最晚结束时间
- 计算时间差并转换为小时
- 确保分组字段一致性
修改后SQL
SELECT
a3.code AS a3_code,
a2.code AS a2_code,
a2.Name AS a2_name,
a7.login_name,
a6.code AS a6_code,
MIN(a1.ACTUALSTARTTIME) AS earliest_start_time, -- 取最早开始时间
MAX(a1.ACTUALENDTIME) AS latest_end_time, -- 取最晚结束时间
-- 计算工时(单位:小时),根据数据库类型调整时间差函数
EXTRACT(EPOCH FROM (MAX(a1.ACTUALENDTIME) - MIN(a1.ACTUALSTARTTIME)))/3600 AS work_hours
FROM F_PRODUCTIONWOMENTRESPONSE6635 a1
LEFT JOIN F_WORKSEGMENTREQUIREMENT a2 ON a1.WORKSEGMENTREQUIREMENT = a2.id
LEFT JOIN F_WORKREQUEST a3 ON a2.WorkRequest = a3.id
LEFT JOIN F_MATERIALACTUAL a4 ON a1.id = a4.SOURCE_ID
LEFT JOIN F_MATERIALACTUALSN a5 ON a4.id = a5.SOURCE_ID
LEFT JOIN F_INDIVIDUALOBJECT a6 ON a5.related_id = a6.ID
LEFT JOIN F_USER a7 ON a1.created_by_id = a7.ID
WHERE a1.ACTUALSTARTTIME IS NOT NULL OR a1.ACTUALENDTIME IS NOT NULL
GROUP BY a3.code, a2.code, a2.Name, a7.login_name, a6.code -- 按关键字段分组
HAVING MIN(a1.ACTUALSTARTTIME) IS NOT NULL -- 确保有开始时间
AND MAX(a1.ACTUALENDTIME) IS NOT NULL -- 确保有结束时间
ORDER BY a2_code DESC, a6_code;
关键点说明
分组逻辑
- 通过
GROUP BY a3.code, a2.code, a2.Name, a7.login_name, a6.code
确保相同工作号+序列号的记录合并 - 其他字段需满足:要么在分组字段中,要么被聚合
- 通过
时间计算
MIN(a1.ACTUALSTARTTIME)
取组内最早开始时间MAX(a1.ACTUALENDTIME)
取组内最晚结束时间- 使用
EXTRACT(EPOCH FROM ...)/3600
将时间差转为小时(适用于PostgreSQL,其他数据库需调整函数)
数据完整性
HAVING
子句过滤掉无开始/结束时间的组- 原始
WHERE
条件保留,排除全空的时间记录
常见问题处理
数据库差异
若使用MySQL:TIMESTAMPDIFF(HOUR, MIN(a1.ACTUALSTARTTIME), MAX(a1.ACTUALENDTIME))
若使用Oracle:(CAST(MAX(a1.ACTUALENDTIME) AS DATE) - CAST(MIN(a1.ACTUALSTARTTIME) AS DATE)) * 24
空值处理
若允许部分组缺少开始/结束时间,可移除HAVING
子句,但需用COALESCE
处理空值:COALESCE(EXTRACT(EPOCH FROM (MAX(...) - MIN(...)))/3600, 0)
相关推荐















