MSSQL 为什么这个函数是不确定的
MSSQL Why is this function non-deterministic
我有这个用户函数,它总是被标记为不确定的,尽管只要输入参数相同,值就会始终相同。我读过的所有内容都表明这应该是确定性的。
有人能找出原因吗?
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: "]
和return基于日期范围、包含的分支数量等的总分
下面的 IsDeterministic 检查总是 0?
SELECT OBJECTPROPERTY(OBJECT_ID('[dbo].[udfGetCriteriaScore]'), 'IsDeterministic')
其他人有同样的问题:
https://www.sqlservercentral.com/Forums/Topic1545616-392-1.aspx
在那里,解决方案是将字符串日期转换为实际日期:
You need to do an explicit CONVERT to use the string literal. I
changed your code a little bit to remove the +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
同样的答案解释了这是因为有 non-deterministic 的日期格式,因此您需要明确设置(确定性的)日期格式以使字符串转换被视为确定性的。
我有这个用户函数,它总是被标记为不确定的,尽管只要输入参数相同,值就会始终相同。我读过的所有内容都表明这应该是确定性的。
有人能找出原因吗?
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: "]
和return基于日期范围、包含的分支数量等的总分
下面的 IsDeterministic 检查总是 0?
SELECT OBJECTPROPERTY(OBJECT_ID('[dbo].[udfGetCriteriaScore]'), 'IsDeterministic')
其他人有同样的问题:
https://www.sqlservercentral.com/Forums/Topic1545616-392-1.aspx
在那里,解决方案是将字符串日期转换为实际日期:
You need to do an explicit CONVERT to use the string literal. I changed your code a little bit to remove the +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
同样的答案解释了这是因为有 non-deterministic 的日期格式,因此您需要明确设置(确定性的)日期格式以使字符串转换被视为确定性的。