【问题标题】:MAX() OVER PARTITION BY not working as intendedMAX() OVER PARTITION 未按预期工作
【发布时间】:2020-09-22 04:26:59
【问题描述】:

当我尝试获取包含一组记录的字段的 MAX 值时遇到了一些问题,我希望你们中的一些人可以帮助我找出我做错了什么。
我正在尝试获取订单中最昂贵的商品的 ID。
鉴于此查询:

SELECT 
       orderHeader.orderKey, orderLines.lineKey, orderLines.itemKey, orderLines.OrderedQty, 
       orderLines.price, (orderLines.price*orderLines.OrderedQty) as LinePrice, 
       ROW_NUMBER() OVER(PARTITION BY orderHeader.orderKey ORDER BY orderLines.lineKey asc) AS [ItemLineNum], 
       ROW_NUMBER() OVER(PARTITION BY orderHeader.orderKey ORDER BY (orderLines.price*orderLines.OrderedQty) DESC) AS [LineMaxPriceNum],
       max(orderLines.itemKey) OVER (PARTITION BY orderHeader.orderKey ORDER BY (orderLines.price*orderLines.OrderedQty) DESC) as [MaxPriceItem]
FROM
       orderHeader inner join orderLines on orderHeader.orderKey=orderLines.orderKey

我得到了这个结果: Results of Query

抱歉,我不允许在帖子中直接插入图片,我会尝试使用 sn-ps 来格式化表格。
这些是结果

| orderKey | lineKey | itemKey | OrderedQty | Price | LinePrice | ItemLineNum | LineMaxPriceNum | MaxPriceItem |
|----------|---------|---------|------------|-------|-----------|-------------|-----------------|--------------|
| 176141   | 367038  | 15346   | 3          | 1000  | 3000      | 2           | 1               | 15346        |
| 176141   | 367037  | 15159   | 2          | 840   | 1680      | 1           | 2               | 15346        |
| 176141   | 367039  | 15374   | 5          | 100   | 500       | 3           | 3               | 15374        |

如您所见,对于同一个“orderKey”,我有三行(lineKey),每行都有不同的项目(itemKey)、不同的数量、不同的价格和不同的总成本(LinePrice)。 我希望在 MaxPriceItem 列中具有较高“LinePrice”的项目的键,但结果是错误的。三行应该显示 15346 是最昂贵的项目,但最后一行是不正确的,我不明白为什么。此外,由同一表达式 (LineMaxPriceNum) 划分的 ROW_NUMBER 给了我正确的顺序。

如果我在 MAX 中更改 ORDER BY 的表达式,像这样(按“OrderedQty”排序):

SELECT 
       orderHeader.orderKey, orderLines.lineKey, orderLines.itemKey, orderLines.OrderedQty, 
       orderLines.price, (orderLines.price*orderLines.OrderedQty) as LinePrice, 
       ROW_NUMBER() OVER(PARTITION BY orderHeader.orderKey ORDER BY orderLines.lineKey asc) AS [ItemLineNum], 
       ROW_NUMBER() OVER(PARTITION BY orderHeader.orderKey ORDER BY (orderLines.price*orderLines.OrderedQty) DESC) AS [LineMaxPriceNum],
       max(orderLines.itemKey) OVER (PARTITION BY orderHeader.orderKey ORDER BY orderLines.OrderedQty DESC) as [MaxPriceItem]
FROM
       orderHeader inner join orderLines on orderHeader.orderKey=orderLines.orderKey

然后就可以了:

| orderKey | lineKey | itemKey | OrderedQty | Price | LinePrice | ItemLineNum | LineMaxPriceNum | MaxPriceItem |
|----------|---------|---------|------------|-------|-----------|-------------|-----------------|--------------|
| 176141   | 367038  | 15346   | 3          | 1000  | 3000      | 2           | 1               | 15374        |
| 176141   | 367037  | 15159   | 2          | 840   | 1680      | 1           | 2               | 15374        |
| 176141   | 367039  | 15374   | 5          | 100   | 500       | 3           | 3               | 15374        |

“OrderedQty”最高的商品为 15374,因此结果正确。

如果我再次更改 MAX 中 ORDER BY 的表达式,如下所示(按“价格”排序):

SELECT 
       orderHeader.orderKey, orderLines.lineKey, orderLines.itemKey, orderLines.OrderedQty, 
       orderLines.price, (orderLines.price*orderLines.OrderedQty) as LinePrice, 
       ROW_NUMBER() OVER(PARTITION BY orderHeader.orderKey ORDER BY orderLines.lineKey asc) AS [ItemLineNum], 
       ROW_NUMBER() OVER(PARTITION BY orderHeader.orderKey ORDER BY (orderLines.price*orderLines.OrderedQty) DESC) AS [LineMaxPriceNum],
       max(orderLines.itemKey) OVER (PARTITION BY orderHeader.orderKey ORDER BY orderLines.price DESC) as [MaxPriceItem]
FROM
       orderHeader inner join orderLines on orderHeader.orderKey=orderLines.orderKey

然后和第一个例子一样,结果是错误的:

