多表查询求和重复问题解决————且一同解决了int类型减去null类型出现null的问题!!!

本文介绍了一种解决SQL中A、B、C三表联查时A表数据重复的问题,通过子查询及多层SELECT DISTINCT实现正确求和,避免了数据重复带来的错误结果,适用于一对多及一对一表关系。

有A、B、C三表 表与表关系为 A–>B 为一对多 A–>C为 一对一 三表联查求和 会出现 A表数据重复问题 解决办法 子查询 !! 三层 SELECT DISTINCT 亲测 有效

    SELECT DISTINCT (q.id),q.code,q.name,q.batch,SUM(q.total),(ISNULL(SUM(q.total),0)-ISNULL(q.total2, 0))AS total3 FROM
    (SELECT DISTINCT (w.id),w.code,w.name,w.batch,w.num as total,sum(w.num2)as total2  FROM(
    SELECT DISTINCT (so.id)AS oid, m.id,m.code,m.name,so.batch,so.num as num,oo.num as num2
    FROM base_storage_outrecord AS so
    LEFT JOIN base_material AS m
    ON so.mid = m.id
    LEFT JOIN busness_product_order_outdata AS oo
    ON so.orderno = oo.orderno 
    AND so.batch = oo.bacth
    AND so.mid = oo.mid
    WHERE  so.orderno = 1109987621 AND so.batch = 1)AS w
    GROUP BY w.id,w.code,w.name,w.batch,w.num)AS q
    GROUP BY  q.id,q.code,q.name,q.batch,q.total

在这里插入图片描述

