MSSQL 为啥这个函数是不确定的

Posted

技术标签:

【中文标题】MSSQL 为啥这个函数是不确定的【英文标题】:MSSQL Why is this function non-deterministicMSSQL 为什么这个函数是不确定的 【发布时间】:2018-03-08 14:39:26 【问题描述】:

我有这个用户函数,它总是被标记为非确定性的,尽管只要输入参数相同,值总是相同的。我读过的所有内容都表明这应该是确定性的。

有人能找出原因吗?

    SET ANSI_NULLS ON
    GO
    SET QUOTED_IDENTIFIER ON
    GO

    ALTER FUNCTION [dbo].[udfGetCriteriaScore] 
    (   
        @InputString varchar(max)
    )
    RETURNS int 
    WITH SCHEMABINDING
    AS
    BEGIN
        DECLARE @returnScore int;

            DECLARE @IdentifierChar NCHAR(1)= '"'
            DECLARE @overallScore int
            DECLARE @FirstID int
            DECLARE @SecondID int
            DECLARE @TargetString varchar(MAX)
            DECLARE @startDateCriteria varchar(MAX);
            DECLARE @endDateCriteria varchar(MAX);

            declare @scoringTable table (
                Criterion varchar(max),
                CriteriaScore int,
                Occurances int,
                SumTotalScore int
            )

            DECLARE @TotalCriterions int = (SELECT LEN(@InputString) - LEN(REPLACE(@InputString, '@', '')))
            declare @COUNT int = 0
            declare @Length int = 0

            WHILE(@COUNT) < @TotalCriterions
            BEGIN
                Set @FirstID = CHARINDEX(@IdentifierChar, @InputString, @Length)
                Set @SecondID = CHARINDEX(@IdentifierChar, @InputString, @FirstID + 1)
                Set @Length = @SecondID - @FirstID
                Set @TargetString = SUBSTRING(@InputString, @FirstID + 1, @Length - 1)
                SET @COUNT = @COUNT + 1
                Set @Length = @SecondID + 1

                DECLARE @criteriaScore int
                DECLARE @criteriaCount int

                DECLARE @Criterion varchar(max)
                SET @Criterion = SUBSTRING(@TargetString, 0, CHARINDEX(':', @TargetString))

                -- Calculate date range score
                IF (LOWER(@Criterion) = '@fromdate' OR LOWER(@Criterion) = '@todate')
                    BEGIN
                        IF LOWER(@Criterion) = '@fromdate'
                            SET @startDateCriteria = SUBSTRING(@TargetString, CHARINDEX(':', @TargetString) + 2, LEN(@TargetString) - CHARINDEX(':', @TargetString))

                        IF LOWER(@Criterion) = '@todate'
                            SET @endDateCriteria = SUBSTRING(@TargetString, CHARINDEX(':', @TargetString) + 2, LEN(@TargetString) - CHARINDEX(':', @TargetString))

                        IF @startDateCriteria IS NOT NULL AND @endDateCriteria IS NOT NULL
                            BEGIN
                                SET @criteriaScore = 5
                                SET @criteriaCount = DATEDIFF (dd, @startDateCriteria, @endDateCriteria) 

                                INSERT INTO @scoringTable 
                                    (Criterion, CriteriaScore, Occurances, SumTotalScore) 
                                VALUES 
                                    ('DateRange', @criteriaScore, @criteriaCount, (@criteriaScore * @criteriaCount))
                            END
                    END
                ELSE
                -- Calculate individual criterion score
                    BEGIN

                        SET @criteriaScore =
                            CASE 
                                WHEN LOWER(@Criterion) = '@branchid' THEN 10
                                WHEN LOWER(@Criterion) = '@locationid' THEN 10
                                WHEN LOWER(@Criterion) = '@salesexecid' THEN 1
                                WHEN LOWER(@Criterion) = '@thedate' THEN 5
                                ELSE 1
                            END

                        SET @criteriaCount =
                            (SELECT 
                                    CASE    
                                        WHEN LEN(REPLACE(@TargetString, @Criterion, '')) < 3 THEN 0
                                        ELSE LEN(@TargetString) - LEN(REPLACE(@TargetString, ';', '')) + 1
                                    END
                            )

                        INSERT INTO @scoringTable 
                                (Criterion, CriteriaScore, Occurances, SumTotalScore) 
                            VALUES 
                                (@Criterion, @criteriaScore, @criteriaCount, (@criteriaScore * @criteriaCount))

                    END
            END

            IF EXISTS (SELECT Occurances from @scoringTable where Occurances > 0 AND LOWER(Criterion) in ('@salesexecid', '@locationid'))
                UPDATE @scoringTable SET SumTotalScore = 0 where LOWER(Criterion) = '@branchid'

            set @returnScore = (select SUM(SumTotalScore) from @scoringTable)

        Return @returnScore;
    END

它被设计成这样分割字符串:

["@BranchID: 154","@FromDate: 2018-02-01T00:00:00","@ToDate: 2018-02-26T00:00:00","@SalesExecID: "]

并根据日期范围、包含的分支数量等返回总分。

下面的 IsDeterministic 检查总是 0?

SELECT OBJECTPROPERTY(OBJECT_ID('[dbo].[udfGetCriteriaScore]'), 'IsDeterministic')

【问题讨论】:

标记您正在使用的 dbms。该代码是特定于产品的。 道歉。微软 SQL 2012 发现这是由于 SET @criteriaCount = DATEDIFF (dd, ....);如果您评论此行,它将是确定性的。检查原因 啊,是的,谢谢。我可以看到它切换到确定性。我认为 DATEDIFF 本身就是一个确定性函数? 您依赖从字符串到 DATETIME 值的隐式转换。这些因语言设置而异,因此不被视为确定性。 【参考方案1】:

其他人也有同样的问题:

https://www.sqlservercentral.com/Forums/Topic1545616-392-1.aspx

在那里,解决方案是将字符串日期转换为实际日期:

您需要执行显式 CONVERT 才能使用字符串文字。一世 稍微更改了您的代码以删除 +1

 CREATE TABLE Test(    DayDate DATE,    DayNumber AS (DATEDIFF( DD,
 CONVERT( DATE, '2014-04-30', 120), DayDate)) PERSISTED)

 INSERT INTO Test(DayDate) VALUES(GETDATE()) SELECT * FROM Test DROP
 TABLE Test

相同的答案解释了这是因为存在不确定的日期格式,因此您需要显式设置(确定的)日期格式,以便将字符串转换视为确定性。

【讨论】:

已编辑答案中的线程是通过逐字询问谷歌“sql server datediff deterministic”找到的 @JeroenMostert 完成

以上是关于MSSQL 为啥这个函数是不确定的的主要内容,如果未能解决你的问题,请参考以下文章

不能对不可变值使用变异成员:函数调用返回不可变值 - 不确定为啥值是不可变的

为啥这个函数在 Swift 中用于确定整数的输入是奇数还是偶数?

为啥电脑每次开机都会弹出“函数错误”的窗口?

如何确定一个列是不是是 MSSQL 2000 中的标识列?

为啥复制构造函数不需要检查输入对象是不是指向自身?

为啥这个 opencl 代码是不确定的?