Home - www.devmedia.com.br

A diferença entre SET e SELECT ao Atribuir Valores à Variáveis.

Tradicionalmente, os desenvolvedores que utilizam o banco de dados SQL Server, estão acostumados a usar a instrução SELECT para atribuir valores às variáveis. Isto era uma boa prática e muito válida até a versão 6.5 do SQL Server. Porém, a Microsoft liberou o SQL Server 7.0 em 1999 e nesta versão, foi introduzida a nova sentença SET, utilizada para inicializar e atribuir valores às variáveis. O Books Online do SQL Server 7.0 aconselha: "recomenda-se que a instrução SET @variável_ local seja usada para atribuição de variáveis, ao invés de SELECT @variável_ local."

Isto causou um pouco de confusão na comunidade de desenvolvedores, uma vez que a Microsoft nunca mencionou, porque é recomendável utilizar-se SET ao invés de SELECT para atribuir valores às variáveis. Neste artigo, serão destacadas as principais diferenças entre SET e SELECT, e algumas coisas que se deve levar em conta, ao usar-se tanto SET quanto SELECT.

Se você for completamente novo no TRANSACT-SQL, então os seguintes exemplos o ajudarão a entender melhor:

/* Declarando as variáveis */

DECLARE @Variable1 AS int, @Variable2 AS int

/* Atribuindo o valor 1 à @Variable1 usando SELECT */

SELECT @Variable1 = 1

/* Atribuindo o valor 2 à @Variable2 usando SET */

SET @Variable2 = 2

Agora vamos às diferenças entre o SET E SELECT! Os padrões são importantes para você? Se sua resposta for “sim”, então você deve utilizar SET. O motivo é que SET, é o formato ANSI padrão para atribuição de valores às variáveis, e o SELECT não é.

Uma outra diferença fundamental entre SET e SELECT, é que é possível usar SELECT para atribuir valores a mais de uma variável ao mesmo tempo. SET permite que sejam atribuídos dados a somente uma variável de cada vez. Veja como:

/* declarando as variáveis */

DECLARE @Variable1 AS int, @Variable2 AS int

/* inicializando duas variáveis de uma vez */

SELECT @Variable1 = 1, @Variable2 = 2


/* o mesmo pode ser feito usando SET, mas duas sentenças serão necessárias */

SET @Variable1 = 1
SET @Variable2 = 2

Até agora tudo bem. Mas se você nunca escreveu código para manipulação de erros no TRANSACT-SQL, você deve estar ciente de que as variáveis de sistema @@ERROR e @@ROWCOUNT deverão ser capturadas, imediatamente depois de uma sentença de manipulação de dados (DML) como INSERT, UPDATE, DELETE, caso contrário, estas variáveis de sistema serão resetadas para 0. Veja o seguinte exemplo:

DECLARE @Error int, @RowCount int
SELECT price/0 FROM dbo.titles
SET @RowCount = @@ROWCOUNT
SET @Error = @@ERROR
SELECT @Error AS Error
GO

Se você executar este trecho de código no banco de dados pubs, que acompanha o SQL Server, o valor da variável de sistema @@ERROR será exibido como 0, mesmo que a “divisão por zero” resulte no erro 8134. Assim, neste cenário particular, esqueça dos padrões e use SELECT, como mostrado abaixo:

DECLARE @Error int, @RowCount int
SELECT price/0 FROM dbo.titles
SELECT @RowCount = @@ROWCOUNT, @Error = @@ERROR
SELECT @Error AS Error

Mas se você insistir em usar SET mesmo neste cenário, há sempre uma maneira de fazê-lo. Está aqui um exemplo, embora não muito claro e não recomendado:

DECLARE @ErrorAndRowcount AS varchar(25), @Error int, @RowCount int
SELECT price/0 FROM dbo.titles



/*
Capturando @@ERROR e @@ROWCOUNT em uma string separada por um ponto
*/

SET @ErrorAndRowcount = CAST(@@ERROR AS varchar(12)) + '.' + CAST(@@ROWCOUNT AS varchar(12))


/* Uma maneira de separar a string contida nas variáveis Error e RowCount */

SET @Error = CAST(PARSENAME(@ErrorAndRowcount, 2) AS int)
SET @RowCount = CAST(PARSENAME(@ErrorAndRowcount, 1) AS int)
SELECT @Error AS Error, @RowCount AS Row_Count


/*
Uma outra maneira de separar a string contida nas variáveis Error e RowCount
*/

SET @Error = CAST(LEFT(@ErrorAndRowcount, CHARINDEX('.', @ErrorAndRowcount)-1) AS int)
SET @RowCount = CAST(RIGHT(@ErrorAndRowcount, CHARINDEX('.', REVERSE(@ErrorAndRowcount))-1) AS int)
SELECT @Error AS Error, @RowCount AS Row_Count
GO

Outras diferenças entre SET e SELECT: Ao usar uma query para popular uma variável, SET falhará e um erro será gerado, se a consulta retornar mais de um valor. Já o SELECT, atribuirá uma das linhas retornadas à variável e irá mascarar o fato de que a query retornou mais de uma linha. Em conseqüência, os erros em seu código poderiam passar despercebidos com SELECT, e estes tipos de erros são muito difíceis de serem depurados. Veja um exemplo:

/* Considere a seguinte tabela com duas linhas */

SET NOCOUNT ON
CREATE TABLE #Test (i int, j varchar(10))
INSERT INTO #Test (i, j) VALUES (1, 'First Row')
INSERT INTO #Test (i, j) VALUES (1, 'Second Row')
GO


