T-SQL Tips - TOP Não é Tunning

/*

      Caros Senhores, Senhoras e Senhoritas DBAs!

      Vamos falar hoje de uma situação bem comum.

      Voce está lá tranquilão analisando o SQL Profiler quando assustadoramente vem aquela query do demônio

      "SELECT TOP 1 * FROM QualquerTabelaGigante ORDER BY QualquerCampo"     com milhares de reads e o login name do desenvolvedor vizinho.

      Você respira fundo e tranquilamente se vira para ele e diz:

            - "Amigão, essa query é pesada hein? Ta precisando mesmo?"

      e ele responde todo tranquilo:

            - "Na verdade só estou checando os dados, mas relaxa que eu coloquei um TOP 1."

      Ser DBA é Alegria de viver!

      Inspirado nessa situação resolvi escrever esse post para demonstrar ao amigo desenvolvedor que: Queridão, TOP NÃO É TUNNING!!

*/   


-- Vamos começar populando uma tabelinha com 100.000 registros
-- (e umas  446 pages)


CREATE TABLE TabelaExemplo (

       CampoID int  identity(1,1) CONSTRAINT PK_TabelaExemplo PRIMARY KEY,

       CampoTexto varchar(100),

       CampoData datetime

       )


SET IDENTITY_INSERT TabelaExemplo ON

INSERT INTO TabelaExemplo (CampoID,CampoTexto,CampoData )

SELECT
       ROW_NUMBER() OVER(ORDER BY Pop1.Valor ) CampoID,
       CAST(ROW_NUMBER() OVER(ORDER BY Pop1.Valor ) as varchar(6)) + '_Texto' CampoTexto,
       DATEADD (HOUR,-1 * CAST(ROW_NUMBER() OVER(ORDER BY Pop1.Valor ) as varchar(6)),GETDATE()) CampoData
FROM
       (VALUES (1),(2),(3),(4),(5),(6),(7),(8),(9),(10)) Pop1 (Valor)
CROSS JOIN   (VALUES (1),(2),(3),(4),(5),(6),(7),(8),(9),(10)) Pop2 (Valor)
CROSS JOIN   (VALUES (1),(2),(3),(4),(5),(6),(7),(8),(9),(10)) Pop3 (Valor)
CROSS JOIN   (VALUES (1),(2),(3),(4),(5),(6),(7),(8),(9),(10)) Pop4 (Valor)
CROSS JOIN   (VALUES (1),(2),(3),(4),(5),(6),(7),(8),(9),(10)) Pop5 (Valor)

SET IDENTITY_INSERT TabelaExemplo OFF

-- Ligamos as estatisticas de IO para termos o retorno de paginas lidas

SET STATISTICS IO ON


-- Vamos começar com a consulta simples. Um TOP 1 limpo na tabela exemplo

SELECT TOP 1 * FROM TabelaExemplo


(1 row(s) affected)

Table 'TabelaExemplo'. Scan count 1, logical reads 2


/*

Faz parecer que o desenvolvedor tinha razão, afinal ele leu apenas duas pages de maneira rápida e econômica.
      Mas ai está a armadilha. Vamos colocar um Where para o campo texto da ultima linha da tabela.

*/



SELECT  TOP 1 * FROM TabelaExemplo
WHERE CampoTexto = '100000_Texto'

(1 row(s) affected)
Table 'TabelaExemplo'. Scan count 1, logical reads 447


/*

      Basicamente, o SQL varreu toda a tabela até encontrar um registro que satisfizesse a consulta. No primeiro caso ele encontrou logo na primeira linha (leu a Page do Indice e leu a leaf-page).

      Já no segundo ele só foi encontrar a página no ultimo registro conforme podemos ver no Execution Plan

*/

 

/*
      Ou seja, basta um WHERE para quebrarmos o falso tunning e termos uma consulta pesada novamente.

      Um simples indice no CampoTexto reduz bastante a nossa leitura.

*/