| orderKey | lineKey | itemKey | OrderedQty | Price | LinePrice | ItemLineNum | LineMaxPriceNum | MaxPriceItem |
|----------|---------|---------|------------|-------|-----------|-------------|-----------------|--------------|
| 176141   | 367038  | 15346   | 3          | 1000  | 3000      | 2           | 1               | 15346        |
| 176141   | 367037  | 15159   | 2          | 840   | 1680      | 1           | 2               | 15346        |
| 176141   | 367039  | 15374   | 5          | 100   | 500       | 3           | 3               | 15374        |

价格最高的商品是 15346,但最后一条记录的 MAX 没有显示。

我在这里缺少什么?为什么我会得到那些不同的结果?

对不起,如果格式没有正确完成,这是我在这里的第一个问题,我已经尽力了。

提前感谢您能给我的任何帮助。

【问题讨论】:

    标签: sql sql-server join max window-functions


    【解决方案1】:

    接受的答案为原始问题提供了一个合理的替代解决方案,但并没有真正解释为什么max() 函数似乎工作不一致。 (剧透警告,您实际上可以按照最初的意图使用max(),只需稍加调整即可。)

    您必须了解聚合函数实际上是在分区内的窗口框架上运行的。默认情况下,框架是整个分区。因此,像 max()sum() 这样的聚合操作确实在整个分区上运行,就像你假设的那样。此默认规范定义为RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING。这只是意味着无论我们在哪个记录上,max() 都会一直回溯到分区中的第一行,并一直向前到分区中的最后一行,以便计算值。

    但有一个隐蔽的陷阱:向分区添加ORDER BY 子句默认框架规范更改为RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW。这意味着无论我们在什么记录上,max() 都会一直回溯到分区中的第一行,然后只回溯到当前行,以便计算值。您可以在上一个示例中清楚地看到这一点(稍微简化了一点):

    SELECT orderKey, itemKey, price,
       ROW_NUMBER() OVER(PARTITION BY orderKey ORDER BY price DESC) AS [PartitionRowNum],
       MAX(itemKey) OVER (PARTITION BY orderKey ORDER BY price DESC) as [MaxPriceItem]
    FROM orders
    

    结果/说明:

    | orderKey | itemKey | Price | PartitionRowNum | MaxPriceItem | Commentary             |
    |----------|---------|-------|-----------------|--------------|------------------------|
    | 176141   | 15346   | 1000  | 1               | 15346        | Taking max of rows 1-1 |
    | 176141   | 15159   | 840   | 2               | 15346        | Taking max of rows 1-2 |
    | 176141   | 15374   | 100   | 3               | 15374        | Taking max of rows 1-3 |
    

    解决方案

    我们可以通过在分区中添加RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING来明确指出窗框规范,如下所示:

    SELECT orderKey, itemKey, price,
       ROW_NUMBER() OVER(PARTITION BY orderKey ORDER BY price DESC) AS [PartitionRowNum],
       MAX(itemKey) OVER (PARTITION BY orderKey ORDER BY price DESC RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) as [MaxPriceItem]
    FROM orders
    

    结果/说明:

    | orderKey | itemKey | Price | PartitionRowNum | MaxPriceItem | Commentary             |
    |----------|---------|-------|-----------------|--------------|------------------------|
    | 176141   | 15346   | 1000  | 1               | 15374        | Taking max of rows 1-3 |
    | 176141   | 15159   | 840   | 2               | 15374        | Taking max of rows 1-3 |
    | 176141   | 15374   | 100   | 3               | 15374        | Taking max of rows 1-3 |
    

    【讨论】:

      【解决方案2】:

      我正在尝试获取订单中最昂贵的商品的 ID。

      你误解了order by子句对窗口函数的作用;它旨在定义窗口框架,而不是比较值; max() 为您提供在窗口框架内作为参数给出的表达式的最大值。

      另一方面,您想要最昂贵订单行的itemKey。我认为first_value() 会做你想做的事:

      first_value(orderLines.itemKey) over(
          partition by orderHeader.orderKey 
          order by orderLines.price * orderLines.OrderedQty desc
      ) as [MaxPriceItem]
      

      【讨论】:

      • 非常感谢您的回答,它成功了!但是,我仍然不明白我的 sql 有什么问题,为什么如果我按 OrderedQty 订购而不是按价格或数量 * 价格订购它会起作用。我认为 ORDER BY 是在窗口框架内设置顺序,所以我试图将窗口框架设置为 orderHeader.orderKey 并按这些概念对其进行排序。无论如何,我会阅读更多关于此的内容,再次感谢您的帮助!编辑:我试图支持你的答案,但我太新了,它说“它正在被记录但没有显示”
      • @Vic:你不能用max() 做到这一点。当您更改order by 时,它只是偶然起作用。你不能投票(你需要 15 个代表),但是,作为问题的提问者,你可以accept the answer
      • @Vic - 你现在有超过 15 个代表 :-)
      • @GMB 再次感谢。正如乔尔所指出的,由于我有超过 15 个代表,我已经能够支持你的答案,我现在会接受它,因为它在第一次尝试时就解决了我的问题;)
      猜你喜欢
      • 1970-01-01
      • 2013-11-27
      • 1970-01-01
      • 2018-09-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-03-21
      • 1970-01-01
      相关资源
      最近更新 更多