Apesar desta discussão ser um tanto quanto batida, ainda vale a pena remexer o cadáver para eliminar todas as dúvidas. Até a versão 6.5, o Microsoft Sql Sever apresentava apenas atribuições de variáveis com SELECT, porém a partir da versão 7.0, a Microsoft apresentou o comando SET para atribuição e inicialização de variáveis. Inclusive, o BOOKS ON LINE da versão 7.0 informava: "It is recommended that SET @local_variable be used for variable assignment rather than SELECT @local_variable." (É recomendado que o SET seja usado para atribuição de variáveis locais em detrimento das atribuições com select).
Isto ocasionou um pouco de confusão entre os desenvolvedores, uma vez que a Microsoft nunca especificou por que o SET era mais indicado. Portanto vou comentar as diferenças entre os dois comandos e algumas coisas que todo desenvolvedor que trabalha com T-SQL deve estar ciente ao criar querys.
Se você leitor do TSQLMASTER é um iniciante no SQL SERVER, observe o código abaixo:
/* Declarando */
DECLARE @var1 AS int, @var2 AS int
/* atribuindo com select*/
SELECT @var1 = 1
/* atribuindo com set */
SET @var2 = 2
O código acima, usa SET e SELECT para atribuir valores em variáveis locais.
Agora vamos as diferenças entre os dois métodos: se você se importa com os padrões ANSI para a linguagem SQL, então utilize o SET para atribuições de variáveis, pois este é o padrão ANSI para atribuição de variáveis locais.
Outra diferença importante: o SELECT permite atribuição múltipla de variáveis. Observe:
/* Declarando */
DECLARE @var1 AS int, @var2 AS int
/* atribuindo com select*/
SELECT @var1 = 1,
@var2 = 2
/* atribuindo com set */
SET @var1 = 1
SET @var2 = 2
Muito bem, agora entendemos as diferenças básicas entre as duas metodologias. Porém se você já escreveu código para tratamento de erros, você sabe que as variáveis de sistema @@error e @@rowcount devem ser capturadas no mesmo comando imediatamente depois do respectivo comando DML, portanto se você optou por ficar com os padrões ANSI, aqui está o seu primeiro problema. Mude o output do Management Studio para Text e execute o código abaixo:
DECLARE @Excecao int, @Linhas int
SELECT object_id/0 FROM sys.objects
set @Linhas = @@ROWCOUNT
set @Excecao = @@ERROR
SELECT
@Excecao AS CodigoErro,
@Linhas as Linhas
GO
Ok, se você quer insistir com o SET, podemos utilizar técnicas avançadas de POG para resolver o dilema:
DECLARE @Excecao int, @Linhas int, @mensagem varchar(100)
SELECT object_id/0 FROM sys.objects
set @mensagem='Código erro: ' +cast(@@ERROR as varchar(10))+
'Linhas: ' + cast(@@ROWCOUNT as varchar(10))
SELECT
@mensagem
O primeiro código (sem POGS), apresenta o código 0 para o erro, apesar da divisão por zero gerar o erro de código interno 8134. Então na situação acima, deixe de lado o SET e use logo o SELECT.
Outra diferença primordial ente o SET e o SELECT, é que o set retorna erros ao atribuir valores de uma coluna de uma query com vários registros, para uma variável, e o select atribuirá o último valor da coluna atribuída. Observe o código abaixo:
create table teste(
nome varchar(100)
)
insert into teste values ('Rodrigo')
insert into teste values ('Daniel')
insert into teste values ('Maria')
declare @nome varchar(100)
select @nome=nome from teste
select @nome
Porém a mesma atribuição com SET dá erro:
declare @nome varchar(100)
declare @nome varchar(100)
set @nome= (select nome from teste)
Msg 512, Level 16, State 1, Line 2
Subquery returned more than 1 value. This is not permitted when the subquery follows =, !=, <, <= , >, >= or when the subquery is used as an expression.
Uma outra funcionalidade bastante interessante, possível apenas com o SET, é a possibilidade de concatenação de valores através de atribuição com select. Observe o código abaixo:
declare @nome varchar(100)
select @nome = ''
select @nome=@nome + nome from teste
select @nome
O código acima deve retornar a string "RodrigoDanielMaria". Esta funcionalidade serve também para valores numéricos.
O código acima deve retornar a string "RodrigoDanielMaria". Esta funcionalidade serve também para valores numéricos.
Outro ponto pertinente em relação ao SET e SELECT, diz respeito a atribuição de querys que não retornam nada. Execute o código abaixo e veja o que quero dizer:
--retorna "null"
declare @nome varchar(100)
set @nome = 'Não encontrado'
set @nome = (
select nome from teste
where 1 <> 1
)
select @nome
--retorna "não encontrado"
declare @nome varchar(100)
set @nome = 'Não encontrado'
select @nome=nome from teste
where 1 <> 1
select @nome
E por último, mas não menos importante: atribuição com SELECT é mais rápida do que com SET? Já li muita documentação contraditória a respeito deste assunto, e depois de muito tempo trabalhando com o SQL Server, cheguei a conclusão que em muitas situações, as duas metodologias se equivalem, porém, devido ao fato das atribuições com SELECT permitirem atribuições múltiplas, a performance com esta situação pode ser mais rápida.
Para tirar a prova dos nove, resolvi testar o seguinte script que faz um comparativo entre as metodologias. Execute e veja os resultados:
DECLARE @Teste1 int, @Teste2 int, @Teste3 int, @TesteVar1 int, @TesteVar2 int
DECLARE @Loop int, @Inicio datetime, @Controle int, @IteracoesLoop int, @IteracoesLoop2 int
SET @Teste1 = 0
SET @Teste2 = 0
SET @Teste3 = 0
SET @Loop = 0
SET @TesteVar2 = 0
SET @IteracoesLoop = 10
SET @IteracoesLoop2 = 50000
WHILE @Loop < @IteracoesLoop
BEGIN
SET @Inicio = CURRENT_TIMESTAMP
SET @Controle = 0
/* Testando a performance do SET */
WHILE @Controle < @IteracoesLoop2
BEGIN
SET @TesteVar1 = 1
SET @TesteVar2 = @TesteVar2 - @TesteVar1
SET @Controle = @Controle + 1
END
SET @Loop = @Loop + 1
SET @Teste1 = @Teste1 + DATEDIFF(ms, @Inicio, CURRENT_TIMESTAMP)
END
SET @Loop = 0
SET @TesteVar2 = 0
WHILE @Loop < @IteracoesLoop
BEGIN
SELECT @Inicio = CURRENT_TIMESTAMP
SELECT @Controle = 0
/* Testando a performance do SELECT */
WHILE @Controle < @IteracoesLoop2
BEGIN
SELECT @TesteVar1 = 1
SELECT @TesteVar2 = @TesteVar2 - @TesteVar1
SELECT @Controle = @Controle + 1
END
SELECT @Loop = @Loop + 1
SELECT @Teste2 = @Teste2 + DATEDIFF(ms, @Inicio, CURRENT_TIMESTAMP)
END
SET @Loop = 0
SET @TesteVar2 = 0
WHILE @Loop < @IteracoesLoop
BEGIN
SELECT @Inicio = CURRENT_TIMESTAMP, @Controle = 0
/* Testando a performance do SELECT em atribuições múltiplas */
WHILE @Controle < @IteracoesLoop2
BEGIN
SELECT @TesteVar1 = 1, @TesteVar2 = @TesteVar2 - @TesteVar1, @Controle = @Controle + 1
END
SELECT @Loop = @Loop + 1, @Teste3 = @Teste3 + DATEDIFF(ms, @Inicio, CURRENT_TIMESTAMP)
END
SELECT
(@Teste1/CAST(@IteracoesLoop AS decimal(7,2)))/1000.00 AS [Atribuição com SET],
(@Teste2/CAST(@IteracoesLoop AS decimal(7,2)))/1000.00 AS [Atribuição com SELECT],
(@Teste3/CAST(@IteracoesLoop AS decimal(7,2)))/1000.00 AS [SELECT com atribuição múltipla]
Boa tarde Rodrigo,
ResponderExcluirBem elaborado esse seu post, direto ao ponto
sem ser superficial. Sanou uma dúvida e ainda me acrescentou mais.
Parabéns.
Obrigado!
ResponderExcluirobrigado!
ResponderExcluir