CREATE NONCLUSTERED INDEX IX_Texto ON TabelaExemplo (CampoTexto)

SELECT TOP 1 * FROM TabelaExemplo WHERE CampoTexto = '100000_Texto'


Table 'TabelaExemplo'. Scan count 1, logical reads 5




/*

      Como o Indice já está ordenado ele pode com pouca leituras te devolver um único registro dentro da condição e ir buscar no clustered index o restante da informação.

      Mais um exemplo agora com o campo data
*/


-- Na query abaixo, a condição WHERE filtra as 24 primeiras linhas da tabela* e colocamos um top 24, ou seja, igual a quantidade filtrada

*ajuste a data para a data que você está lendo o post


SELECT  TOP 24 * FROM TabelaExemplo
WHERE CampoData  BETWEEN  '2012-01-04' AND '2012-01-05'

(24 row(s) affected)
Table 'TabelaExemplo'. Scan count 1, logical reads 7



/*

Novamente o resultado é satisfatório, pois ele encontrou as 24 linhas e parou a varredura.
      Mas basta mudarmos o top d 24 para 25 que a coisa já não é mais a mesma.
*/


SELECT  TOP 25 * FROM TabelaExemplo
WHERE CampoData  BETWEEN  '2012-01-04' AND '2012-01-05'

(24 row(s) affected)
Table 'TabelaExemplo'. Scan count 1, logical reads 447


/*

      A tabela foi completamente lida porque o SQL não podia garantir que não existia a 25ª linha.

      Com ORDER BY a coisa também muda de figura. Vamos voltar o TOP 24 e ordenar para o campo Data

*/


SELECT  TOP 24 * FROM TabelaExemplo

WHERE CampoData  BETWEEN  '2012-01-04' AND '2012-01-05'

ORDER BY CampoData



(24 row(s) affected)

Table 'TabelaExemplo'. Scan count 1, logical reads 447

  

/*

      Boom! Mais uma vez, leitura completa da tabela pois não existe como o SQL determinar que as 24 primeiras linhas são suficientes para o retorno correto.


      Tudo isso da pra resolver com indice, mas indice é Tunning e conforme queriamos demonstrar Queridão, TOP NÃO É TUNNING!!

Obrigado e Abraços!!

Felipe Antunes
email:fjantunes@gmail.com
Twitter:@felipe_store
 

*/

T-SQL Tips - Movendo pacotes SSIS entre servidores

