【问题标题】:Running Totals in a Persisted Computed Column在持久计算列中运行总计
【发布时间】:2017-08-16 14:24:05
【问题描述】:

我在 SQL Server 2012 中有一个这样的表:

CREATE TABLE [dbo].[CustomerLedgerEntries]
(
    [Id] [bigint] IDENTITY(1,1) NOT NULL,
    [CustReference] [bigint] NOT NULL,
    [DebitAmount] [decimal](18, 2) NOT NULL,    
    [CreationTime] [datetime] NOT NULL,
    [Description] [nvarchar](512) NULL,
    [ProductId] [nvarchar](32) NULL,

    CONSTRAINT [PK_dbo.CustomerLedgerEntries] 
        PRIMARY KEY CLUSTERED ([Id] ASC)
) ON [PRIMARY]

我想添加一个经过计算的持久列,该列将为我提供每个客户的运行余额。目前我使用这个查询来获取每个客户的流动余额:

SELECT 
    Id, CustReference, CreationTime, DebitAmount, CreditAmount, 
    SUM(DebitAmount - CreditAmount) OVER (PARTITION BY CustReference ORDER BY Id 
                                          ROWS UNBOUNDED PRECEDING) AS Balance 
FROM
    CustomerLedgerEntries

我尝试使用此查询添加计算列:

ALTER TABLE dbo.CustomerLedgerEntries 
    ADD Balance AS (SUM(DebitAmount - CreditAmount) OVER (PARTITION BY CustReference 
                                                          ORDER BY Id 
                                                          ROWS UNBOUNDED PRECEDING));

但是它会抛出一个错误:

消息 4108,第 15 级,状态 1,第 8 行
窗口函数只能出现在 SELECT 或 ORDER BY 子句中。

请问我怎样才能做到这一点?

编辑 1: 遵循下面@JM_ 的建议并创建 UDF 后,表格中的余额显示如下:

Id CusReference CreationTime DebitAmount CreditAmount Balance 30 3 2017-07-12 15:26:36.753 15000.00 0.00 14000.00 31 3 2017-07-12 15:26:36.753 0.00 1000.00 14000.00

而当我运行查询 Select Id, CustReference, CreationTime, DebitAmount, CreditAmount , Sum(DebitAmount - CreditAmount) Over( Partition By CustReference Order by Id rows Unbounded Preceding) As Balance From CustomerLedgerEntries 时,结果正确显示:

30 3 2017-07-12 15:26:36.753 15000.00 0.00 15000.00 31 3 2017-07-12 15:26:36.753 0.00 1000.00 14000.00

差不多到了……但还没有完全到。请问有什么帮助吗?

【问题讨论】:

  • 不支持您尝试执行的操作,因为计算列仅包含有关插入的行的信息..您可以创建视图
  • 不支持,因为它不是确定性的。由于可以使用较早的 ID 添加新记录,导致该 ID 之后的所有记录,对于给定的 custReference,都将被重新评估;引擎无法更新超过这一记录。您要执行的操作在计算列中是不可能的。
  • 在计算中使用 UDF 怎么样?

标签: sql-server tsql sql-server-2012


【解决方案1】:

我假设您的表有一个信用列,但上面的脚本没有包含它。您可以使用 UDF 来实现:

--编辑:

CREATE FUNCTION [dbo].[UDF_GetBalance] (@CustRef bigint, @ID bigint)
RETURNS decimal(18,2)
AS
BEGIN
  DECLARE @Balance decimal(18,2)
  SELECT @Balance = SUM(DebitAmount - CreditAmount) OVER (PARTITION BY CustReference ORDER BY Id 
                                      ROWS UNBOUNDED PRECEDING) 
FROM
CustomerLedgerEntries WHERE CustReference =@CustRef and ID <= @id


RETURN @Balance
END


ALTER TABLE CustomerLedgerEntries ADD Balance as (dbo.UDF_GetBalance(CustReference, ID))

【讨论】:

  • 请看我上面的Edit 1,有什么可以帮忙的吗?
  • 更改为使用 ID 仅保留当前记录及以下
  • 太棒了!工作@JM_。谢谢你一百万!
【解决方案2】:

出于多种原因,为此在计算列中使用标量 udf 是一个糟糕的想法。一个大问题是针对该表的查询,即使未引用计算列,也会连续运行。这是一篇很棒的文章,解释了为什么你不想这样做:Another reason why scalar functions in computed columns is a bad idea

SQL Server 2012 提供了执行索引 builds/rebuilds in parallel 的功能,但是当您以这种方式使用标量 udf 时,您将失去该功能。您可以使用this article 中引用的跟踪标志自行测试。

由于许多其他原因,标量函数也很糟糕。为了获得在您的 CustomerLedgerEntries 表上限制串行执行的最佳性能,我将使用 iTVF(请注意,我无法对此进行测试)。 iTVF 将允许您使用所有您的 CPU。

CREATE FUNCTION dbo.UDF_GetBalance (@CustRef bigint)
RETURNS TABLE AS RETURN
  SELECT balance = SUM(DebitAmount - CreditAmount) 
    OVER (PARTITION BY CustReference ORDER BY Id ROWS UNBOUNDED PRECEDING) 
FROM CustomerLedgerEntries 
WHERE CustReference =@CustRef;
GO

【讨论】:

  • 我创建了这个 iTVF,但我不知道如何使用它。我该怎么做?
猜你喜欢
  • 1970-01-01
  • 2020-06-30
  • 1970-01-01
  • 1970-01-01
  • 2010-10-14
  • 1970-01-01
  • 2017-10-04
  • 2021-12-20
相关资源
最近更新 更多