SQL Server 为什么REPLICATE函数在传递给返回表的函数时要重新计算NEWID的值?

kqlmhetl  于 2023-01-29  发布在  其他
关注(0)|答案(1)|浏览(92)

我写了这个函数,然后去测试它。结果有点让我吃惊。知道为什么会这样吗?

CREATE FUNCTION dbo.chunk(@input VARCHAR(MAX), @chunkSize INT = 36)

RETURNS TABLE AS    

RETURN (

    WITH CTE AS (

        SELECT SUBSTRING(@input,1,@chunkSize) AS [chunk], 1 AS [row]

        UNION ALL

        SELECT SUBSTRING(@input,1+([row]*@chunkSize),@chunkSize)
        , [row] + 1 
        FROM cte
        WHERE LEN(@input) > ([row]*@chunkSize)  

    )

    SELECT [chunk] 
    FROM cte

)

GO
/* This does what I would expect this to do. */
DECLARE @input varchar(MAX) = REPLICATE(NEWID(),2);
SELECT * FROM dbo.chunk(@input,36)

/* But this, this is odd. If I call replicate here it calls newid() twice... */
SELECT * FROM dbo.chunk(REPLICATE(NEWID(),2),36);

| 大块|
| - ------|
| E1810B3D-三个数据分配器4 - 4F55-B650-ED2DB28BCF70|
| E1810B3D-三个数据分配器4 - 4F55-B650-ED2DB28BCF70|
| 大块|
| - ------|
| 89A26C8B-D5C7 - 47A8-三位一体式|
| 型号:F9636F76 - 1ED6 - 4D19-A309-BA35EAC9F782|

atmip9wb

atmip9wb1#

这是意料之中的。
您使用的函数是内联表值函数,因此调用内联到常规执行计划中。
该函数具有VARCHAR(MAX)类型的@input参数,并且您将REPLICATE(NEWID(),2)传递给它,因此概念上

  • 将函数中@input的所有示例替换为CONVERT(VARCHAR(MAX),REPLICATE(NEWID(),2))
  • 同样,只需将@chunkSize的所有示例替换为36。

一旦你这样做了,你就得到了下面的结果,它给出了一个相同的执行计划和行为。

WITH CTE AS (

    SELECT SUBSTRING(CONVERT(VARCHAR(MAX),REPLICATE(NEWID(),2)),1,36) AS [chunk], 1 AS [row]

    UNION ALL

    SELECT SUBSTRING(CONVERT(VARCHAR(MAX),REPLICATE(NEWID(),2)),1+([row]*36),36)
    , [row] + 1 
    FROM cte
    WHERE LEN(CONVERT(VARCHAR(MAX),REPLICATE(NEWID(),2))) > ([row]*36)  

)

SELECT [chunk] 
FROM cte

NEWID()在计划中被引用了三次。一次在递归CTE的锚分支中,两次在递归分支中。在您的情况下,您得到了定位点的一次调用和递归分支的一次调用,因此它被计算了三次。对于涉及LEN的表达式,重新计算不会'因为即使实际GUID已经改变,长度也将相同,所以不会对结果产生任何差别。
一些非确定性函数被当作runtime constants处理,但是NEWID()不是其中之一,这在这个递归CTE的情况下并没有真正的帮助。
如果您尝试以下操作...

CREATE OR ALTER FUNCTION dbo.RandRows(@input float, @Rows INT = 10)
RETURNS TABLE AS    
RETURN (
    WITH CTE AS (
        SELECT 1 AS Level, @input as [rand]
        WHERE @Rows >= 1 
        UNION ALL
        SELECT Level + 1, @input as [rand]
        FROM cte
        WHERE @Rows > Level + 1  
    )
    SELECT [rand] 
    FROM cte
)

GO

SELECT *
FROM dbo.RandRows(RAND(), 10)

结果和

WITH CTE AS (
        SELECT 1 AS Level, rand() as [rand]
        WHERE 10 >= 1 
        UNION ALL
        SELECT Level + 1, rand() as [rand]
        FROM cte
        WHERE 10 > Level + 1  
    )
    SELECT [rand] 
    FROM cte

锚分支和递归分支中的调用被视为两个不同的调用,并被赋予不同的运行时常量标签,因此得到one value in the first row and a different value in the subsequent rows

相关问题