我使用 SQL 标量 UDF 来计算特定股票的加权移动平均线。 我创建了以下 UDF [dbo].[fn_WeightedMovingAverageClosePriceCalculate]。 (见下文)
但是,调用该函数时我得到的结果好坏参半。这是在同时执行两个查询时,但我得到不同的结果。我从查询中的函数中取出代码,插入我的测试值,它运行得很好(WMA13 = 1540.8346)。我很想知道为什么我在第二个结果集中得到 15.7313 作为 WMA13 的值,而两个查询完全相同。
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
所以有两种不同的执行计划,这也让我感到惊讶。 右一-https://drive.google.com/file/d/1vPHbAS3X8Jmua8E5ReUgumsiUuovtL4p/view?usp=sharing
错误的 - https://drive.google.com/file/d/180-Z3bMtzvV31En6z-zA-sVM_yPNyaQv/view?usp=sharing
最佳答案
这是标量 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 |
+-------------+----------------+
所有 4 行都是按键顺序从聚集索引中读取的。读取第一个后,SUM
为 NULL
。读取第二个后,SUM
为 23
。然后,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)
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/