segunda-feira, 1 de fevereiro de 2010

SET vs. SELECT

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)
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.

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]



3 comentários:

Leave your comment here!