/*
      Olá Senhores, Senhoras e Senhoritas DBAs!!!!
     
      Recentemente tive a necessidade de migrar mais de duzendos pacotes de Integration entre um servidor e o outro. A experiencia que no inicio parecia bem dolorosa (dado que os pacotes estavam em solutions diferentes), se mostrou bem interessante quando resolvi utilizar o dtutil! 
      Um aplicativo tipo prompt que serve justamente para gerenciar esse tipo de situação.
     
      Depois de brigar um pouco com a Syntaxe, cheguei a uma solução que gostaria muito de compartilhar com vocês.
     
      Espero que te Ajude!! E vamos por a mão na massa!
     
     
     
      Essa solução está dividida em x partes
     
      1 - Definir os comandos necessários do dtutil.
      2 - Criar consultas para gerar os comandos no estado do servidor atual.
      3 - Colocar tudo em um bom e velho .BAT e executar.
     


--Parte 1 - O comando


      Particularmente achei a syntaxe do dtutil bem chatinha (como voce pode ver no link do books online abaixo). E após uma certa briga chegamos as
      seguintes syntaxes
     
      a) Comando para recriar a estrutura de pastas e subpastas no servidor destino
     
      dtutil /FC SQL;"/PastaPai/";"SubPasta" /SourceS ServidorDestino
     
           
      b) Comando para copiar os pacotes do servidor origem para o servidor destino
     
      dtutil /SQL "PastaPai/SubPasta/Pacote" /SourceS ServidorOrigem /COPY SQL;"PastaPai/SubPasta/Pacote" /DestS ServidorDestino
     
     
BOOKS ONLINE: ms-help://MS.SQLCC.v10/MS.SQLSVR.v10.en/s10is_5techref/html/6c7975ff-acec-4e6e-82e5-a641e3a98afe.htm
--Parte 2 - Consultas para gerar os comandos

      a) A primeira consulta é uma query recursiva que usa as tabelas do MSDB para criar na ordem as pastas e subpastas.
           
      ATENÇÃO: As tabelas Data Collector e Maintenance Plans são padrão em todas as instalações. Por isso remova-as da lista gerada.
     
      Obs: Para entender maiores informações sobre consultas recursivas, leia esse post aqui.

*/   

      use msdb
      DECLARE @ServidorDestino varchar(100) = 'Coloque aqui a instancia Destino'
     
      ;WITH CTE_Recursiva (parentfolder,foldername,folderid,Level)
      as
      (
      SELECT
            CAST('/' as sysname) parentfolder,
            foldername folder,
            folderid ,
            0 Level

      FROM
            sysssispackagefolders
      WHERE
            parentfolderid = '00000000-0000-0000-0000-000000000000'
      UNION ALL
      SELECT
            CAST(C.parentfolder + C.foldername +'/'  as sysname)   parentfolder,
            f.foldername folder,
            f.folderid ,
            C.Level + 1 Level
      FROM
            sysssispackagefolders f
      INNER JOIN CTE_Recursiva C on C.folderid = f.parentfolderid     
      )
      SELECT
            'dtutil /FC SQL;"' + parentfolder + '";"' + foldername + '" /SourceS ' + @ServidorDestino  Comando
      FROM
            CTE_Recursiva
      ORDER BY
            Level,
            parentfolder           


/*
           
            b) A segunda consulta (tambem recursiva) gera os comandos que copiam os pacotes entre um servidor e outro.
           
            ATENÇÃO: Da mesma maneira que a consulta anterior, tome cuidado para remover os pacotes da Data Collector e Maintenance Plans (nesse ultimo só se voce quiser)
           
*/



GO
use msdb
DECLARE @ServidorOrigem varchar(21) = 'Coloque aqui a instancia de Origem'
DECLARE @ServidorDestino varchar(21) = 'Coloque aqui a instancia Destino'

;WITH CTE_Recursiva (Package,FolderID)
AS
(
SELECT
      CAST(name as sysname) Package,
      folderid
FROM
      sysssispackages
UNION ALL
SELECT
      CAST(f.foldername + '/' + C.Package as sysname) Package ,
      f.parentfolderid folderid
FROM
      sysssispackagefolders f
INNER JOIN CTE_Recursiva C on C.folderid = f.folderid           
)
SELECT
      'dtutil /SQL "' +
      RIGHT(Package,LEN(Package) - 1)
      + '" /SourceS ' + @ServidorOrigem + ' /COPY SQL;"'
      + RIGHT(Package,LEN(Package) - 1) 
      + '" /DestS ' + @ServidorDestino Comando
FROM CTE_Recursiva
WHERE FolderID IS NULL
ORDER BY
      Comando
     
/*

--Parte 3 - Gerar o BAT.

      E pronto caro leitor, mude a saida dos comandos para Texto e copie esses comandos em um arquivo .BAT.
     
      E é só executar!



      Espero que tenha te ajudado, porque isso me economizou algumas horas de import/export mecanico e como diria Super Sam: - Time is money!!
     
      Se tiver uma critica, duvida ou sugestão por favor me escreva no fjantunes@gmail.com, twite no felipe_jose@store.com.br ou me mande perguntas
      no http://www.formspring.me/agoradba
     
      Abraços!!!
     
      Felipe Antunes
      Keen on SQL Server!!!!

*/