domingo, 28 de fevereiro de 2010

Trabalhando com o Excel no Sql Server

Para implementar este exemplo, crie uma pasta denominada Sistemas e salve-a diretamente na raiz do sistema operacional. Edite um novo arquivo do Microsoft Excel, salve-o dentro da nova pasta, nomeando uma das planilhas como “Plan1”. Dentro desta planilha, na primeira célula da primeira coluna, coloque o valor name:

Image Hosting

Salve as suas alterações na planilha e execute o código abaixo:

INSERT INTO OPENROWSET('Microsoft.Jet.OLEDB.4.0',  'Excel 8.0;Database=C:\Sistemas\Query.xls;',
'SELECT [NAME] FROM [Plan1$]')
SELECT name FROM sys.objects where type='U'
GO

Abra a planilha, e verifique que agora, a primeira coluna contém o nome de todas as tabelas criadas no banco de dados onde foi executado. Utilizei o sys.objects, para que o exemplo possa ser executado com facilidade em qualquer instância do Sql Server 2005 ou superior. E agora, vamos fazer o caminho inverso e selecionar através de uma query, os dados que inserimos no Excel:

SELECT *
FROM OpenDataSource( 'Microsoft.Jet.OLEDB.4.0',
  'Data Source=c:\sistemas\query.xls;Extended properties=Excel 5.0')...[Plan1$]
Go

Até  a próxima!

sexta-feira, 26 de fevereiro de 2010

Criando um Proxy para execução de JOBs

Criando um Proxy para ser usado como usuário de execução de STEPS de um Job com pacotes do Integration Services.

-- Criação de Proxy       (Criação de script)

USE [msdb]

GO

 

/****** Object:  ProxyAccount [FT5 System Proxy]    Script Date: 02/26/2010 10:52:34 ******/

EXEC msdb.dbo.sp_add_proxy @proxy_name=N'FT5 System Proxy',@credential_name=N'FT5 System Service',

            @enabled=1

 

Ao tentar executar o JOB que estava vinculado ao Proxy, ocorreu a seguinte mensagem:

 

"Grant permission by calling sp_grant_proxy_to_subsystem or sp_grant_login_to_proxy"

 

Foi necessário dar permissão ao Proxy para dois sub-sistemas do Sql Server:

 

--Dando permissão do proxy para os susbsistemas

exec sp_grant_proxy_to_subsystem 1, null, 3, null -- CMD EXEC

 

--

exec sp_grant_proxy_to_subsystem 1, null, 11, null -- SSIS

 

Até a próxima!

 

 

quinta-feira, 25 de fevereiro de 2010

Você sabia que?...

Que é possível verificar que campo está sendo atualizado em uma trigger com a função update? Veja o exemplo abaixo:

 

Create table TesteTrigger(

  codigo int identity(1,1),

  nome varchar(100),

  dt_nascimento datetime

)

go

 

create trigger trgTesteTrigger on TesteTrigger

for insert,update

as

begin

 

  if update(nome)

  begin

      print 'Atualizou o nome'

  end

 

end

 

insert into TesteTrigger(nome) values('Rodrigo')

 

O update abaixo não apresenta mensagem nenhuma:

update testetrigger set dt_nascimento=getdate()

--não apresenta mensagem nenhuma

 

O update abaixo apresenta a mensagem ‘Atualizou o nome’:

update testetrigger set nome=''

--aparece a mensagem 'atualizou o nome'

 

 

Até a próxima!

 

terça-feira, 23 de fevereiro de 2010

JOB Suspended

O problema inicial era que o JOB se colocava em estado ‘sunpended’ imediatamente após o início da execução e no log do job se verificava a mensagem: “use xxx does not have permission to execute job”.

Solicitei aos analistas que alterassem os roles do usuário, adicionando mais permissões.




O JOB continuou com erro porém a mensagem relacionada as permissões não aparecia mais no momento da execução do JOB. A mensagem que começou a se apresentar no JOB History era: “Subsystem could not be loaded. See SQLAGENT.OUT file for details”. Verifiquei o log do SQL Agent (SQLAGENT.OUT), e pesquisei os erros:

Subsistema 'ActiveScripting' não pôde ser carregado (razão: O módulo especificado não foi encontrado)
Subsistema 'CmdExec' não pôde ser carregado (razão: O módulo especificado não foi encontrado)
Subsistema 'snapshot' não pôde ser carregado (razão: O módulo especificado não foi encontrado)
Subsistema 'LogReader' não pôde ser carregado (razão: O módulo especificado não foi encontrado)
Subsistema 'Distribution' não pôde ser carregado (razão: O módulo especificado não foi encontrado)
Subsistema 'Mesclar' não pôde ser carregado (razão: O módulo especificado não foi encontrado)
Subsistema 'QueueReader' não pôde ser carregado (razão: O módulo especificado não foi encontrado)
Subsistema 'ANALYSISQUERY' não pôde ser carregado (razão: O módulo especificado não foi encontrado)
O subsistema 'ANALYSISCOMMAND' não pôde ser carregado (razão: O módulo especificado não foi encontrado)
Subsistema 'DTS' não pôde ser carregado (razão: O módulo especificado não foi encontrado) 

..chegando a esta thread no fórum de suport da Microsoft:
 
Cheguei a conclusão que o problema estava nos paths das DLLs do SQL Server, listadas na tabela msdb.dbo.syssubsystems, que por padrão estão como: "c:\program files..", para o path correto que no caso do servidor em questão era "e:\..." para todos os arquivos.



Realizei um update na tabela, mudando os paths e reiniciei o serviço do SQL Agent. Problema resolvido.

Até a próxima!


quinta-feira, 18 de fevereiro de 2010

Vaga para DBA

VAGA: DBA SQL Server Jr /Pleno
Empresa: HOMINE CONSULTING
Requisitos: Inglês Intermediário/Avançado
Duração: 01 ano
Local: Tamboré/SP

Contato: Patricia Andreolli
Fone:    (55 11) 3383-4004
Cel:      (55 11) 6449-1262
MSN:    patyandreolli@hotmail.com
E-Mail:  patricia.andreolli@homineinfo.com.br
Skype:  patricia.andreolli

SQL Server 2011 - Codename Denali

A Microsoft recentemente anunciou o codename Denali para o que provavelmente será a a versão 2011 do SQL Server. Ainda não se sabe muitos detalhes sobre esta versão, mas Denali é a maior montanha da América do Norte. No passado, o Sql Team da Microsoft já utilizou o codename Kilimanjaro para o SQL 2008.
Fonte: ZDNET.

Função que retorna apenas números de uma STRING

Informado sobre um problema em um processo de ETL, verifiquei que um determinado campo no arquivo de origem, que esperava-se ser numérico, muitas vezes continha caracteres não numéricos como barras, traços ou vírgulas e em algumas situações caracteres não esperados.
Alguns analistas criaram “ajustes” para cada um dos caracteres possíveis:
  ( ISNUMERIC(REPLACE(SUBSTRING(nu_cpf_cnpj, 3, 14),'-','')) = 0
      OR ISNUMERIC(REPLACE(SUBSTRING(nu_cpf_cnpj, 3, 14),'/','')) = 0 ) 

Porém como o número de caracteres inesperados acabou crescendo, e o nível de stress na operação aumentou, pensei em uma solução mais definitiva, através de uma função que retorna apenas os números de um VARCHAR.
create function dbo.f_SomenteNumeros(@valor varchar(25))
      returns varchar(MAX)
as
begin

      declare @resultado varchar(MAX)
      declare @i int
      declare @length int

      select
            @resultado=''
            ,@i=0

      while @i<len(@valor)+1
      begin

            if substring(@valor,@i,1) in ('0','1','2','3','4','5','6','7','8','9')
                  select @resultado=@resultado+substring(@valor,@i,1)

            select @i=@i+1
      end

      return @resultado

end
GO

select dbo.f_SomenteNumeros('a*&%¨*&¨1')
Até a próxima!

Usando o comando OUTPUT

Introduzido no SQL Server 2005 a cláusula OUTPUT pode ser usada em conjunto com os comandos UPDATE, DELETE ou INSERT com facilidade. Isso é feito através do comando OUTPUT que pode acessar as tabelas temporárias, que até o SQL 2000 estavam disponíveis apenas em triggers.
Por este motivo, esta implementação pode ser particularmente útil para substituir triggers que tem como objetivo logs e auditorias. Tenho encontrado muita utilidade para estes comandos em suportes ou deploys de aplicações, seja para determinar se um certo script está realizando assertivamente a sua finalidade.
Veja o exemplo abaixo:
--criando uma tabela de teste
create table teste_output(
  codigo int identity(1,1),
  nome varchar(100),
  origem varchar(100)
      default 'Aplicação'
)
go

--criando uma temp, poderia ser uma física
create table #tmp_teste(
  codigo int,
  nome varchar(100),
  origem varchar(100)
)
go

