Problema de alto nível: Quero atualizar uma tabela existente para preencher uma coluna existente com 32 bytes gerados aleatoriamente, codificados em base64. Os dados aleatórios devem ser diferentes para cada linha.
Ignorando por um momento o requisito de codificação base64, a solução é simples, conforme ilustrado neste código de exemplo:
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
Isso funciona como esperado. CRYPT_GEN_RANDOM(32)
gera um valor diferente para cada linha atualizada. Agora tente adicionar o requisito de codificação 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
Isso não funciona: ele coloca o mesmo valor em cada linha. Eu tentei empacotar a codificação base64 em um UDF, para ver se isso ajudaria:
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
E então a declaração de atualização se torna:
update @table
set txt = ConvertBytesToBase64(CRYPT_GEN_RANDOM(32))
Mas isso ainda produz o mesmo valor em todas as linhas.
O que eu fundamentalmente não entendo é, dado que o SQL Server avalia CRYPT_GEN_RANDOM(32)
para cada linha (o que parece sensato), por que ele não avalia ConvertBytesToBase64(CRYPT_GEN_RANDOM(32))
para cada linha? Como posso fazer com que ele avalie para cada linha? (e talvez relacionado, há uma maneira melhor de fazer codificação base64 no SQL Server 2019+?)
Historicamente, UDFs escalares no SQL Server eram avaliadas como RBAR.
Você pode adicionar
WITH INLINE = OFF, SCHEMABINDING
à definição da função para desabilitar o inlining da função, caso contrário, ela pode acabar sendo tratada da mesma forma que sua consulta original (o schemabinding não é necessário para interromper o inlining, mas está lá para evitar um spool desnecessário noUPDATE
plano de proteção de Halloween).Em relação à sua pergunta original...
a subconsulta não é mais uma expressão escalar simples e requer uma subárvore XML PATH. A geração do plano de execução vê que ele retornará apenas uma linha, então coloca a operação cara do lado de fora dos loops aninhados, então ela é avaliada apenas uma vez.
Descobri que adicionar um
OPTION (FORCE ORDER)
impediu que isso acontecesse, e ele foi avaliado dentro dos loops aninhados.Você também pode adicionar alguma correlação falsa como neste exemplo (que é anexada
0x
ao valor binário, então não altera o resultado, mas agora é uma subconsulta correlacionada)O Azure tem uma função interna
BASE64_ENCODE
, mas isso ainda não está na versão on prem. Acabei de tentarno Azure e obtive avaliação "por linha" e um valor diferente em cada linha.