【问题标题】:Aggregate functions based on current Row value基于当前行值的聚合函数
【发布时间】:2021-10-30 20:11:15
【问题描述】:

我正在处理类似于以下的数据,

week | product | sale    
1    | ABC     | 2
1    | ABC     | 1
2    | ABC     | 1
3    | ABC     | 5
4    | ABC     | 1
2    | DEF     | 5

假设这是我的 Orders 表,名为 tblOrders。现在,在每一行中,我想汇总该产品上周的总销售额 - 例如,如果我在产品“ABC”的第 2 周,我需要显示产品 ABC 第 1 周的总销售额。因此,输出应如下所示,

week | product | sale    | ProductPreviousWeekSales
1    | ABC     | 2       | 0
1    | ABC     | 1       | 0
2    | ABC     | 1       | 3
3    | ABC     | 5       | 1
4    | ABC     | 1       | 5
2    | DEF     | 5       | 0

我最初认为我可以使用聚合和窗口函数来解决这个问题,但看起来并非如此。我的另一个想法是使用条件聚合 - 类似于 sum(case when x=currentRow.x then sale else 0 end),但这也行不通。

这是上述示例的 SQLFiddle - http://sqlfiddle.com/#!18/890b7/2

注意:我需要计算过去 4 周的相似值,因此尽量避免将其作为子查询或多个连接(如果可能)执行,因为我正在使用的数据集非常大,并且不想在尝试合并此更改时增加太多性能开销。

【问题讨论】:

  • 您确定要在结果中保留 1|ABC 的两行吗?难道您不想在累积销售中排一排吗?如果您想在结果中保留多行和产品,并且我将 3|DEF|1 和 3|DEF|2 添加到您的数据中,那么我想这两个都将显示前一周的销售量 5?
  • @ThorstenKettner 是的,我需要保留 1|ABC 的两条记录。您是对的,如果添加 3|DEF|1 和 3|DEF|2,则两者都应该将上周的总销售额显示为 5。
  • @TimBiegeleisen 完成。图像现在替换为文本。
  • 正如 Thorson 所说,如果您想要该产品在该周的总销售额,那么您的输出没有意义,那么您的前 2 个结果将是与销售额 3 的一行。否则这没有意义。您也许可以使用 LAG 窗口函数来获取前几周的销售额

标签: sql sql-server sql-server-2019


【解决方案1】:

免责声明

不幸的是,我在下面显示的查询在 SQL Server 中不起作用。在 SQL Server 版本 2019 之前,DBMS 缺乏对查询工作所必需的 RANGE 子句的完全支持。在 SQL Server 中运行查询会导致

Msg 4194 Level 16 State 1 Line 1 RANGE 仅支持 UNBOUNDED 和 CURRENT ROW 窗口框架分隔符。

我不会删除此答案,因为这是标准 SQL,并且该方法可能会对未来的读者有所帮助。它在很多 DBMS 中运行良好,也许未来版本的 SQL Server 也能够处理这个问题。我添加了演示以显示它在 PostgreSQL、MySQL 和 Oracle 中运行,但在 SQL Server 2019 中失败。


原始答案

您在小提琴 (select a.*, sum(sale) over(partition by product) ProductPreviousWeekSales from tblOrder a) 中显示的查询只是缺少适当的窗口子句。当您在这里处理关系时(每个产品和每周超过一行),这需要是一个RANGE 子句:

select a.*,
  sum(sale) over(partition by product 
                 order by week range between 1 preceding and 1 preceding
                ) as ProductPreviousWeekSales
from tblOrder a
order by product, week;

(如果您想看到零而不是 NULL,请使用 COALESCE。)

演示:

