SQL公用表表达式(CTE)技术深度解析:考勤与生产数据分析实战
公用表表达式(CTE)作为SQL查询中的强大工具,通过模块化设计和临时结果集的高效利用,显著提升了复杂查询的可读性和维护性。在考勤统计与生产数据分析这类多表关联、多层次计算的场景中,CTE技术能够将复杂逻辑分解为清晰的步骤,使查询结构更加直观。本文基于实际生产环境中处理破碎车间考勤与绩效统计的SQL代码案例,全面解析CTE技术的核心概念、语法结构、应用场景及性能优化技巧。
一、CTE基本概念与语法结构
公用表表达式(CTE)是SQL查询中定义的临时命名结果集,通过WITH
关键字引入,允许在单个查询中多次引用,且不会实际存储到数据库中。CTE的语法结构简洁明了,通常由三部分组成:表达式名称、列别名(可选)和查询定义。例如,WITH cte_name AS (SELECT column1 FROM table1) SELECT * FROM cte_name;
。与传统子查询相比,CTE具有以下优势:
可读性提升:CTE将复杂查询分解为逻辑块,每个CTE都有清晰的名称,使代码结构更清晰。相比之下,多层嵌套的子查询很快会变得难以阅读和调试。Meta面试题的解答案例表明,CTE使复杂查询的可读性显著提升,即使面对需要跨多个表和多次计算的场景,也能保持代码的简洁性。
重用性增强:CTE可以在同一查询中多次引用,避免了重复计算和代码冗余。在需要多次使用相同中间结果的场景中,CTE比子查询更高效。例如,用户代码中ActualWork
的考勤统计结果被主查询多次引用,避免了重复计算。
递归查询支持:CTE支持递归查询,这是处理树形结构和层次数据的强大工具。虽然用户代码未涉及递归,但在其他场景如组织架构查询、菜单层级结构等中,递归CTE提供了简洁而高效的解决方案。
代码模块化:CTE使得复杂查询可以按功能拆分为多个模块,每个模块负责特定任务,大大提高了代码的可维护性。当查询需要修改时,只需调整对应的CTE,而无需在整个查询中查找和替换,这在大型企业应用中尤为重要。
二、代码中CTE的逻辑层次与数据流动
用户提供的SQL代码通过三个CTE构建了一个完整的考勤与生产数据分析流程,每个CTE都有明确的功能定位,并形成数据处理的流水线:
StaffDailyHours CTE作为基础层,从考勤表KQ破碎考勤流水视图
中提取数据。该CTE使用CAST(kq.day AS DATE)
将考勤时间转换为纯日期,按员工和日期分组统计每日工作小时。WHERE
子句限制了查询范围为最近30天的考勤数据,确保只处理相关数据。此CTE是整个查询的起点,为后续处理提供了标准化的考勤数据。
WITH StaffDailyHours AS (
SELECT
CAST(kq.day AS DATE) AS DDate,
kq.StaffName,
SUM(kq.上班小时) AS DailyHours
FROM KQ破碎考勤流水视图 kq
WHERE kq.day BETWEEN @startDate AND @endDate
GROUP BY CAST(kq.day AS DATE), kq.StaffName
)
StaffHoursWithShift CTE作为中间层,通过多表关联补充班次信息。首先与搬运破碎工班次表_明细
关联,筛选出非"废带分拣工"的员工;然后与破碎班次表_明细
关联,确保考勤日期在班次的有效期内。此CTE的核心是将员工考勤数据与其所属班次进行精确匹配,为后续的绩效计算提供基础。
StaffHoursWithShift AS (
SELECT
s.DDate,
s.StaffName,
s.DailyHours,
c.绩效班次 AS Shift
FROM StaffDailyHours s
INNER JOIN 搬运破碎工班次表_明细 b
ON s.StaffName = b.员工姓名
AND b.工作品种 != '废带分拣工'
INNER JOIN 破碎班次表_明细 c
ON b.所属班次 = c.班次
AND s.DDate BETWEEN c.所属日期 AND c.结束日期
)
ActualWork CTE作为汇总层,按日期和班次汇总人数和总工时。使用COUNT(DISTINCT StaffName)
统计实际出勤人数,避免重复计数;使用SUM(DailyHours)
计算班次总工时。此CTE完成了考勤数据的最终整理,为后续的绩效计算和工资核算提供了结构化的数据。
ActualWork AS (
SELECT
DDate,
Shift,
COUNT(DISTINCT StaffName) AS ActualWorkNumber,
SUM(DailyHours) AS TotalStaffHours
FROM StaffHoursWithShift
GROUP BY DDate, Shift
)
这三层CTE构成了一条清晰的数据处理流水线:从原始考勤数据出发,经过班次信息补充,最终形成汇总的考勤统计。这种模块化设计使查询逻辑易于理解,也便于后续的维护和修改。
三、CTE在复杂查询中的应用场景
CTE技术在处理复杂查询时展现出强大的应用价值,尤其适合以下场景:
多表关联与数据清洗:在需要从多个表中提取数据并进行清洗的场景中,CTE提供了分步处理的机制。用户代码中的班次匹配就是一个典型示例,通过CTE分步处理关联和条件筛选,避免了复杂的嵌套子查询。
中间结果复用:当同一个中间结果需要被多次引用时,CTE比子查询更高效。用户代码中ActualWork
的考勤统计结果被主查询多次引用,用于计算不同部门的绩效和工资,这种复用避免了重复计算。
分段计算与逻辑拆分:复杂的计算可以分段进行,每个CTE负责一个计算阶段。例如,用户代码中的生产检验单重量统计和计时工作记录分别通过独立的CTE处理,最终在主查询中合并,使整个计算过程更加清晰。
与窗口函数结合:CTE可以与窗口函数结合,实现更复杂的分析计算。虽然用户代码中未使用窗口函数,但在实际应用中,可以使用CTE预处理数据,然后在窗口函数中进行排名、累计等计算。
递归查询:处理树形结构和层级关系数据时,递归CTE提供了简洁的解决方案。例如,查询员工的上下级关系或菜单的层级结构时,递归CTE比传统方法更高效。
在用户代码中,CTE技术被用于构建一个完整的考勤与生产数据分析系统,包括考勤统计、生产检验单重量计算、计时工作记录处理等模块。这种应用方式充分体现了CTE在复杂查询中的核心价值:将复杂逻辑分解为可管理的步骤,提高代码可读性和维护性。
四、CTE的执行计划与性能优化
尽管CTE主要是一种结构优化工具,但在某些情况下也可以显著提升查询性能。理解CTE的执行计划是优化查询的关键:
执行顺序:CTE按定义顺序执行,其结果集对后续的CTE和主查询可见。在用户代码中,StaffDailyHours
首先执行,然后是StaffHoursWithShift
,最后是ActualWork
,形成数据处理的流水线。
物化策略:CTE是否会被物化为临时表取决于数据库优化器的决策。SQL Server等数据库系统会对被多次引用的CTE自动物化,以避免重复计算。用户代码中ActualWork
的结果被主查询多次使用,因此优化器可能会物化此CTE。
性能优化技巧:
-
索引优化:为高频使用的关联字段和筛选条件创建索引。例如,为
员工姓名
、班次
、所属日期
等字段创建索引,可以显著提高关联速度。在破碎班次表_明细
中,创建复合索引(班次, 所属日期)
将有助于快速查找匹配的班次。 -
JOIN顺序优化:优先连接小表再连接大表,减少中间结果数据量。用户代码中先连接
搬运破碎工班次表_明细
(假设较小)再连接考勤表(可能较大)的策略是合理的。 -
避免全表扫描:确保在
WHERE
子句中使用索引列,避免对索引列使用函数或计算。用户代码中对day
字段使用CAST
可能导致索引失效,可以考虑在表中直接存储日期部分或使用覆盖索引。 -
物化控制:通过
MATERIALIZED
提示强制物化CTE,适用于大数据量和需要复用中间结果的场景。例如,在SQL Server中可以使用WITH (MATERIALIZED)
强制物化ActualWork
CTE。 -
统计信息维护:确保数据库中的统计信息是最新的,以便优化器能够做出正确的执行计划。对于经常更新的表,可以手动更新统计信息:
UPDATE STATISTICS KQ破碎考勤流水视图;
注意事项:
-
数据完整性:用户代码中使用
INNER JOIN
可能导致未关联到班次表的员工数据丢失。需要根据业务需求判断是否应该使用LEFT JOIN
保留所有考勤数据。 -
日期范围覆盖:确保
破碎班次表_明细
中的所属日期
和结束日期
能够正确覆盖考勤日期范围。如果班次表的日期范围设置不当,可能导致考勤数据与班次信息匹配错误。 -
字段传递一致性:CTE之间传递的字段(如
DDate
和Shift
)需要类型匹配,并确保关联条件正确。用户代码中通过CAST
统一日期格式的做法是正确的。 -
执行计划分析:使用
EXPLAIN
或图形化工具(如SQL Server Management Studio)分析CTE的执行计划,定位高开销操作。例如,可以查看破碎班次表_明细
的关联是否使用了索引,或者是否存在不必要的排序操作。 -
避免过深嵌套:虽然用户代码中CTE未嵌套,但需注意CTE的嵌套层级不宜过深。SQL Server等系统对CTE的嵌套层级有限制(如64层),过深嵌套可能导致查询性能下降。
五、CTE在工资计算场景中的实际应用
用户代码中的CTE技术展示了如何处理考勤与绩效数据的关联计算,这种模式在工资计算场景中具有广泛应用价值:
绩效计算模块化:通过BreakingStrapPerformance
、BreakingFilmPerformance
、OverIronPerformance
等字段,将不同部门的绩效计算分解为独立的逻辑块。每个绩效计算都包含基本单价、人数百分比调整后的实际单价,以及与生产重量的乘积,形成了清晰的绩效计算流水线。
COALESCE(pd.单价, 0) * COALESCE(pd_percent.百分比/100, 1) * COALESCE(p.破带重量, 0) AS BreakingStrapPerformance,
计时工作记录整合:通过TimingWork
CTE独立处理计时工作记录,然后在主查询中通过LEFT JOIN
与考勤数据合并。这种设计使计时工资计算与考勤绩效计算相互独立,提高了代码的可维护性。
TimingWork AS (
SELECT
CAST(t.开始时间 AS DATE) AS DDate,
t.班次 AS Shift,
t.任务类型,
t.计时人数,
t.计时时数,
t.单价
FROM 破碎计时工作记录表_主表 t
WHERE t.开始时间 BETWEEN @startDate AND @endDate
)
总工资计算:将不同部门的绩效和计时工资整合为总工资,通过CTE的中间结果复用避免了重复计算。用户代码中将各部门绩效和计时工资相加,然后除以总工时计算"每小时工资",这种设计清晰展示了工资计算的逻辑。
(COALESCE(pd.单价, 0) * COALESCE(pd_percent.百分比/100, 1) * COALESCE(p.破带重量, 0))+
(COALESCE(pm.单价, 0) * COALESCE(pm_percent.百分比/100, 1) * COALESCE(p.破膜重量, 0))+
(COALESCE(pt.单价, 0) * COALESCE(pt_percent.百分比/100, 1) * COALESCE(p.过铁重量, 0))+
COALESCE(tw.单价, 0)
AS TotalSalary,
人数百分比调整:通过BreakingStrapActualUnitPrice
等字段,实现了根据实际出勤人数调整单价的计算逻辑。这种动态调整在绩效工资计算中非常常见,CTE使这一复杂逻辑变得清晰易懂。
COALESCE(pd.单价 * COALESCE(pd_percent.百分比/100, 1), 0) AS BreakingStrapActualUnitPrice,
零值防御与默认值:用户代码中大量使用COALESCE
函数处理可能的空值,确保计算结果的完整性。例如,COALESCE(tw.计时人数, 0)
将空值转换为0,避免了计算错误。
COALESCE(tw.计时人数, 0) AS TimingPeople,
最终结果排序:通过ORDER BY a.DDate, a.Shift
对结果进行排序,使输出数据更加有序和易于分析。这种排序在报表生成和数据分析中非常重要。
这种应用模式展示了CTE技术在工资计算中的核心价值:通过模块化设计将复杂计算分解为可管理的步骤,同时利用中间结果复用提高查询效率。在实际应用中,可以进一步扩展这种模式,例如加入税收计算、社保扣除等模块,构建完整的工资核算系统。
六、CTE与其他查询技术的对比与选择
CTE并非适用于所有场景,了解其与其他查询技术的优缺点对比有助于做出合理选择:
与子查询对比:
特性 | CTE | 子查询 |
---|---|---|
可读性 | 更高(模块化设计) | 较低(嵌套结构复杂) |
重用性 | 可多次引用 | 通常只能使用一次 |
递归能力 | 支持递归查询 | 不支持递归查询 |
执行性能 | 与子查询相近,但可减少重复计算 | 可能因重复执行导致性能下降 |
在用户代码中,CTE的使用明显提升了查询的可读性和维护性。例如,考勤统计和生产检验单重量计算被拆分为独立的CTE,使整个查询结构更加清晰。对于复杂查询,CTE的可读性优势尤为明显,即使查询性能与子查询相近,其结构优势也值得优先考虑。
与临时表对比:
特性 | CTE | 临时表 |
---|---|---|
作用范围 | 仅限当前查询 | 可跨多个查询使用 |
维护成本 | 无需维护 | 需要创建和清理 |
性能影响 | 通常与子查询相近 | 可能因临时表操作增加开销 |
灵活性 | 更灵活(可递归、可复用) | 较低(结构固定) |
CTE在大多数情况下可以替代临时表,特别是在需要模块化设计且中间结果仅在当前查询中使用的情况下。用户代码中的CTE设计避免了临时表的创建和清理开销,同时保持了良好的可读性。然而,对于非常复杂的查询或需要跨多个查询使用中间结果的场景,临时表可能仍是更好的选择。
与视图对比:
特性 | CTE | 视图 |
---|---|---|
作用范围 | 仅限当前查询 | 可被多个查询引用 |
参数化 | 支持(可引用变量) | 不支持参数化 |
执行效率 | 与子查询相近 | 可能因视图的物化而增加开销 |
可维护性 | 高(每个查询独立) | 高(集中维护) |
CTE的优势在于其参数化能力和与当前查询的紧密集成,这在用户代码中得到了充分体现——通过变量@startDate
和@endDate
定义日期范围,然后在所有CTE中使用这些变量。这种设计使查询更加灵活,能够轻松调整日期范围而不必修改每个CTE的定义。相比之下,视图无法直接引用变量,需要通过参数化视图或动态SQL实现类似功能,增加了复杂度。
七、CTE技术的最佳实践与安全建议
基于用户代码的分析和CTE技术的特性,以下是使用CTE的最佳实践和安全建议:
命名规范:为CTE赋予清晰、有描述性的名称,反映其功能。用户代码中的StaffDailyHours
、StaffHoursWithShift
等名称就非常清晰,立即让人明白每个CTE的用途。
关联条件明确:确保JOIN条件和WHERE子句中的条件明确无歧义。用户代码中所属班别 = c.班次
和s.DDate BETWEEN c.所属日期 AND c.结束日期
等条件清晰指定了关联逻辑,避免了潜在的数据匹配错误。
数据完整性保障:根据业务需求选择合适的JOIN类型。用户代码中StaffHoursWithShift
使用INNER JOIN
确保只有有效关联的班次数据才被保留,这在考勤统计中是合理的。但在某些场景下(如保留所有考勤记录),可能需要使用LEFT JOIN
。
字段传递一致性:确保CTE之间传递的字段类型和名称一致。用户代码中DDate
和Shift
字段在CTE间保持一致,确保了数据对齐的准确性。
避免过深嵌套:虽然用户代码中的CTE未嵌套,但需注意CTE的嵌套层级不宜过深。SQL Server等系统对CTE的嵌套层级有限制(如64层),过深嵌套可能导致查询性能下降。
性能调优策略:
-
索引优化:为关联字段(如
员工姓名
、班次
)和筛选条件(如day
字段)创建索引,特别是复合索引。例如,破碎班次表_明细
的班次
和所属日期
字段可以创建复合索引。 -
查询重写:对于性能敏感的查询,可以考虑将CTE转换为临时表或物化视图,特别是当CTE的结果集较大且需要多次引用时。
-
统计信息维护:定期更新表的统计信息,确保优化器能够生成高效的执行计划。对于频繁更新的表,可以手动更新统计信息。
-
执行计划分析:使用
EXPLAIN
或图形化工具分析CTE的执行计划,定位高开销操作。例如,可以查看破碎班次表_明细
的关联是否使用了索引。
数据安全与权限控制:CTE本身不提供安全机制,但可以通过限制对基础表的访问权限来保护数据。例如,确保只有授权用户才能访问考勤表和班次表。
版本兼容性:CTE在SQL Server 2005及更高版本中支持,但在不同数据库系统中可能存在语法差异。例如,PostgreSQL支持RECURSIVE
关键字定义递归CTE,而MySQL 8.0+也支持CTE,但某些功能可能受限。
递归查询限制:如果后续扩展需要递归查询,应设置合理的递归深度限制,避免无限递归。在SQL Server中可以通过OPTION (MAXRECURSION n)
设置最大递归次数。
八、总结与扩展应用
公用表表达式(CTE)技术通过模块化设计和中间结果复用,为处理复杂查询提供了强大工具。在考勤统计与生产数据分析这类多表关联、多层次计算的场景中,CTE技术能够显著提升查询的可读性和维护性,同时在某些情况下也能提高查询性能。
用户代码展示了一个完整的CTE应用案例:从考勤数据统计到班次信息关联,再到生产检验单重量计算,最后整合计时工作记录和单价信息,计算总工资和每小时工资。这种模块化设计使每个计算步骤都清晰可见,便于理解和维护。
CTE技术的扩展应用包括:递归查询处理组织架构、菜单层级结构;与窗口函数结合进行排名、累计计算;多步骤数据清洗和转换;以及构建复杂的分析报表。在工资计算场景中,CTE可以进一步扩展为处理税收、社保、奖金等模块,构建完整的工资核算系统。
在性能优化方面,除了索引优化和执行计划分析,还应注意:避免在CTE中进行不必要的复杂计算;根据数据量大小决定是否物化CTE;对于大数据量查询,可以考虑将CTE转换为临时表或分步处理。
最终,CTE技术的价值不仅体现在查询性能上,更体现在代码可读性和维护性上。在处理复杂业务逻辑时,CTE能够帮助开发者构建清晰、可维护的查询,减少代码冗余,提高开发效率。
九、示例代码参考
-- 定义日期范围
DECLARE @endDate DATE = GETDATE();
DECLARE @startDate DATE = DATEADD(DAY, -30, @endDate);
-- CTE 1: 实际工作人数统计
WITH StaffDailyHours AS (
-- 按员工和日期统计每日上班小时
SELECT
CAST(kq.day AS DATE) AS DDate,
kq.StaffName,
SUM(kq.上班小时) AS DailyHours
FROM KQ破碎考勤流水视图 kq
WHERE kq.day BETWEEN @startDate AND @endDate
GROUP BY CAST(kq.day AS DATE), kq.StaffName
),
StaffHoursWithShift AS (
-- 关联班次信息,确定员工所属班次
SELECT
s.DDate,
s.StaffName,
s.DailyHours,
c.绩效班次 AS Shift
FROM StaffDailyHours s
INNER JOIN 搬运破碎工班次表_明细 b
ON s.StaffName = b.员工姓名
AND b.工作品种 != '废带分拣工'
INNER JOIN 破碎班次表_明细 c
ON b.所属班别 = c.班次
AND s.DDate BETWEEN c.所属日期 AND c.结束日期
),
ActualWork AS (
-- 按日期和班次汇总人数和总小时数
SELECT
DDate,
Shift,
COUNT(DISTINCT StaffName) AS ActualWorkNumber,
SUM(DailyHours) AS TotalStaffHours
FROM StaffHoursWithShift
GROUP BY DDate, Shift
),
-- CTE 2: 生产检验单净重统计(按部门拆分)
ProductionWeight AS (
SELECT
CAST(p.生产日期 AS DATE) AS DDate,
c.绩效班次 AS Shift,
SUM(CASE WHEN c.所属部门 = '废带破碎部' THEN m.净重/1000 ELSE 0 END) AS 破带重量,
SUM(CASE WHEN c.所属部门 = '废膜破碎部' THEN m.净重/1000 ELSE 0 END) AS 破膜重量,
SUM(CASE WHEN c.所属部门 = '原料过料' THEN m.净重/1000 ELSE 0 END) AS 过铁重量
FROM 破碎生产检验单_主表 p
INNER JOIN 破碎生产检验单原料标签_明细 m
ON p.ExcelServerRCID = m.ExcelServerRCID
INNER JOIN 搬运破碎工班次表_明细 b
ON m.称重操作人 = b.员工姓名
INNER JOIN 破碎班次表_明细 c
ON p.班次 = c.班次
AND CAST(p.生产日期 AS DATE) BETWEEN c.所属日期 AND c.结束日期
WHERE p.生产日期 BETWEEN @startDate AND @endDate
GROUP BY CAST(p.生产日期 AS DATE), c.绩效班次
),
-- CTE 3: 破碎计时工作记录统计
TimingWork AS (
SELECT
CAST(t.开始时间 AS DATE) AS DDate,
t.班次 AS Shift,
t.任务类型,
t.计时人数,
t.计时时数,
t.单价
FROM 破碎计时工作记录表_主表 t
WHERE t.开始时间 BETWEEN @startDate AND @endDate
)
-- 最终结果:合并考勤人数与破带/破膜/过铁重量及对应单价,计时信息
SELECT
a.DDate AS DDate,
a.Shift AS Shift,
a.ActualWorkNumber AS ActualWorkNumber,
COALESCE(p.破带重量, 0) AS BreakingStrapWeight,
COALESCE(pd.单价, 0) AS BreakingStrapBasicUnitPrice,
-- 破带实际单价计算
COALESCE(pd.单价 * COALESCE(pd_percent.百分比/100, 1), 0) AS BreakingStrapActualUnitPrice,
-- 破带绩效计算
COALESCE(pd.单价, 0) * COALESCE(pd_percent.百分比/100, 1) * COALESCE(p.破带重量, 0) AS BreakingStrapPerformance,
COALESCE(p.破膜重量, 0) AS BreakingFilmWeight,
COALESCE(pm.单价, 0) AS BreakingFilmBasicUnitPrice,
-- 破膜实际单价计算
COALESCE(pm.单价 * COALESCE(pm_percent.百分比/100, 1), 0) AS BreakingFilmActualUnitPrice,
-- 破膜绩效计算
COALESCE(pm.单价, 0) * COALESCE(pm_percent.百分比/100, 1) * COALESCE(p.破膜重量, 0) AS BreakingFilmPerformance,
COALESCE(p.过铁重量, 0) AS OverIronWeight,
pt.单价 AS OverIronBasicUnitPrice,
-- 过铁实际单价计算
pt.单价 * COALESCE(pt_percent.百分比/100, 1) AS OverIronActualUnitPrice,
-- 过铁绩效计算
COALESCE(pt.单价, 0) * COALESCE(pt_percent.百分比/100, 1) * COALESCE(p.过铁重量, 0) AS OverIronPerformance,
-- 计时类型、计时人数、计时时数、计时单价
COALESCE(tw.任务类型, CAST('无' AS VARCHAR)) AS TaskType,
COALESCE(tw.计时人数, 0) AS TimingPeople,
COALESCE(tw.计时时数, 0) AS TimingHours,
COALESCE(tw.单价, 0) AS TimingSalary,
-- 计算总工资
(COALESCE(pd.单价, 0) * COALESCE(pd_percent.百分比/100, 1) * COALESCE(p.破带重量, 0))+
(COALESCE(pm.单价, 0) * COALESCE(pm_percent.百分比/100, 1) * COALESCE(p.破膜重量, 0))+
(COALESCE(pt.单价, 0) * COALESCE(pt_percent.百分比/100, 1) * COALESCE(p.过铁重量, 0))+
COALESCE(tw.单价, 0)
AS TotalSalary,
COALESCE(a.TotalStaffHours, 0) + COALESCE(tw.计时时数, 0) AS DutyPersonnelTotalWorkHours,
-- 最终结果字段修改
COALESCE(
(
(COALESCE(pd.单价, 0) * COALESCE(pd_percent.百分比/100, 1) * COALESCE(p.破带重量, 0)) +
(COALESCE(pm.单价, 0) * COALESCE(pm_percent.百分比/100, 1) * COALESCE(p.破膜重量, 0)) +
(COALESCE(pt.单价, 0) * COALESCE(pt_percent.百分比/100, 1) * COALESCE(p.过铁重量, 0)) +
COALESCE(tw.单价, 0)
)
/ NULLIF(COALESCE(a.TotalStaffHours, 0), 0), -- 分母零值防御
0 -- 默认值
) AS DutyPersonnelEveryHourSalary
FROM ActualWork a
LEFT JOIN ProductionWeight p
ON a.DDate = p.DDate AND a.Shift = p.Shift
-- 匹配破带单价(类型=废带破碎部)
LEFT JOIN 破碎类型单价表_明细 pd
ON pd.类型 = '废带破碎部'
AND COALESCE(p.破带重量, 0) >= pd.最小
AND COALESCE(p.破带重量, 0) < pd.最大
-- 匹配破膜单价(类型=废膜破碎部)
LEFT JOIN 破碎类型单价表_明细 pm
ON pm.类型 = '废膜破碎部'
AND COALESCE(p.破膜重量, 0) >= pm.最小
AND COALESCE(p.破膜重量, 0) < pm.最大
-- 匹配过铁单价(类型=原料过铁)
LEFT JOIN 破碎类型单价表_明细 pt
ON pt.类型 = '原料过铁'
AND COALESCE(p.过铁重量, 0) >= pt.最小
AND COALESCE(p.过铁重量, 0) < pt.最大
-- 人数百分比匹配
LEFT JOIN 破碎类型人数百分比 pd_percent
ON pd_percent.类型 = '废带破碎部'
AND pd_percent.人数 = a.ActualWorkNumber
LEFT JOIN 破碎类型人数百分比 pm_percent
ON pm_percent.类型 = '废膜破碎部'
AND pm_percent.人数 = a.ActualWorkNumber
LEFT JOIN 破碎类型人数百分比 pt_percent
ON pt_percent.类型 = '原料过铁'
AND pt_percent.人数 = a.ActualWorkNumber
-- 计时工作记录数据匹配
LEFT JOIN TimingWork tw
ON a.DDate = tw.DDate AND a.Shift = tw.Shift
ORDER BY a.DDate, a.Shift;