如何对不同值求和 OVER (PARTITION BY DISTINCT)

Posted

技术标签:

【中文标题】如何对不同值求和 OVER (PARTITION BY DISTINCT)【英文标题】:How to sum OVER (PARTITION BY DISTINCT) for Distinct Values 【发布时间】:2021-05-14 08:06:47 【问题描述】:

我正在寻找一种在 SQL Server 中使用 Partition by Over 的巧妙方法。

我在 SQL Server 中有 3 个表(下面的所有 *_id 列都只是伪主键)

PO (po_id, po_no); PO_ITEM (po_item_id, po_id, po_item_no, 数量); // 存储 PO ITEM 的订购数量 PO_ITEM_DELY (po_item_dely_id, po_item_id, dely_no, dely_qty); // 存储每个交货编号的每个 PO 项目的交货数量。
select
    po.po_no, pt.po_item_no, pt.qty, pd.dely_no, pd.dely_qty
from 
    PO
inner join 
    PO_ITEM pt on pt.po_id = po.po_id
inner join 
    PO_ITEM_DELY pd on pd.po_item_id = pt.po_item_id
where 
    po.po_no = 'PO1'

此 SQL 查询结果供参考:

po_no po_item_no qty dely_no dely_qty
PO1 PoI11 300 1 210
PO1 PoI11 300 2 48
PO1 PoI11 300 3 55
PO1 PoI12 100 1 100
PO1 PoI13 250 1 150
PO1 PoI13 250 2 100

因此在本例中,PO1 的总订购数量为 650,但总交付数量为 663。

想要的结果:

po_no OrdPOQty DelyPOQty po_item_no OrdItemQty delyItemQty dely_no dely_qty
PO1 650 663 PoI11 300 313 1 210
PO1 650 663 PoI11 300 313 2 48
PO1 650 663 PoI11 300 313 3 55
PO1 650 663 PoI12 100 100 1 100
PO1 650 663 PoI13 250 250 1 150
PO1 650 663 PoI13 250 250 2 100

现在我可以使用子查询来完成这项任务:

with poOrdQtyDtl as (
-- Form a Join between PO and PO_ITEM to get Total Ordered Qty Per PO
select
    po.po_id,
    po.po_no,
    sum(pt.qty) OrdPoQty
from PO
inner join PO_ITEM pt on pt.po_id = po.po_id
group by po.po_id, po.po_no
)
select
    poOrdQtyDtl.po_no [PO No.],
    poOrdQtyDtl.OrdPoQty [Ordered Qty For PO],
    sum(itemDely.currDelyQty) over (partition by poOrdQtyDtl.po_no) as [Delivered Qty For Po],
    itemDely.po_item_no [Item No.],
    itemDely.OrdItemQty [Ordred Item Qty],
    itemDely.DelItemQty [Delivered Item Qty],
    itemDely.dely_no [Dely No.],
    itemDely.currDelyQty [Item Qty Delivered in Current Dely]
from poOrdQtyDtl
inner join (
-- Join PO_ITEM and PO_ITEM_DELY to get Item Quantity details
select
    pt.po_id,
    pt.po_item_id,
    pt.po_item_no,
    pt.qty OrdItemQty,
    sum(pd.dely_qty) over (partition by pt.po_item_no) DelItemQty,
    pd.dely_no,
    pd.dely_qty currDelyQty
from PO_ITEM pt
inner join PO_ITEM_DELY pd on pd.po_item_id = pt.po_item_id
) itemDely on itemDely.po_id = poOrdQtyDtl.po_id
WHERE poOrdQtyDtl.po_no = 'PO1'
;

但是,我只是想知道是否有更巧妙地应用over partition by 子句来进行求和的更简单方法。主要挑战在于下面的查询,因为我不能在partition by 子句中使用distinct

select
    po.po_no,
    -- sum (pt.qty) over (partition by distinct po.po_no, pt.po_item_no) TotPoQOrd, -- INCORRECT
    sum (pt.qty) over (partition by po.po_no, pt.po_item_no) TotPoQOrd,
    sum(pd.dely_qty) over (partition by po.po_no) TotPoQDely,
    pt.po_item_no,
    pt.qty,
    sum(pd.dely_qty) over (partition by po.po_no, pt.po_item_no) TotItemQ,
    pd.dely_no,
    pd.dely_qty
from PO
inner join PO_ITEM pt on pt.po_id = po.po_id
inner join PO_ITEM_DELY pd on pd.po_item_id = pt.po_item_id
where po.po_no = 'PO1'

【问题讨论】:

PARTITION BY基本上就是GROUP BYDISTINCTPARTITION BY子句之后就不需要了。 “所以在这个例子中,PO1 的总订购数量是 650,但总交付数量是 663。” - 我可能很厚,但是这些数字在哪里从哪里来? PO1 有 3 项:PoI11 (Ordered qty = 300), PoI12 (Ordered qty = 100), PoI13 (Ordered qty = 250) 所以总共是 650。但是如果在表中添加 dely_qty 列1 然后是 663。基本上 PoI11 在第 1、2、3 号交付中已经交付了 3 次。PoI12 仅交付了 1 次。 PoI13 交付了 2 次。 【参考方案1】:

