将多个表中的多行用于具有标量 UDF 的持久计算列

Posted

技术标签:

【中文标题】将多个表中的多行用于具有标量 UDF 的持久计算列【英文标题】:Using multiple rows from multiple tables for Persisted Computed Column with a Scalar UDF 【发布时间】:2018-10-10 08:46:06 【问题描述】:

我正在尝试在 Order Transactions Table 中创建一个新字段作为 Persisted 计算列,并使用标量 UDF 值作为该字段的值。 p>

我了解 Persisted 列的要求是该值是确定性的,这意味着我拥有的多表 UDF 是不确定的,因为它不使用源表中的字段。

功能

USE [MyDatabase]
GO
/****** Object:  UserDefinedFunction [dbo].[fnCalcOutstandingBalance]    
Script Date: 08/10/2018 14:01:18 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER FUNCTION [dbo].[fnCalcOutstandingBalance](@ItemReferance int)

RETURNS INT
WITH SCHEMABINDING 
AS
Begin
DECLARE @AcceptedQty INT
DECLARE @SumOfQty INT
DECLARE @Result INT

SELECT @AcceptedQty = 

    ISNULL([Accepted Quantity],0)
    FROM 
    dbo.[Order Transactions Table]
    WHERE @ItemReferance = [Item Referance] 

SELECT @SumOfQty =
    ISNULL(sum(Quantity),0)
    FROM dbo.[Delivery Table]
    GROUP BY [Item Referance]
    HAVING @ItemReferance = [Item Referance]

    SET @Result = ISNULL(@AcceptedQty,0) - ISNULL(@SumOfQty,0)

return @Result

END

我正在寻找一种解决方法,以便能够在订单交易表中使用上述函数生成的值。

添加列

ALTER TABLE [Order Transactions Table]
ADD CalcOB AS [dbo].[fnCalcOutstandingBalance]([Item Referance]) PERSISTED

我已经测试了这个函数,它可以作为一个独立的函数调用在 select 中正常工作。问题是我需要在计算列中使用它而不是虚拟列。

【问题讨论】:

【参考方案1】:

您可以在 UDF 中尝试WITH SCHEMABINDING。 这意味着如果不删除 UDF(和计算列等),基础表就无法更改

没有这个,肯定会阻止PERSISTED。

您是否意识到使用这样的 UDF 对性能和并发性的影响?

It's a cursor(对每一行,一一汇总) You have odd concurrent behaviours

cmets之后

CREATE VIEW dbo.SomeView
AS
SELECT
   ott.Col1, ott.Col2, ...,
   OutstandingBalance = ISNULL(ott.[Accepted Quantity],0) - ISNULL(SUM(dt.Quantity),0)
FROM
   dbo.[Order Transactions Table] ott
   LEFT JOIN
   dbo.[Delivery Table] dt ON ott.[Item Referance] = dt.[Item Referance]
GROUP BY
   ott.Col1, ott.Col2, ott.[Accepted Quantity], ...

您可以对视图进行架构绑定,但不能使用 LEFT JOIN 对其进行索引

【讨论】:

我已经尝试过这样做,但由于此错误消息,现在添加列时出现问题:表“订单交易表”中的计算列“CalcOB”无法保留,因为该列确实存在用户或系统数据访问权限。 @RikuDas 这是意料之中的。老实说,这是个坏主意 这是为什么呢?是否取决于持久列使用的存储量?我对这个问题的选择有限,计算列是我可以使用的最佳解决方案。这个错误信息是什么意思? 这意味着...基础数据可以更改,但不会通过 UDF 显示,因为计算列不会查询 UDF。因此 SQL Server 无法保持值更新 我会删除 UDF 并用视图封装 JOIN 和查找等【参考方案2】:

@gbn 的回答让我大吃一惊,但请允许我添加我的 0.02 美元。因为您的标量 UDF 访问表,所以我相信您将无法保留此列。也就是说,让我们 100% 清楚:

以您描述的方式添加计算列绝对没有任何好处,而且有很多损失。

首先,即使您可以保留此列,访问此表的任何查询都会变慢,在某些情况下会变慢。用于计算列、作为约束或默认值的 T-SQL 标量 UDF 使引用该表的查询不可并行化;仅串行执行!此外,一旦引入了 T-SQL 标量 UDF,可用的优化就会受到极大的限制。再次 - 糟糕,糟糕的坏主意。

正如 gbn 所说-索引视图是要走的路(如果您可以丢失左连接)。另一种选择是在需要该值时使用内联表值函数;它将比计算列执行得更好(前提是您添加了适当的索引。函数看起来像这样:

CREATE FUNCTION dbo.fnCalcOutstandingBalance(@ItemReferance int)
RETURNS TABLE WITH SCHEMABINDING  AS RETURN
SELECT   Result = ISNULL(sum(Quantity),0) -
         (
           SELECT ISNULL([Accepted Quantity],0)
           FROM   dbo.[Order Transactions Table]
           WHERE  @ItemReferance = [Item Referance] 
         )
FROM     dbo.[Delivery Table]
GROUP BY [Item Referance]
HAVING   @ItemReferance = [Item Referance];

要利用此功能,您需要了解APPLY。这里有一些关于为什么 T-SQL 标量 UDF 对计算列和约束很糟糕的好读物。

A Computed Column with a [scalar udf] might Impact Query Performance——程坤(SQLCAT)

Another Hidden Parallelism Killer: Scalar UDFs In Check Constraints – 埃里克·达林

Another reason why scalar functions in computed columns is a bad idea – 埃里克·达林

Beware-row-row-operations-udf-clothing – 布赖恩·莫兰

Be careful with constraints calling UDFs – Tibor Karaszi

Why does the Execution Plan include a scalar udf call for a persisted computed column? – 堆栈溢出

【讨论】:

【参考方案3】:

对于任何感兴趣的人,我已经设法通过使用光标(谢谢 @gbn)来处理现有数据的计算并填充具有相应计算值的新字段 (CalculatedOB)。

我已使用触发器(在 [Order Transactions Table].[Accepted Quantity][Delivery Table].[Quantity] 上)处理未来对未结余额。

光标和所有触发器都使用 fnCalcOutstandingBalance() 函数来计算值。

用于填充现有数据的光标:

declare @refid int;
declare @Result int;
declare refcursor cursor for
select [Item Referance] from [Order Transactions Table];

open refcursor

fetch next from refcursor into @refid

while @@FETCH_STATUS = 0
begin 
print @refid

fetch next from refcursor into @refid
set @Result = [dbo].[fnCalcOutstandingBalance](@refid)

update [Order Transactions Table] set CalculateOB = @Result 
    where [Item Referance] = @refid
end 

close refcursor;
deallocate refcursor;

更新触发器示例:

CREATE TRIGGER [dbo].[UPDATE_AcceptedQty]
ON [dbo].[Order Transactions Table]
for update
AS

DECLARE @ItemRef int;
declare @result int;

IF UPDATE ([Accepted Quantity])
Begin

SELECT @ItemRef=i.[Item Referance] from INSERTED i;

SET @result = [dbo].[fnCalcOutstandingBalance](@ItemRef)

UPDATE [Order Transactions Table] set CalculateOB = @Result 
    where [Item Referance] = @ItemRef

END

GO

这两种技术的结合使我能够模拟计算列的功能,而不受确定性要求或性能影响的限制。

非常感谢 @gbn 和 @Alan Burstein 的贡献!

【讨论】:

以上是关于将多个表中的多行用于具有标量 UDF 的持久计算列的主要内容,如果未能解决你的问题,请参考以下文章

将多个参数传递给 Pig Filter UDF

ValidFrom 可以在 UDF 中使用,但 ValidTo - 不是吗? (时态表中的计算列)

如何使用具有多个源列的 pandas_udf 将多个列添加到 pyspark DF?

MSSQL 上标量 UDF 中的 NULL 参数

从标量 UDF 引用表 - 绑定到模式名称?

如何将单个工作表中的多行(在 excel 中)转换为多个 CSV 文件