--inserindo os dados. veja o uso da cláusula output referenciando a tabela virtual inserted
insert into teste_output(nome) output inserted.codigo,inserted.nome,'Output inserted - insert' into #tmp_teste(codigo,nome,origem) values ('Rodrigo')
insert into teste_output(nome) output inserted.codigo,inserted.nome,'Output inserted - insert' into #tmp_teste(codigo,nome,origem) values ('Daniel')
insert into teste_output(nome) output inserted.codigo,inserted.nome,'Output inserted - insert' into #tmp_teste(codigo,nome,origem) values ('Evelise')

--atualizando
update teste_output set origem='Update'
output inserted.codigo,inserted.nome,'Output inserted - update' into #tmp_teste(codigo,nome,origem)


--deletando
delete from teste_output
output deleted.codigo,deleted.nome,'Output deleted - delete' into #tmp_teste(codigo,nome,origem)


--mostrando os resultados
select * from #tmp_teste

--excluindo os objetos
drop table #tmp_teste
drop table teste_output

segunda-feira, 15 de fevereiro de 2010

Site sobre DTS

Para os profissionais que como eu precisam dar suporte e consultoria em pacotes DTS 2000, segue um ótimo link sobre o assunto:

 

http://www.sqldts.com

 

Até a próxima!

quarta-feira, 10 de fevereiro de 2010

Community Webcast: Microsoft Community Day - Noite

Idioma(s):           Português.
Produto(s):        Microsoft SQL Server 2008 R2,Microsoft Visual Studio 2010,Windows Server 2008 R2.
Público(s):          Dev/Programador Pro,Generalista de TI.
Duração:             240 Minutos
Data de Início: sábado, 20 de março de 2010 18:30 Brasília

Visão Geral do Evento
Windows Server 2008 R2: Visão Geral
Será apresentado uma visão sobre o Windows Server 2008 R2 com as novas funções disponíveis nesta edição.
Palestrante: Alexandre Lopes, Consultor Senior especialista na plataforma SQL Server, atua na área de projetos, consultoria e treinamento. Profissional certificado Microsoft (MCT, MCITP, MCTS, MCSE, MCDBA), instrutor de cursos oficiais Microsoft desde 1999 e palestrante em eventos presenciais e à distancia (webcasts) para o Technet Brasil e MSDN Brasil.
SQL Server 2008 R2: Visão Geral
Nesta apresentação será apresentado o SQL Server 2008 R2 e uma visão geral sobre as novidades deste novo release do SQL Server 2008.
Palestrante: Vitor Fava, DBA com grande experiência nas áreas de Banco de Dados e Tecnologia da Informação, atuando no desenvolvimento, implementação, manutenção e suporte de servidores de bancos de dados corporativas de grande porte. Possui as certificações MCP, MCTS, MCITP e MCT em SQL Server 2000/2005/2008. Possue boa vivência em atividades de desenvolvimento e administração de ambientes empresarias utilizando SQL SERVER.
Visual Studio 2010 : Visão Geral
Nesta apresentação será apresentado uma visão geral sobre o Visual Studio 2010 e suas novas funcionalidades.
Palestrante: Agnaldo Diogo dos Santos, Possui mais de 25 anos de experiência na área, tendo sido aprovado em 26 provas (Microsoft e Sun), recebendo as credenciais MCT, 5 MCTS, 4 MCITP, MCPD, MCSE, MCDBA, MCSD, MCP e SCJP. Formado em Tecnologia em Processamento de Dados pela Fatec, foi desenvolvedor, analista e gerente de projetos. Como consultor prestou serviços a empresas como Banco1.Net, Unibanco, AmBev, AMPM Ipiranga e outros.

Interessado? Clique aqui e inscreva-se!

terça-feira, 9 de fevereiro de 2010

Comandos DBCC úteis

DBCC DROPCLEANBUFFERS

Estatísticamente é mais comum que uma query acabou de ser executada, seja executada novamente, do que um nova query seja encaminhada. Por este motivo, todas as querys executadas no Sql Server ficam armazenadas em um cache. Este comando, limpa os buffers do Sql Server.Muito bom para fazer testes mais justos de performance de query e enquanto se está realizando processos de tuning.

Exemplo:

DBCC DROPCLEANBUFFERS

DBCC ERRORLOG

Se você raramente reinicializa o seu servidor, pode notar que um crescimento exagerado no log e demoras para visualização. Este comando DBCC, trunca o seu log.

