为什么SQL Server说这个函数是不确定的?

时间:2018-01-26 19:46:57

标签: sql-server tsql non-deterministic

在T-SQL中执行此功能:

CREATE FUNCTION [dbo].[Parse_URI_For_Scheme](
         @URI nvarchar(4000))
RETURNS nvarchar(250)
WITH SCHEMABINDING
AS
BEGIN

   DECLARE @temp_string varchar(4000)
   DECLARE @return_string nvarchar(250)
   DECLARE @pos int

   SET @pos = CHARINDEX('://', @URI);
   --select @pos
   IF @pos > 0
      BEGIN
         SET @temp_string = SUBSTRING(@URI, 0, @pos);

         -- SET @pos = CHARINDEX('/', @temp_string)

         IF @pos > 0
            BEGIN
               SET @temp_string = LEFT(@temp_string, @pos - 1);

               SET @pos = CHARINDEX('@', @temp_string);

               IF @pos > 0
                  SET @return_string = SUBSTRING(@temp_string, @pos + 1, 250);
               ELSE
                  SET @return_string = @temp_string;
            END
         ELSE
            SET @return_string = '';
      END
   ELSE
      SET @return_string = '';

   RETURN @return_string;

END;

然后执行此命令,返回0:

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

有人可以告诉我为什么这不是确定性的功能吗?

2 个答案:

答案 0 :(得分:1)

SQL Server将函数标记为确定性的一个关键点是SchemaBinding功能。为了使您的函数具有确定性,您需要使用With SchemaBinding定义函数。

在您的示例中,如果删除With SchemaBinding,则ObjectProperty函数将为IsDeterministic属性返回0,因此通过添加With SchemaBinding,问题将得到解决你

@Paul has detailed explanation around this issue, here

答案 1 :(得分:0)

瓦希德给出了很好的答复。我同意他所说的一切,并且喜欢保罗怀特评论的链接。

有几点需要注意:

<强> 1。您正在使用的逻辑对于您要执行的操作过于复杂。

我认为你可以逃脱:

SUBSTRING(@URI, 1, ISNULL(NULLIF(CHARINDEX('://', @URI)-1,0),''))

<强> 2。如果必须具有此任务的功能,请使用内联表值函数(iTVF)。

这个建议改变了我的职业生涯,多年来我学到的是有两种T-SQL函数:iTVF和非常慢的函数(例如Scalar和多语句表值函数)。 Paul White对此主题的阅读更为精彩:Understanding and Using APPLY (Part 1)

让我们将您的功能转变为高性能,支持并行性的DETERMINISITC功能。

CREATE FUNCTION dbo.parse_uri_for_scheme_itvf(@URI nvarchar(4000))
RETURNS TABLE WITH SCHEMABINDING AS RETURN
SELECT newURI = SUBSTRING(@URI, 1, ISNULL(NULLIF(CHARINDEX('://', @URI)-1,0),''));

和性能测试,以证明为什么标量函数不适合匆忙的人。

示例数据

SELECT string = cast(pr+'://'+samples.txt as nvarchar(4000))
INTO #strings
FROM (VALUES ('http'),('https'),('ftp')) pr(pr)
CROSS JOIN 
(
  SELECT replicate(newid(), abs(checksum(newid())%2)+1)
  FROM (VALUES (1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) a(x),
       (VALUES (1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) b(x),
       (VALUES (1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) c(x),
       (VALUES (1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) d(x),
       (VALUES (1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) e(x)
) samples(txt);

Perf测试

SET NOCOUNT ON;
PRINT 'Scalar'+char(10)+replicate('-',60);
GO
DECLARE @st datetime = getdate(), @txt nvarchar(4000);
SELECT @txt = dbo.Parse_URI_For_Scheme(t.string)
FROM #strings t;
PRINT datediff(ms,@st,getdate())
GO 3

PRINT 'iTVF'+char(10)+replicate('-',60);
GO
DECLARE @st datetime = getdate(), @txt nvarchar(4000);
SELECT @txt = itvf.newURI
FROM #strings t
CROSS APPLY dbo.parse_uri_for_scheme_itvf(t.string) itvf;
PRINT datediff(ms,@st,getdate())
GO 3

<强>结果

Scalar
------------------------------------------------------------
Beginning execution loop
1423
1380
1360
Batch execution completed 3 times.

iTVF
------------------------------------------------------------
Beginning execution loop
423
427
437
Batch execution completed 3 times.

我知道在准备测试之前,iTVF会快3-4倍。