/* O SELECT abaixo retornará duas linhas, mas a variável será preenchida com valor de apenas uma delas, sem que um erro seja gerado. Isto pode não ser o que você esperava. Uma vez que nenhum erro será retornado, você nunca saberá que duas linhas foram retornadas nesta circunstância, onde i = 1 */

DECLARE @j varchar(10)
SELECT @j = j FROM #Test WHERE i = 1
SELECT @j
GO


/* Se você reescrever a mesma query, mas desta vez usando SET para a inicialização da variável, você verá o seguinte erro: */

DECLARE @j varchar(10)
SET @j = (SELECT j FROM #Test WHERE i = 1)
SELECT @j

Server: Msg 512, Level 16, State 1, Line -1074284106
Subquery returned more than 1 value. This is not permitted when the subquery follows =, !=, <, <= , >, >= or when the subquery is used as an expression.

Baseado nos resultados acima, ao usar uma consulta para popular variáveis, utilize sempre SET, se você quiser estar certo de que somente uma linha será retornada. Se preferir não utilizar SET por alguma razão, você poderia obter o mesmo comportamento, usando SELECT, como mostrado abaixo:

DECLARE @j varchar(10)
SELECT @j = (SELECT j FROM #Test WHERE i = 1)
SELECT @j

Eis aqui uma outra diferença com relação a atribuição de valores baseados em uma query, especialmente quando a consulta não retornar linha alguma. Execute o seguinte exemplo com o banco de dados pubs e veja por você mesmo:

/* Returna NULL */

DECLARE @Title varchar(80)
SET @Title = 'Not Found'

SET @Title =
(
SELECT title
FROM dbo.titles
WHERE title_id = 'InvalitTitleID'
)

SELECT @Title
GO

/* Returna a string 'Not Found' */

DECLARE @Title varchar(80)
SET @Title = 'Not Found'

SELECT @Title = title
FROM dbo.titles
WHERE title_id = 'InvalitTitleID'

SELECT @Title
GO

Por último, mas não menos importante! Há alguma diferença de desempenho entre SET e SELECT? Algum deles é mais rápido ou mais lento do que o outro? Esta é uma pergunta que a maioria dos desenvolvedores e DBAs não têm tanta certeza. Acompanhe a seguir, um teste realizado para tentar esclarecer melhor estas questões.
Foi utilizado um computador com todas as aplicações fechadas e serviços desnecessários parados. O agente de serviço do SQL Server também foi parado para se ter a certeza de que nenhum job fosse executado durante os testes de performance. A máquina também foi desconectada da rede. Dessa maneira obteve-se uma máquina SQL Server isolada, com nada mais do que apenas o serviço do SQL Server rodando nela. Em seguida, foi criado um script de teste que continuamente atribuía valores à variáveis dentro de um loop (de iterações configuráveis) utilizando SET e SELECT, onde o tempo para completar cada laço foi medido. Os resultados obtidos foram os seguintes:

Houve muito pouca diferença de desempenho entre SET e SELECT, quando foram feitas inicializações de variáveis ou atribuições de valores para elas. Mas, uma descoberta surpreendente foi feita. Como se sabe, uma única sentença SELECT pode ser usada para atribuir valores à múltiplas variáveis. Esta característica do SELECT o torna superior ao SET neste quesito. No teste realizado, uma única instrução SELECT atribuiu valores a 3 variáveis diferentes, e esta execução foi muito mais rápida do que quando foram utilizadas 3 sentenças diferentes com SET, atribuindo valores a 3 variáveis diferentes. Neste cenário, usar um SELECT foi pelo menos duas vezes mais rápido, do que o uso de SET. Desta forma, a conclusão a que se chegou foi a seguinte: se você tiver um laço em seu procedimento armazenado que manipula os valores de diversas variáveis, e se você quiser obter o máximo de desempenho quanto for possível fora deste laço, então faça todas as manipulações variáveis em uma única sentença SELECT (ou agrupe as variáveis relacionadas em poucas instruções SELECT) como a mostrado abaixo:

SELECT @TestVar1 = @TestVar1 + 1, @TestVar2 = @TestVar2 - 1, @CTR = @CTR + 1

O teste foi realizado no SQL Server, versões 7.0, 2000 e SQL Server Yukon Beta 1 e os resultados foram consistentes. Estes mesmos testes foram aplicados tanto em computadores com um processador quanto em computadores com multii-processadores, e os resultados foram os mesmos. Apenas é aconselhável não rodar o teste em uma máquina de produção, uma vez que ele exige 100% de utilização da CPU durante a sua execução. Além disso, se você achar que o teste irá demorar muito para terminar, diminua o valor da variável @TimesToLoop2, de modo que o número de iterações seja reduzido. Ao final do teste, o script exibirá quanto tempo (em segundos) ele levou para atribuir valores às variáveis utilizando SET, SELECT e SELECT com atribuições múltiplas.

Clique aqui para fazer o download do script de teste.

 

Por Edison Costa
edison@sqlmagazine.com.br

  A diferença entre SET e SELECT    
  Backups no SQL Server    
  Principais Comandos SQL    
  Copiar tabela sem os registros    
  Selecionar grupo de registros    
  Executar rotina na inicialização    
  Introdução ao SQL Server    
     

 

Todos os direitos reservados: DevMedia Group
SQL Magazine - 2004