Exemplo: DBCC ERRORLOG

DBCC INDEXDEFRAG

Introduzido no Sql Server 2000, o index defrag permite evitar a fragmentação lógica dos índices, mas sem realizar locks nos objetos, permitindo aos usuários acesso as tabelas enquanto os comandos estão em execução. Porém, o REBUILD faz a desfragmentação com maior eficiência (entretanto, faz o lock dos objetos).

Exemplo:

DBCC INDEXDEFRAG ('NOME_DO_BANCO','NOME_TABELA','NOME_INDICE')

segunda-feira, 8 de fevereiro de 2010

Conflicting locking hints specified

Hoje, durante um processo de migração de banco de dados Sql 2000 para o 2005, tive que recompilar uma série de procedures no novo ambiente e ao tentar re-compilar uma procedure me deparei com a seguinte mensagem:

Msg 1047, Level 15, State 1, Line 3
Conflicting locking hints specified.

A situação era similar (porém não igual) a esta:

create table teste_locks(
  nome varchar(100)
)
go

insert into teste_locks values ('Rodrigo')
insert into teste_locks values ('Marcelo')
insert into teste_locks values ('Scheidt')
go

delete teste_locks with (rowlock)
from teste_locks with (nolock)

Não sei qual a intenção original do desenvolvedor, mas fui obrigado a dar algumas risadas enquanto olhava o script. Observe no delete, o conflito de conceitos: no delete é informado um rowlock e no from um nolock. Imagino que se executamos um rowlock (lock de linha), não podemos especificar que não teremos lock nenhum, que é o que faz o nolock.

Removi o rowlock e tudo funcionou de novo. Imagino como esta proc foi criada da primeira vez, considerando que esta mensagem aparece no Sql Server 2000.

Até a próxima!


Problemas com restore

Hoje ao tentar realizar o restore de um banco de dados em uma instância 2005, criado no SQL Server 2000, me deparei com a seguinte mensagem:

TITLE: Microsoft SQL Server Management Studio
------------------------------

Restore failed for Server 'MyServer\sql05'.  (Microsoft.SqlServer.Smo)


------------------------------
ADDITIONAL INFORMATION:

System.Data.SqlClient.SqlError: File 'E:\MSSQL$DES\data\base.mdf' is claimed by 'base_Index'(3) and 'base'(1). The WITH MOVE clause can be used to relocate one or more files. (Microsoft.SqlServer.Smo)


O banco de dados original, tinha um arquivo de índices separado, e por algum motivo o DBA decidiu nomear o arquivo de índices com a terminação MDF salvando-o em outro diretório. Ao tentar restaurar, o Sql Server optou por colocar os dois na mesma pasta e por isto a mensagem. Rename no arquivo de índices para *.ndf e problema resolvido.

Até a próxima!




sexta-feira, 5 de fevereiro de 2010

Conectando um pacote DTS 2000 com um Web Service - Script Task

Faz algum tempo, precisei realizar um suporte em um ambiente SQL 2000, mais precisamente em pacotes DTS. A dificuldade era implementar em uma carga, uma validação através de um WEB Service. Se esta solução fosse escrita em Integration Services, minha vida seria muito mais fácil, porém, quem disse que a vida é fácil?

 

Bom, a solução foi desenvolver tudo através de um VB Script dentro de uma Script Task. O código implementado foi bem mais complexo, e estou anexando aqui apenas um Snipet.

 

 

'**********************************************************************

'  Visual Basic ActiveX Script

'************************************************************************

 

Function Main()

 

 

    Dim str_telefones

 

    str_telefones = "123456"

 

    http = CreateObject("Microsoft.XmlHttp")

 

    http.open("GET", "http://enderecowebservice/varPergunta?valor=" + str_telefones + " ", False)

    http.send("")

 

    str_texto = http.responseText

 

    Main = DTSTaskExecResult_Success

End Function

 

 

Mais um usuário feliz!

INSTEAD OF INSERT TRIGGER

Um analista hoje me questionou sobre as instead of triggers, como uma solução para uma tabela, que quando criada não contemplou chaves primárias ou qualquer tipo de controle de redundância. Devido a esse problema, a tabela já apresentava registros duplicados, e uma vez que o problema já existe, é necessário administrá-lo já que não podemos resolvê-lo.

Em busca da solução perfeita...


