高级问题:我想更新现有表,用随机生成的 32 字节、base64 编码的数据填充现有列。每行的随机数据应该不同。
暂时忽略 base64 编码要求,解决方案很简单,如以下示例代码所示:
DECLARE @table TABLE (
id int,
bin varbinary(max) null
)
-- put a few rows in the table
insert into @table (id) values (1)
insert into @table (id) values (2)
insert into @table (id) values (3)
-- perform the update
update @table
set bin = CRYPT_GEN_RANDOM(32)
-- check result
select *
from @table
这按预期工作。CRYPT_GEN_RANDOM(32)
为每个更新的行生成不同的值。现在尝试添加 base64 编码要求:
DECLARE @table TABLE (
id int,
txt nvarchar(max) null
)
-- put a few rows in the table
insert into @table (id) values (1)
insert into @table (id) values (2)
insert into @table (id) values (3)
-- perform the update
update @table
set txt = (SELECT CRYPT_GEN_RANDOM(32) FOR XML PATH(''), BINARY BASE64)
-- check result
select *
from @table
这不起作用:它会在每一行中放置相同的值。我尝试将 base64 编码打包到 UDF 中,看看是否有帮助:
CREATE FUNCTION ConvertBytesToBase64
(
@bytes varbinary(max)
)
RETURNS nvarchar(max)
AS
BEGIN
DECLARE @result nvarchar(max)
SET @result = (SELECT @bytes FOR XML PATH(''), BINARY BASE64)
RETURN @result
END
GO
然后更新语句变成:
update @table
set txt = ConvertBytesToBase64(CRYPT_GEN_RANDOM(32))
但这仍然会在每一行产生相同的值。
我根本不明白的是,既然 SQL Server 会评估CRYPT_GEN_RANDOM(32)
每一行(这似乎是合理的),为什么不评估ConvertBytesToBase64(CRYPT_GEN_RANDOM(32))
每一行?我怎样才能让它评估每一行?(也许相关,在 SQL Server 2019+ 中是否有更好的方法进行 base64 编码?)
SQL Server 中的标量 UDF 历史上被评估为 RBAR。
您可以添加
WITH INLINE = OFF, SCHEMABINDING
到函数定义中以禁用函数的内联,否则它最终可能会与您的原始查询处理方式相同(不需要模式绑定来停止内联,但可以防止UPDATE
万圣节保护计划中不必要的假脱机)。关于您最初的疑问...
子查询不再是简单的标量表达式,而是需要 XML PATH 子树。执行计划生成发现它只返回一行,因此将昂贵的操作放在嵌套循环的外部,这样它只会被评估一次。
我发现添加一个
OPTION (FORCE ORDER)
可以阻止这种情况的发生,并且它是在嵌套循环内部进行评估的。您还可以像本例中添加一些伪关联(附加
0x
到二进制值,因此不会改变结果,但现在是一个相关子查询)Azure 有一个内置
BASE64_ENCODE
函数,但本地版本中还没有这个函数。我刚刚尝试了在 Azure 中得到“每行”评估并且每行都有不同的值。