【讨论】:

  • 是sql server还是mysql?
  • @Ketan Kotak:这是标准 SQL。我在演示中使用了 MySQL 8 引擎,因为未知原因无法在 SQL Server 中运行。但是这个查询在 SQL Server、Oracle、PostgreSQL 中应该能很好地工作……你可以命名它:-)
  • 好的。它没有在sql server中运行。但我很好奇,因为与临时表或 cte 相比,它的语法很简单
  • @Ketan Kotak:感谢您的回复。问题仅仅是我根本无法让 dfiddle.uk 中的 SQL Server 工作。今天它起作用了。不幸的是,这表明我的查询没有在 SQL Server 中运行。我收到“消息 4194 Level 16 State 1 RANGE 仅支持 UNBOUNDED 和 CURRENT ROW 窗口框架分隔符”。这意味着我的解决方案不适用于 OP。 SQL Server 缺少一项基本功能,我们只能希望未来的版本能够弥补这一差距。
  • 好的,谢谢。我在 mysql 中越来越少地使用 SQL 服务器。但我猜 MySQL 是一天比一天强大。
【解决方案2】:

您需要从 TblOrders 中选择两次。一次,按周和产品分组并对销售额求和,第二次,对 TblOrders 进行逐行扫描,将其与同一产品和周偏移量为 1 的分组查询左连接: 如果连接失败,则连接分组查询的sales 值返回NULL。您可以使用 COALESCE() 输入 0 而不是 NULL,但 ISNULL() 有可能更快,因为它有固定数量的参数,而 COALESCE() 有一个可变参数列表,这是有一定成本的。

WITH
tblorders(wk,product,sales) AS (
            SELECT 1,'ABC',2
  UNION ALL SELECT 1,'ABC',1
  UNION ALL SELECT 2,'ABC',1
  UNION ALL SELECT 3,'ABC',5
  UNION ALL SELECT 4,'ABC',1
  UNION ALL SELECT 2,'DEF',5
)
,
grp AS (
  SELECT
    wk
  , product
  , SUM(sales) AS sales
  FROM tblorders
  GROUP BY
    wk
  , product
)
SELECT
    o.wk
  , o.product
  , o.sales
  , ISNULL(g.sales,0) AS productpreviousweeksales
FROM tblorders o
LEFT
JOIN grp       g
  ON o.wk - 1 = g.wk
 AND o.product= g.product
ORDER BY 2,1
;
 wk | product | sales | productpreviousweeksales 
----+---------+-------+--------------------------
  1 | ABC     |     2 |                        0
  1 | ABC     |     1 |                        0
  2 | ABC     |     1 |                        3
  3 | ABC     |     5 |                        1
  4 | ABC     |     1 |                        5
  2 | DEF     |     5 |                        0

【讨论】:

    【解决方案3】:

    您可以通过以下方式进行操作

    ; WITH cteorder AS
           (
                SELECT DISTINCT product, week FROM dbo.tblOrder
           )
           
           SELECT 
    cte.*,
    SUM(ISNULL(b.sale,0))  ProductPreviousWeekSales
    from tblOrder a
    INNER JOIN cteorder cte ON  cte.product = a.product AND cte.week = a.week
    LEFT JOIN dbo.tblOrder b ON b.product = cte.product AND b.week = (a.week-1)
    GROUP BY cte.product,
             cte.week
    

    你可以从:Fiddle

    【讨论】:

      【解决方案4】:

      这是一种方法,它首先将您的表格汇总到单独的 CTE 中,然后使用 LAG 查找每周和产品的前一周金额:

      WITH cte AS (
          SELECT week, product,
                 LAG(SUM(sale)) OVER (PARTITION BY product ORDER BY week) AS lag_total_sales
          FROM yourTable
          GROUP BY week, product
      )
      
      SELECT t1.week, t1.product, t1.sale,
             COALESCE(t2.lag_total_sales, 0) AS ProductPreviousWeekSales
      FROM yourTable t1
      INNER JOIN cte t2
          ON t2.week = t1.week AND
             t2.product = t1.product
      ORDER BY
          t1.product,
          t1.week;
      

      Demo

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-07-03
        • 2011-10-12
        • 1970-01-01
        相关资源
        最近更新 更多