SQL Scalar UDF 返回预期结果,然后返回一致的怪异值

标签 sql sql-server

我使用 SQL 标量 UDF 来计算特定股票的加权移动平均线。 我创建了以下 UDF [dbo].[fn_WeightedMovingAverageClosePriceCalculate]。 (见下文)

但是,调用该函数时我得到的结果好坏参半。这是在同时执行两个查询时,但我得到不同的结果。我从查询中的函数中取出代码,插入我的测试值,它运行得很好(WMA13 = 1540.8346)。我很想知道为什么我在第二个结果集中得到 15.7313 作为 WMA13 的值,而两个查询完全相同。

enter image description here

ALTER FUNCTION [dbo].[fn_WeightedMovingAverageClosePriceCalculate]
(
@SecCode varchar(100),
@StartDateId int,
@MovingAverageCount int 
)
RETURNS decimal(18,4)
AS
BEGIN

--Generate the Weighting Factor
Declare @WeightingFactor as decimal(18,8)
Set @WeightingFactor  = (@MovingAverageCount*(@MovingAverageCount+1))/2  -- using the formula n(n+1)/2


-- Declare the return variable here
Declare @MovingAverage as decimal (18,4) 
Set @MovingAverage = 0
if @MovingAverageCount <> 0 
begin 
Select @MovingAverage  = SUM(ClosePrice*RowNum/@WeightingFactor)from 
(   
    Select ROW_NUMBER() OVER(order by BusinessDateId asc) AS RowNum , ClosePrice, BusinessDateId
    from 
    (

        Select TOP (@MovingAverageCount) ClosePrice, BusinessDateId 
                from dbo.BhavCopy
                where BusinessDateId <=@StartDateId
                and SecCode = @SecCode
                and Exchange  = 'NSE'
        order by BusinessDateId desc
    )d

)a
end 

Set @WeightingFactor = 0
Set @MovingAverageCount = 0


-- Return the result of the function
Return @MovingAverage

查看我正在使用的数据: Data That Im working with

所以有两种不同的执行计划,这也让我感到惊讶。 右一-https://drive.google.com/file/d/1vPHbAS3X8Jmua8E5ReUgumsiUuovtL4p/view?usp=sharing

enter image description here

错误的 - https://drive.google.com/file/d/180-Z3bMtzvV31En6z-zA-sVM_yPNyaQv/view?usp=sharing

enter image description here

最佳答案

这是标量 UDF 内联及其在某些情况下如何处理标量聚合的错误 ( report )。

问题在于,内联时执行计划包含带有

的流聚合
ANY(SUM(ClosePrice*CONVERT_IMPLICIT(decimal(19,0),[Expr1007],0)/[Expr1002]))

此处 SUM 嵌套在 ANY 中是不正确的。

ANY 是一个内部聚合,它返回找到的第一个 NOT NULL 值(如果未找到,则返回 NULL

因此,在您的情况下,流聚合接收其第一行(很可能是 13 个符合条件的 BusinessDateId 中最低的行) - 计算 SUM(ClosePrice*RowNum/@WeightingFactor) 对于该行,将部分聚合结果传递给 ANY - 它认为其工作已完成并将其用作最终结果。其余 12 行对 SUM 的任何贡献都会丢失。

您可以将 with inline = off 添加到函数定义中以将其禁用,直到问题得到解决。

下面是一个更简单的演示(在 SQL Server 2019 RTM 和 RTM-CU2 上测试)

设置

DROP TABLE IF EXISTS dbo.Numbers

GO

CREATE TABLE dbo.Numbers(Number INT UNIQUE CLUSTERED);

INSERT INTO dbo.Numbers VALUES (NULL), (23), (27), (50);

演示 1

CREATE OR ALTER FUNCTION [dbo].[fnDemo1]()
RETURNS INT
AS
BEGIN
DECLARE @Result as int, @Zero as int = 0
SELECT @Result = SUM(Number + @Zero) from dbo.Numbers 
RETURN @Result
END

GO

DECLARE @Zero INT = 0
SELECT SUM(Number + @Zero) AS SanityCheck, 
         dbo.fnDemo1() AS FunctionResult
FROM dbo.Numbers 
OPTION (RECOMPILE) --I found the inlining happened more reliably with this

演示 1 结果

+-------------+----------------+
| SanityCheck | FunctionResult |
+-------------+----------------+
|         100 |             23 |
+-------------+----------------+

enter image description here

所有 4 行都是按键顺序从聚集索引中读取的。读取第一个后,SUMNULL。读取第二个后,SUM23。然后,ANY 可以停止并认为其工作已完成。其余两行仍会被读取,但不会影响返回的 ANY(SUM())

演示 2

如果没有中间@Result变量,则会抛出虚假错误

CREATE OR ALTER FUNCTION [dbo].[fnDemo2]()
RETURNS INT
AS
BEGIN
DECLARE @Zero as int = 0;
RETURN (SELECT SUM(Number + @Zero) from dbo.Numbers);
END

GO

Select dbo.fnDemo2()
OPTION (RECOMPILE) 

enter image description here

Msg 512, Level 16, State 1, Line xx

Subquery returned more than 1 value. This is not permitted when the subquery > > follows =, !=, <, <= , >, >= or when the subquery is used as an expression.

实际上只有一行来自流聚合,但流聚合还会计算 COUNT(*) 以及 ANY(SUM())。它没有包含在 ANY 中,因此在本例中总共为 4。它在断言运算符中使用,以给出将返回太多行的虚假错误。

演示 3

CREATE OR ALTER FUNCTION [dbo].[fnDemo3]()
RETURNS INT
AS
BEGIN
DECLARE @Zero as int = 0;
RETURN (SELECT SUM(Number + @Zero) from dbo.Numbers GROUP BY ());
END

GO

Select dbo.fnDemo3()
OPTION (RECOMPILE) 

这会生成堆栈转储和不同的错误

Msg 8624, Level 16, State 17, Line xx

Internal Query Processor Error: The query processor could not produce a query plan. For more information, contact Customer Support Services.

关于SQL Scalar UDF 返回预期结果,然后返回一致的怪异值,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62311952/

相关文章:

mysql - 删除mysql中带有连接的列

sql-server - 如何将动态 sql 插入临时表?

sql - T-SQL : checking for email format

sql-server - 在 Linux 中添加 SSL 证书后 SQL Server 服务中断

sql匹配条件

sql - 查看以识别分组值或对象

sql - 在oracle sql中查找两个日期之间耗时

c# - 如何显示数千条记录?

sql - 将主键字段添加到现有 Derby 表

mysql - 将 SELECT LEFT() 应用于多列组合的总字符数