【问题标题】:Sliding window functions in SQL Server, advanced calculationSQL Server 中的滑动窗口函数,高级计算
【发布时间】:2026-02-18 15:20:08
【问题描述】:

我有一个问题,例如用 C# 代码很容易解决,但我不知道如何在 SQL 查询中编写。

情况如下:假设我有一个包含 3 列的表 (ID, Date, Amount),这是一些数据:

ID  Date         Amount
-----------------------
1   01.01.2016    -500
2   01.02.2016    1000
3   01.03.2016    -200
4   01.04.2016     300
5   01.05.2016     500
6   01.06.2016    1000
7   01.07.2016    -100
8   01.08.2016     200

我想从表格中得到的结果是这样的(ID, Amount .... Order By Date):

ID  Amount
-----------------------
2    300
4    300
5    500
6    900
8    200

这个想法是将金额分配到分期付款,但问题是当负金额发挥作用时,您需要从最后一期中删除金额。我不知道我有多清楚,所以这里是一个例子:

假设我有 3 张发票,金额分别为 500、200、-300。

如果我开始分配这些发票,首先我分配金额 500,然后是 200。但是当我来到第三张 -300 时,我需要从最后一张发票中删除。在其他工作中 200 - 300 = -100,因此第二张发票的金额将消失,但仍有 -100 需要从第一张发票中减去。所以 500 - 100 = 400。我需要的结果是一行数据集(第一张发票金额为 400)

另一个例子是第一张发票的金额为负数(-500、300、500)。 在这种情况下,第一张 (-500) 发票将使第二张发票消失,并且从第三张发票中减去另外 200。所以结果将是:金额为 300 的第三张发票。

这有点像编程语言中的 Stack 实现,但我需要使用 SQL Server 中的滑动窗口函数来实现。

如果有人有任何想法,请分享。

谢谢。

【问题讨论】:

  • 为什么2amount 等于300。根据你的解释应该是500(=-500+100),或者不是?
  • 2的实际金额是1000,但实际应该是1000(2)-500(1)-200(3) = 300 负数需要减去前面的金额,但在这种情况下,第一个数量是负数,它必须从它之后的第一个正数中减去
  • 我认为第二行应该是4|300,第三行应该是5|500
  • 目前还不清楚你如何定义一个组(意味着我们如何知道在这里对项目进行分组?)。这涉及定义滑动窗口和窗口框架,以便我们可以根据需要计算值。
  • 看起来ID 值也被翻译了?这就是让你的问题一开始不容易理解的原因。

标签: sql sql-server database sliding-window


【解决方案1】:

我使用 TSQL 解决了它。但我认为这个任务也可以使用递归 CTE 解决。 我使用 ID 来查找上一行或下一行。

-- create and fill test table
CREATE TABLE Invoices(
  ID int,
  [Date] date,
  Amount float
)

INSERT Invoices(ID,Date,Amount) VALUES
(1,'20160101', -500),
(2,'20160201', 1000),
(3,'20160301', -200),
(4,'20160401',  300),
(5,'20160501',  500),
(6,'20160601', 1000),
(7,'20160701', -100),
(8,'20160801',  200)

我的解决方案

-- copy all the data into temp table
SELECT *
INTO #Invoices
FROM Invoices

DECLARE
  @nID int,
  @nAmount float,
  @pID int

-- run infinity loop
WHILE 1=1
BEGIN

  -- set all the variables to NULL
  SET @nID=NULL
  SET @nAmount=NULL
  SET @pID=NULL

  -- get data from the last negative row
  SELECT
    @nID=ID,
    @nAmount=Amount
  FROM
    (
      SELECT TOP 1 *
      FROM #Invoices
      WHERE Amount<0
      ORDER BY ID DESC
    ) q

  -- get prev positive row
  SELECT @pID=ID
  FROM
    (
      SELECT TOP 1 *
      FROM #Invoices
      WHERE ID<@nID
        AND Amount>0
      ORDER BY ID DESC
    ) q

  IF(@pID IS NULL)
  BEGIN
    -- get next positive row
    SELECT @pID=ID
    FROM
      (
        SELECT TOP 1 *
        FROM #Invoices
        WHERE ID>@nID
          AND Amount>0
        ORDER BY ID
      ) q
  END

  -- exit from loop
  IF(@pID IS NULL) BREAK

  -- substract amount from positive row
  UPDATE #Invoices
  SET
    Amount+=@nAmount
  WHERE ID=@pID

  -- delete used negative row
  DELETE #Invoices
  WHERE ID=@nID

END

-- show result
SELECT *
FROM #Invoices

DROP TABLE #Invoices

【讨论】:

  • 我认为它会起作用,但在我有周期的要求中不是一个选项。只是滑动函数或递归 CTE。另一件事,这是一个客户的例子,让我们说。我需要进行分区,以便我可以按客户等对发票进行分组......所以我不知道如何
  • @CrazyStuff27 。 . .只能回答您提出的问题。如果这回答了您的问题,您应该接受答案。如果您还有其他问题,请将其作为另一个问题提出,而不是在评论中提出。
  • @GordonLinoff 你说得对,下次我会问更具体的问题。感谢您的意见。