使用多种不同的窗口规格来解决这个问题:

    select
      x.po_no, 

      x.OrdPOQty,
      SUM(pd.dely_qty) OVER(PARTITION BY x.po_no) as DelyPOQty,
      
      x.po_item_no,

      x.OrdItemQty,
      SUM(pd.dely_qty) OVER(PARTITION BY x.po_no, x.po_item_no) as DelyItemQty,
       
      x.qty, 
      pd.dely_no, 
      pd.dely_qty
    from 
      ( 
        SELECT 
          po.po_id, po.po_no, pt.po_item_id, pt.po_item_no, pt.qty, 
          SUM(pt.qty) OVER(PARTITION BY po.po_no) as OrdPOQty, 
          SUM(pt.qty) OVER(PARTITION BY po.po_no, pt.po_item_no) as OrdItemQty
        FROM PO inner join PO_ITEM pt on pt.po_id = po.po_id
      ) x
      inner join PO_ITEM_DELY pd on pd.po_item_id = x.po_item_id
    where 
      x.po_no = 'PO1'

从技术上讲,partition by po_no 是不必要的,因为 where 子句可确保只有一个,但我将其保留以防您想扩展查询以考虑多个 po_no

如果你永远只查询一个po_no

    select
      x.po_no, 

      x.OrdPOQty,
      SUM(pd.dely_qty) OVER() as DelyPOQty,
      
      x.po_item_no,

      x.OrdItemQty,
      SUM(pd.dely_qty) OVER(PARTITION BY x.po_item_no) as DelyItemQty,
       
      x.qty, 
      pd.dely_no, 
      pd.dely_qty
    from 
      ( 
        SELECT 
          po.po_id, po.po_no, pt.po_item_id, pt.po_item_no, pt.qty, 
          SUM(pt.qty) OVER(PARTITION BY po.po_no) as OrdPOQty, 
          SUM(pt.qty) OVER(PARTITION BY po.po_no, pt.po_item_no) as OrdItemQty
        FROM PO inner join PO_ITEM pt on pt.po_id = po.po_id
      ) x
      inner join PO_ITEM_DELY pd on pd.po_item_id = x.po_item_id
    where 
      x.po_no = 'PO1'

想知道是否有一种更简单的方法来通过更巧妙地应用 over partition by 子句来进行求和

嗯,基本上使用基本形式,你最终会得到 N 行重复,你可以计算重复次数并将组中值的总和除以组的重复次数,所以你在求和值是其原始值的三分之一,但对于相同的总和重复了 3 次。但我确实觉得这比仅在没有笛卡尔积的水平上进行求和和计数会造成更大的混乱,然后结果只是被执行并重复..

或者我们可以只计算一件物品,假设每件物品至少有一个交付 #1:

select
  po.po_no, 

  SUM(CASE WHEN pd.dely_no = 1 THEN pt.qty ELSE 0 END) OVER(PARTITION BY po.po_no) as OrdPOQty,
  SUM(pd.dely_qty) OVER(PARTITION BY po.po_no) as DelyPOQty,
  
  pt.po_item_no,

  SUM(CASE WHEN pd.dely_no = 1 THEN pt.qty ELSE 0 END) OVER(PARTITION BY po.po_no, pt.po_item_no) as OrdItemQty,
  SUM(pd.dely_qty) OVER(PARTITION BY po.po_no, pt.po_item_no) as DelyItemQty,
   
  pt.qty, 
  pd.dely_no, 
  pd.dely_qty
from 
  PO
  inner join PO_ITEM pt on pt.po_id = po.po_id
  inner join PO_ITEM_DELY pd on pd.po_item_id = pt.po_item_id
where 
  po.po_no = 'PO1'

如果您添加另一个表,导致 pd.dely_no 每个 po/po+item 分区的重复值 1,那么您需要扩展 CASE 逻辑

【讨论】:

我在 where 子句中使用 po_no = 'PO1' 只是为了限制结果以显示 PO1 作为示例。实际上,我想将其扩展为为所有 PO 生成此文件。但是,建议的 SQL 不会产生正确的结果。我已经编辑了 SQL 以显示哪些列会产生不正确的结果。问题是,如果一个项目有多个交付,那么该行会重复。例如PoI11 订购了 300 件,但被送达 3 次,送达数量 = 210 + 48 + 55 = 313。这会导致 PO_ITEM.qty 行重复,因此会错误添加 3 次。 已编辑,以便在加入交付之前完成订单总和,这就是我理解的导致 po/item 行重复的原因 感谢您的帮助。我接受了这个答案,因为它确实解决了问题。然而,我确实知道我可以使用原始帖子中所述的子查询来解决这个问题。 正如答案末尾所指出的,您实际上是在问“我如何在外层解决这个我导致不早做某事的问题” - 你有一个采购订单和 3 个项目 - 每个采购订单都很好且易于总结,但使用 SUM()OVER() 保留细节。但是然后你将这些项目(唯一)加入交付中,它们变得不唯一 - 所以你'已经导致数据库不得不重复它们。通过在造成重复后寻找再次删除它们的方法,数据库必须做更多的工作来删除引入的数据。 这当然可以,但是有点像穿过泥巴走进屋子,然后想知道打扫屋子的最佳方法——早脱鞋意味着没有帖子处理工作要做清理泥浆,而且比较容易。有很多方法可以修饰它 - 例如使用 WITH 或将 SUM OVER 烘焙到视图中,然后查询与交付表连接的视图

以上是关于如何对不同值求和 OVER (PARTITION BY DISTINCT)的主要内容,如果未能解决你的问题,请参考以下文章