将多个表中的多行用于具有标量 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 behaviourscmets之后
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 的持久计算列的主要内容,如果未能解决你的问题,请参考以下文章
ValidFrom 可以在 UDF 中使用,但 ValidTo - 不是吗? (时态表中的计算列)