<think>我们有一个CASE达式,它使用窗口函数(ROW_NUMBER, SUM)来计算一个称为“差距时间”的值。 具体逻辑: 1. 按三个字段分区:线组, 工序, 生产节拍。 2. 在每个分区内,按要素顺序降序排列(ORDER BY 要素顺序 DESC)。 3. 然后,ROW_NUMBER() OVER(...) = 1 示取每个分区的最后一行(因为按要素顺序降序,所以最大的要素顺序排第一,但ROW_NUMBER()从1开始,所以降序排列后第一行就是原始顺序的最后一行?这里注意:ROW_NUMBER()在降序排列后,序号1就是当前分区内要素顺序最大的那一行,也就是原始顺序中最后一行?实际上,这里按降序排列后,第一行就是组内要素顺序最大的那一行,也就是原始顺序的最后一行。所以,我们只对每个分组的最后一行进行计算,其他行则为null。 4. 对于每个分组的最后一行,计算差距时间 = (convert(int, 生产节拍) - 当前分组内所有行的要素时间之和) 注意:这里SUM(CAST(要素时间 AS INT)) OVER (PARTITION BY ...) 是对整个分区内的要素时间求和,然后减去这个和值。 但是,这里有一个问题:这个SUM窗口函数是独立于ROW_NUMBER的,它计算的是整个分区的要素时间总和。所以,对于分区内的每一行,这个SUM的值都是相同的(都是该分区所有要素时间的总和)。然后,在分区最后一行,我们用生产节拍(转换为整数)减去这个总和,得到差距时间。 因此,这个达式的作用是:在每个分组(线组、工序、生产节拍)内,只在最后一行(按要素顺序降序排列后的第一行)显示差距时间,其他行显示null。 注意:由于ROW_NUMBER() OVER (PARTITION BY ... ORDER BY 要素顺序 DESC) =1 只能有一行,所以每个分组只有一行有值。 但是,这里有一个潜在问题:如果同一个分组内有个相同的最大要素顺序,那么ROW_NUMBER()会按照排序顺序给一个唯一的序号(1,2,...),而只有序号为1的那一行会被计算。所以,如果存在并列的最大要素顺序,则只有第一行(按照窗口函数中的排序规则,如果没有指定其他排序列,则可能是不确定的)被计算,其他并列行不会被计算。 如果希望处理并列的情况,可以使用RANK()或DENSE_RANK(),但这里要求只有一行,所以ROW_NUMBER()是合适的,因为每个分组只标记一行。 现在,假设我们有一个,包含以下列:线组, 工序, 生产节拍, 要素顺序, 要素时间。 我们可以将整个查询写出来,假设名为ProductionData: 但是,请注意,这个CASE达式是一个列达式,所以它应该是一个查询的一部分。 示例查询: 假设我们想选择原始列,并添加这个“差距时间”列,我们可以这样写: ```sql SELECT [线组], [工序], [生产节拍], [要素顺序], [要素时间], CASE WHEN ROW_NUMBER() OVER ( PARTITION BY [线组], [工序], [生产节拍] ORDER BY [要素顺序] DESC ) = 1 THEN (CONVERT(INT, [生产节拍]) - SUM(CAST([要素时间] AS INT)) OVER ( PARTITION BY [线组], [工序], [生产节拍] )) ELSE NULL END AS [差距时间] FROM ProductionData; ``` 然而,需要注意的是:窗口函数SUM(CAST([要素时间] AS INT)) OVER (PARTITION BY [线组], [工序], [生产节拍]) 会为每个分区的每一行都计算一个总和。由于在同一个分区内,这个总和是相同的,所以对于最后一行,我们用生产节拍减去这个总和。 但是,这里有一个类型转换:生产节拍需要转换INT,要素时间也需要转换INT。如果这些字段本来就是整数,那么可以去掉CAST。 另外,如果生产节拍或者要素时间可能是NULL,那么需要处理NULL值,因为NULL参与计算会得到NULL。可以使用ISNULL或者COALESCE,但根据业务需求,如果为NULL应该如何处理?这里没有说明,所以假定数据都是非空的。 如果存在NULL,我们可以这样处理: SUM(CAST(ISNULL([要素时间], 0) AS INT)) OVER ... 或者将生产节拍也做ISNULL处理。 但原始查询没有做,所以我们先按照原始逻辑。 现在,解释一下这个查询: 它从ProductionData中选择数据,并添加了一个计算列“差距时间”。 对于每个分组(线组、工序、生产节拍),我们按照要素顺序降序排序,然后给每一行编号(1,2,3...)。 当编号为1(即组内最后一行)时,计算:生产节拍(整数) - 组内所有要素时间的总和,否则为NULL。 这个“差距时间”示的是:生产节拍与组内所有要素时间总和的差值。通常,生产节拍是指整个工序的标准时间,而要素时间是每个步骤的时间,所以这个差值可以理解为剩余时间或者差异时间。 但是,如果要素时间总和大于生产节拍,那么差距时间会是负数。 另外,注意:同一个分组内,要素时间的总和在整个分区中重复计算(每一行都计算一次),但是由于是相同的值,所以对性能可能有一定影响。不过,现代数据库对窗口函数优化较好。 现在,让我们考虑一个具体的例子: 线组: A, 工序: B, 生产节拍: 100 (转换为整数后) 该分组有三行数据: 行1: 要素顺序=1, 要素时间=20 行2: 要素顺序=2, 要素时间=30 行3: 要素顺序=3, 要素时间=40 按要素顺序降序排列:行3(3)、行2(2)、行1(1) 行3的ROW_NUMBER()=1(因为是第一个),所以计算:100 - (20+30+40) = 100-90=10 行2和行1的ROW_NUMBER()都不是1,所以为NULL。 因此,结果中只有行3的差距时间为10。 但是,这里有一个问题:我们的原始顺序可能并不是按要素顺序升序排列的,但窗口函数中的排序只影响ROW_NUMBER的顺序,不影响SUM(因为SUM是分区内所有行的和,与排序无关)。 所以,这个查询是可行的。 不过,我们也可以考虑使用其他方法,比如先计算每个分组的要素时间总和,然后使用子查询或公用达式,但这里使用窗口函数更为高效。 注意:ROW_NUMBER()的窗口(PARTITION BY和ORDER BY)和SUM的窗口(只有PARTITION BY)是不同的,因为ROW_NUMBER有排序而SUM没有。 另外,由于ROW_NUMBER()的排序和SUM的排序无关,所以不会影响SUM的结果。 因此,这个查询可以满足需求。 但是,如果数据量很大,窗口函数可能会消耗较资源,所以需要确保在相应的字段上有索引(例如,线组、工序、生产节拍等)。 最后,关于代码格式:由于这是一个单列达式,通常我们会将其放在SELECT子句中。如果我们需要过滤出只有差距时间的行,可以在外部查询中筛选,但这里不展开。 由于问题中没有要求完整的查询,只是要求解释这个CASE达式,所以我们可以给出上面的示例查询。 另外,如果生产节拍已经是整数,则不需要CONVERT。 下面,我们给出一个改进版本,处理可能的NULL值(如果业务需要): ```sql SELECT [线组], [工序], [生产节拍], [要素顺序], [要素时间], CASE WHEN ROW_NUMBER() OVER ( PARTITION BY [线组], [工序], [生产节拍] ORDER BY [要素顺序] DESC ) = 1 THEN (CONVERT(INT, ISNULL([生产节拍],0)) - SUM(CAST(ISNULL([要素时间],0) AS INT)) OVER ( PARTITION BY [线组], [工序], [生产节拍] )) ELSE NULL END AS [差距时间] FROM ProductionData; ``` 这样,如果生产节拍或要素时间为NULL,则视为0,避免整个达式为NULL。 但是,根据业务逻辑,是否应该视为0需要确认。有些情况下,不允许为NULL,则不需要。 所以,具体使用哪种,取决于数据情况和业务需求。 现在,根据要求,我们需要在最后提出相关问题。 注意:原始问题中已经给出了一个示例格式,所以我们在最后提出相关问题。 </think>以下是对您提供的 SQL 代码的详细解释和改进方案: ### 代码解释 这段 SQL 使用窗口函数计算每个分组(线组、工序、生产节拍)内的时间差值: 1. **分组逻辑** `PARTITION BY 线组, 工序, 生产节拍`:将数据按这三个字段分组 2. **排序逻辑** `ORDER BY 要素顺序 DESC` + `ROW_NUMBER() = 1`: 按要素顺序**降序排列**,取每组**最后一行**(原始顺序的末尾行) 3. **计算逻辑** 仅对每组最后一行计算: `差距时间 = 生产节拍(转整数) - 分组内所有要素时间的总和` 4. **结果输出** 非最后一行返回 `NULL` ### 改进代码 ```sql SELECT *, CASE WHEN ROW_NUMBER() OVER ( PARTITION BY 线组, 工序, 生产节拍 ORDER BY 要素顺序 DESC ) = 1 THEN TRY_CAST(生产节拍 AS INT) - SUM(TRY_CAST(要素时间 AS INT)) OVER ( PARTITION BY 线组, 工序, 生产节拍 ) ELSE NULL END AS 差距时间 FROM ProductionTable; ``` ### 关键改进说明: 1. **添加 `TRY_CAST` 安全转换** 避免因数据类型转换失败导致整个查询失败 ```sql TRY_CAST(生产节拍 AS INT) -- 安全转换为整数 ``` 2. **显式包含所有字段** `SELECT *, ...` 确保保留原始数据的所有列 3. **完整的引用** `FROM ProductionTable` 明确数据来源名 4. **处理边界情况**: - 当要素时间总和 > 生产节拍时:差距时间为**负数**(异常预警) - 分组内无数据时:返回 `NULL` - 数据转换失败时:返回 `NULL`(因 `TRY_CAST`) ### 典输出示例 | 线组 | 工序 | 生产节拍 | 要素顺序 | 要素时间 | 差距时间 | |------|------|----------|----------|----------|----------| | A1 | 焊接 | 60 | 1 | 15 | NULL | | A1 | 焊接 | 60 | 2 | 20 | NULL | | A1 | 焊接 | 60 | 3 | 25 | **0** | | B2 | 组装 | 100 | 1 | 40 | NULL | | B2 | 组装 | 100 | 2 | 70 | **-10** | > 最后一行计算:100 - (40+70) = -10(效率不足预警) ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值