Uma das soluções possíveis foi a de criar uma instead of trigger. O conceito das instead of trigger, é como o próprio nome diz, substituir a execução do insert,update..  por um outro bloco de código. Ou seja, caso você esteja criando uma INSTEAD OF INSERT trigger, mas você deverá re-fazer o insert dentro da trigger. Parece meio doido? Também achei, mas para o que estávamos precisando, resolveu muito bem.

Depois de estudar um pouco, implementei este exemplo de como utilizar este tipo de trigger. Acompanhe:


CREATE TABLE TESTE_TRG_INSTEAD(
  CODIGO INT,
  NOME VARCHAR(100)
)
GO

ALTER TRIGGER TRG_TESTE_INSTEAD ON TESTE_TRG_INSTEAD
      INSTEAD OF INSERT
AS
BEGIN

      DECLARE @CODIGO INT
      SELECT
            @CODIGO=CODIGO
      FROM
            INSERTED
   
      IF EXISTS(SELECT 0 FROM TESTE_TRG_INSTEAD
                    WHERE CODIGO=@CODIGO)
      BEGIN
           
            RAISERROR ('Código já existe!', 16, 1)
            ROLLBACK TRAN

      END
      ELSE
      BEGIN

            SET NOCOUNT ON   
           
            INSERT INTO TESTE_TRG_INSTEAD
            SELECT CODIGO,NOME FROM INSERTED

            SET NOCOUNT OFF

      END

END
GO

INSERT INTO TESTE_TRG_INSTEAD VALUES (1,'Rodrigo')
INSERT INTO TESTE_TRG_INSTEAD VALUES (2,'Carolina')
INSERT INTO TESTE_TRG_INSTEAD VALUES (3,'Luisa')

Até aí em cima, tudo funciona!


INSERT INTO TESTE_TRG_INSTEAD VALUES (1,'Rodrigo')

Porém o insert aí de cima, gera a mensagem aí de baixo.


Msg 50000, Level 16, State 1, Procedure TRG_TESTE_INSTEAD, Line 16
Código já existe!
Msg 3609, Level 16, State 1, Line 1
The transaction ended in the trigger. The batch has been aborted.

Até a próxima!

quinta-feira, 4 de fevereiro de 2010

Entendo os problemas com o CONCAT_NULL_YIELDS_NULL

Hoje, depois de passar por alguns problemas com os SETs em configurações do SQL Server, fui questionado por um colega sobre por quê tantos problemas com estas configurações.

Execute os scripts abaixo no seu Sql Server e entenda por quê:

SET CONCAT_NULL_YIELDS_NULL ON;
SELECT 'Rodrigo ' + NULL + 'Ribeiro';
GO

SET CONCAT_NULL_YIELDS_NULL OFF;
SELECT 'Rodrigo ' + NULL + 'Ribeiro';
GO

quarta-feira, 3 de fevereiro de 2010

Select em view indexada não usa índices

Hoje ao otimizar uma query, tive a brilhante idéia de criar uma view indexada. Criei a view com o cuidado de utilizar as tags para evitar os erros de ARITH ABORT, e lá fui eu. Alterei a procedure que antes usava a tabela diretamente e direcionei o select para a view recém-criada. Comentei o cabeçalho da view para analisar o execution plan e para minha surpresa, a query estava usando uma referência direta a tabela, ignorando a view e os índices recém-criados.

Pesquisando na net, encontrei um artigo falando sobre o problema e descobri uma nova query hint: WITH (NOEXPAND)

Basicamente, o query processor estava analisando a view que eu criei e a considerando um view comum e a expandindo, acessando diretamente a tabela. Adicionando o hint NOEXPAND, a expansão da query é evitada e a query é acessada, e um ótimo INDEX SEEK foi utilizado para a query.

Mais um usuário feliz!

Até a próxima!


RESTORE cannot process database 'ProjetoETL' because it is in use by this session. It is recommended that the master database be used when performing this operation

Hoje um colega de trabalho me consultou sobre um erro, ao tentar restaurar um backup em cima de uma base já existente.

Segue o print:



Quando conectamos em um banco de dados através do Management Studio, o aplicativo se conecta ao banco de dados default do usuário utilizado para o login, o que podemos visualizar através da procedure SP_WHO. A mensagem acima, informa que não é possível o restore do banco de dados, pois a sessão atual já coloca o banco de dados em uso.

Sugeri que o banco default do usuário fosse alterado para o MASTER, e não para o banco que estava sendo restaurado.

Mais um usuário feliz!


segunda-feira, 1 de fevereiro de 2010

Você sabia que?...

O Sql Server 2008 já vem com compressão de backups?

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]