0

我正在尝试使用标量 UDF 值作为字段值在Order Transactions 表中创建一个新字段作为持久计算列。

我知道对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

我已经测试了这个函数,它应该在选择中作为一个独立的函数调用。问题是我需要在计算列中使用它而不是虚拟列。

4

3 回答 3

2

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

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

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

评论后

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 对其进行索引

于 2018-10-10T09:03:32.437 回答
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 对计算列和约束很糟糕的好读物。

带有 [scalar udf] 的计算列可能会影响查询性能–Kun Cheng (SQLCAT)

另一个隐藏的并行杀手:检查约束中的标量 UDF – Erik Darling

计算列中的标量函数是一个坏主意的另一个原因– Erik Darling

当心-row-row-operations-udf-clothing – Brian Moran

小心调用 UDF 的约束– Tibor Karaszi

为什么执行计划包含对持久计算列的标量 udf 调用? - 堆栈溢出

于 2018-10-10T18:53:12.413 回答
0

对于任何感兴趣的人,我已经设法通过使用光标(谢谢@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 的贡献!

于 2018-10-11T09:13:41.657 回答