Verificar a versão dos dados de uma tabela por CHECKSUM

Olá pessoas!

Recentemente, encontrei um script bem interessante para realizar o CHECKSUM de todos os dados de todas as colunas de uma tabela, fiz algumas alterações mínimas para reduzir o código do script original, e ficou desta forma:

DECLARE @schema_name sysname
SET @schema_name = 'dbo'

DECLARE @table_name sysname
SET @table_name = 'Nome da tabela'

DECLARE @column_list VARCHAR(MAX)

SELECT @column_list =
	COALESCE(@column_list + ', ', '') + QUOTENAME(COLUMN_NAME)
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = @table_name
AND TABLE_SCHEMA = @schema_name

DECLARE @sql VARCHAR(MAX)
SET @sql = 'SELECT CHECKSUM_AGG(CHECKSUM({@column_list})) FROM [{@schema_name}].[{@table_name}]'

SET @sql = REPLACE(@sql, '{@column_list}', @column_list)
SET @sql = REPLACE(@sql, '{@schema_name}', @schema_name)
SET @sql = REPLACE(@sql, '{@table_name}', @table_name)

EXEC (@sql)

Esta consulta retorna o CHECKSUM de todos os dados da tabela de forma rápida (na maioria das vezes), e permite verificar por meio deste CHECKSUM se a tabela foi ou não alterada, sendo uma ótima alternativa comparar alterações em tabelas similares em bancos distintos e criar uma estrutura simples de versionamento.

A única limitação que encontrei para este script é a necessidade de um tratamento diferenciado para colunas XML:

Msg 8116, Level 16, State 4, Line 1
Argument data type xml is invalid for argument X of checksum function.

Referências

Script original:
http://stackoverflow.com/questions/1560306/calculate-hash-or-checksum-for-a-table-in-sql-server

Função CHECKSUM:
http://msdn.microsoft.com/en-us/library/ms189788.aspx

Função CHECKSUM_AGG:
http://msdn.microsoft.com/en-us/library/ms188920.aspx

Artigos relacionados:

Comparando a estrutura de tabelas diferentes
http://sqlfromhell.wordpress.com/2010/01/09/query-from-hell-comparando-a-estrutura-de-tabelas-diferentes/

Red Gate – Overview – SQL Data Compare
http://sqlfromhell.wordpress.com/2011/05/23/red-gate-overview-sql-data-compare/

Visual Studio 2010 – Data Compare
http://sqlfromhell.wordpress.com/2011/01/27/visual-studio-2010-data-compare/

Aniversário de 3 anos do SQL From Hell!!

Há três anos iniciou o SQL From Hell, sem muita perspectiva do que iria se tornar, e hoje, me surpreendo que um blog escrito em português sobre assuntos técnicos bem específicos, tenha conseguido ajudar tantas pessoas, além das várias críticas e elogios que tem recebidos.

Procurei nestes três anos tratar assuntos que eu gosto, como SMO, SQL CLR, Service Broker, FOR XML e XQuery, também artigos que escrevi com o objetivo de disponibilizar tutoriais para ajudar no dia-a-dia dos novos DBAs, como habilitar SQL Authentication, habilitar conexão remota, alterar as portas de conexão do SQL Server, usuários orfãos, scripts para validar CNPJ, entender JOINs, scripts de cidades, scripts para o dicionários de dados e vários outros assuntos, que algumas vezes não estavam diretamente ligados com SQL Server, como os tutoriais de NoSQL e alguns artigos sobre gestão e carreira.

Obrigado todos aqueles que contribuíram com esta iniciativa!!!

Monitorando Deadlocks do SQL Server com SMO

Na semana passada vimos um exemplo de Deadlock com SQL Server, e para monitorar e rastrear eventos deste gênero podemos optar por várias alternativas, dentre elas a utilização de Trace Flags e SQL Server Profiler, e também podemos monitorar de forma bem transparente com os eventos do SMO.

De uma maneira bem simples, o código abaixo monitora os eventos de Deadlock de uma determinada instancia do SQL Server, e quando ocorre uma Deadlock, ele informa os SPID que estão relacionadas a este evento, e gera um Deadlock File para obter maiores detalhes sobre o deadlock.

using System;
using System.IO;
using System.Linq;
using System.Xml.Linq;
using Microsoft.SqlServer.Management.Smo;

namespace DemoSmo
{
    static class DemoEvent3
    {
        public static void Run()
        {
            const string serverName = @".\SQLEXPRESS";

            var server = new Server(serverName);

            var eventSet = new ServerTraceEventSet(
                ServerTraceEvent.LockDeadlock,
                ServerTraceEvent.LockDeadlockChain,
                ServerTraceEvent.DeadlockGraph
                );

            server.Events.SubscribeToEvents(eventSet, OnDeadlock);

            server.Events.StartEvents();

            Console.ReadKey();

            server.Events.StopEvents();

            server.Events.UnsubscribeFromEvents(eventSet);
        }

        private static void OnDeadlock(object sender, ServerEventArgs e)
        {
            switch (e.EventType)
            {
                case EventType.DeadlockGraph:
                    DeadlockGraph(e);
                    break;
                case EventType.LockDeadlockChain:
                    LockDeadlockChain(e);
                    break;
                case EventType.LockDeadlock:
                    Console.WriteLine("Data/Hora: {0:dd/MM/yyyy HH:mm}", e.PostTime);
                    Console.WriteLine("Vítima escolhida: SPID {0}", e.Spid);
                    break;
            }

            Console.WriteLine();
        }

        private static void LockDeadlockChain(ServerEventArgs e)
        {
            var textData = e.Properties.FirstOrDefault(prop => prop.Name == "TextData");

            if (textData == null) return;

            Console.WriteLine("Data/Hora: {0:dd/MM/yyyy HH:mm}", e.PostTime);
            Console.WriteLine(((string)textData.Value).TrimEnd());
        }

        private static void DeadlockGraph(ServerEventArgs e)
        {
            var path = string.Format(@"C:\TEMP\{0}.xdl", Guid.NewGuid());

            var textData = e.Properties.FirstOrDefault(prop => prop.Name == "TextData");

            if (textData == null) return;

            var xml = XDocument.Parse(((string)textData.Value))
                .Elements("TextData")
                .Elements("deadlock-list")
                .FirstOrDefault();

            if (xml == null) return;

            using (var file = new StreamWriter(path)) file.Write(xml.ToString(SaveOptions.DisableFormatting));

            Console.WriteLine("Data/Hora: {0:dd/MM/yyyy HH:mm}", e.PostTime);
            Console.WriteLine("Arquivo gerado: {0}", path);
        }
    }
}

A execução do código:

Monitorando Deadlock com SMO

Os arquivos gerados:

Deadlock Files

E um arquivo de deadlock file gerado pelo SMO aberto no SQL Server Management Studio, possuindo informações sobre os processos, os objetos em lock e a query que foi executada.

Deadlock File

Assim terminamos os artigos sobre SMO, espero que tenham gostado de conhecer um pouco mais sobre esta feature.

Exemplo de Deadlock no SQL Server

Deadlock acontece quando dois ou mais processos são impedidos de prosseguir pois um estar bloqueando o outro, como exemplo, duas pessoas estão querendo usar o mesmo telefone para ligar para números diferentes, enquanto uma delas não ceder sua vez, nenhuma irá conseguir ligar.

No nível de transações de banco de dados não é muito diferente, teremos duas tabelas em nosso banco de dados:

CREATE TABLE Pagamentos (ID INT PRIMARY KEY)
GO
CREATE TABLE Contas (ID INT PRIMARY KEY)
GO

Agora, imagine que um cliente, o senhor 52, irá abrir uma transação para inserir um registro na tabela de pagamentos:

BEGIN TRAN
GO
INSERT INTO Pagamentos VALUES(1)
GO

E ao mesmo tempo, outro cliente, o senhor 55, irá inserir um registro na tabela de contas:

BEGIN TRAN
GO
INSERT INTO Contas VALUES(1)
GO

Até ai, tudo bem, as transações estão abertas, o senhor 55 e o senhor 52 estão utilizando tabelas totalmente diferentes. Mas o senhor 55 lembra que além de inserir um registro na tabela de contas, ele precisa inserir um registro na tabela de pagamentos na mesma transação, assim ele terá que esperar que o senhor 52 termine a transação dele primeiro.

INSERT INTO Pagamentos VALUES(1)
GO

Executando...

Por enquanto nada crítico, é só um cliente esperando o outro terminar.

Mas o senhor 52 também inventa de querer inserir um registro na tabela de Contas:

INSERT INTO Contas VALUES(1)
GO

Dai acontece o Deadlock, o senhor 52 esperando o senhor 55 terminar a transação dele, e o senhor 55 esperando o senhor 52. Como se trata de uma Deadlock bem simples, o SQL Server vai rapidamente escolher qual dos dois clientes tem a preferência, e por consequência definirá sua vítima, que é o cliente que terá que morrer para que aquele que possui preferência consiga terminar seu processo.

E assim, no meu cenário, senhor 52 foi brutalmente assassinado (a transação sofre um ROLLBACK pelo próprio SQL Server, de forma que todos os rastros da vítima sejam apagados):

RIP

Msg 1205, Level 13, State 47, Line 4
Transaction (Process ID 52) was deadlocked on lock resources with another process and has been chosen as the deadlock victim. Rerun the transaction.

E em homenagem a todos os processos que diariamente são vítimas de Deadlock:

Em homenagem a todos os processos que diariamente são vítimas de Deadlock

|| EDIT 2012-03-29
|| Via: http://pessoalex.wordpress.com/2012/03/29/exemplo-de-deadlock-no-sql-server/

Deadlock

Capturando eventos de um determinado bancos de dados com .NET via SMO sem Triggers ou Trace

Assim como explicado no artigo da semana passada, o monitoramento de eventos do SMO pode capturar eventos tanto no nível mais macro do SQL Server, quanto nos níveis mais específicos, como eventos relativos a tabelas ou outras estruturas do banco de dados.

Quem viu o artigo da semana passada irá perceber que não há muita diferença para capturar eventos de objetos específicos do SQL Server, pois as diferenças básicas são os objetos aos quais se associam os eventos (eventos de banco de dados, se associa a um objeto Database, eventos de instancia, a um objeto Server) e o grupo de eventos (EventSet) a ser utilizado para identificar quais os eventos que serão monitorados.

No exemplo a seguir veremos como implementar a captura de eventos de um determinado banco de dados:

using System;
using System.Linq;
using System.Xml.Linq;
using Microsoft.SqlServer.Management.Smo;

namespace DemoSmo
{
    static class DemoEvent2
    {
        public static void Run()
        {
            // Definindo um grupo de eventos a serem monitorados
            var dbEventSet = new DatabaseEventSet(DatabaseEvent.CreateTable);

            // Relacionando os eventos de um determinado banco de dados
            //   e definindo o método OnCreate para o tratamento destes eventos
            const string serverName = @".\SQLEXPRESS";

            var server = new Server(serverName);

            #region Criando um banco de dados

            var db1 = new Database(server, "NovoBanco");

            db1.Create();

            #endregion

            var database = server.Databases["NovoBanco"];

            database.Events.SubscribeToEvents(dbEventSet, OnCreate);

            // Iniciando a captura dos eventos
            database.Events.StartEvents();

            #region Criando uma tabela no banco de dados criado

            Console.ReadKey();

            var table1 = new Table(db1, "NovaTabela", "dbo");

            table1.Columns.Add(new Column(table1, "Codigo", DataType.Int) { Nullable = false });

            table1.Create();

            #endregion

            // Terminando a captura dos eventos

            Console.ReadKey();

            database.Events.StopEvents();

            database.Events.UnsubscribeFromEvents(dbEventSet);

            #region Excluindo o banco de dados criado

            db1.Drop();

            #endregion
        }

        private static void OnCreate(object sender, ServerEventArgs e)
        {
            // Exibindo o SPID e a data do evento
            Console.WriteLine("SPID: {0}", e.Spid);
            Console.WriteLine("Data: {0:dd/MM/yyyy HH:mm}", e.PostTime);

            // Exibindo informações sobre o que foi executado
            Console.WriteLine("Foi criada uma tabela!");

            // Relacionando outras propriedades que serão exibidas

            var showProperties = new[] { "DatabaseName", "LoginName" };

            var properties = e.Properties.Where(prop => showProperties.Contains(prop.Name));

            foreach (var prop in properties)
            {
                Console.WriteLine("{0}: {1}", prop.Name, prop.Value);
            }

            // Exibindo o comando que acionou o evento

            var command = e.Properties.FirstOrDefault(prop => prop.Name == "TSQLCommand");

            if (command != null)
            {
                var xml = XDocument.Parse((string)command.Value)
                    .Elements("TSQLCommand")
                    .Elements("CommandText")
                    .FirstOrDefault();

                if (xml != null)
                {
                    Console.WriteLine("CommandText: {0}", xml.Value);
                }
            }

            Console.WriteLine();
        }
    }
}

E como resultado deste exemplo:

Aqueles que gostariam de saber como funciona a implementação de eventos de notificação, podem encontrar no link abaixo referências de como implementá-los com T-SQL, no qual conhecimento de Service Broker será indispensável:
http://msdn.microsoft.com/en-us/library/ms178080.aspx

Capturando eventos do SQL Server com .NET via SMO sem Triggers ou Trace

Neste artigo de número 300, vermos uma funcionalidade bem interessante do SMO, que permite capturar eventos do SQL Server, seja em nível de instância, de banco de dados ou até no nível de tabela, permitindo monitorar criação e alterações de estruturas e configurações do SQL Server sem a necessidade de usar triggers ou trace, tudo por meio do serviço de notificações de eventos do Service Broker. Mas se você não “manja” de Service Broker, fique tranquilo, o SMO permite a utilização desta funcionalidade de forma bem transparente.

Neste exemplo, veremos como capturar/monitorar eventos de criação de banco de dados e de tabelas com o SMO:

using System;
using System.Linq;
using System.Xml.Linq;
using Microsoft.SqlServer.Management.Smo;

namespace DemoSmo
{
    static class DemoEvent1
    {
        public static void Run()
        {
            // Definindo um grupo de eventos a serem monitorados
            var eventSet = new ServerEventSet(ServerEvent.CreateDatabase, ServerEvent.CreateTable);

            // Relacionando os eventos a uma instância do SQL Server
            //   e definindo o método OnCreate para o tratamento destes eventos
            const string serverName = @".\SQLEXPRESS";

            var server = new Server(serverName);

            server.Events.SubscribeToEvents(eventSet, OnCreate);

            // Iniciando a captura dos eventos
            server.Events.StartEvents();

            Console.ReadKey();

            // Executando alguns comandos para testar se os eventos estão sendo monitorados

            const string query = @"
USE [master]
GO

CREATE DATABASE [NovoBanco]
GO

USE [NovoBanco]

CREATE TABLE [NovaTabela] (CODIGO INT)
GO

USE [master]
GO

DROP DATABASE [NovoBanco]
GO";

            server.ConnectionContext.ExecuteNonQuery(query);

            // Terminando a captura dos eventos

            Console.ReadKey();

            server.Events.StopEvents();

            server.Events.UnsubscribeFromEvents(eventSet);
        }

        private static void OnCreate(object sender, ServerEventArgs e)
        {
            // Exibindo o SPID e a data do evento
            Console.WriteLine("SPID: {0}", e.Spid);
            Console.WriteLine("Data: {0:dd/MM/yyyy HH:mm}", e.PostTime);

            // Exibindo informações sobre o que foi executado
            switch (e.EventType)
            {
                case EventType.CreateDatabase:
                    Console.WriteLine("Foi criado um banco de dados!");
                    break;
                case EventType.CreateTable:
                    Console.WriteLine("Foi criada uma tabela!");
                    break;
            }

            // Relacionando outras propriedades que serão exibidas

            var showProperties = new[] { "DatabaseName", "TableName", "LoginName" };

            var properties = e.Properties.Where(prop => showProperties.Contains(prop.Name));

            foreach (var prop in properties)
            {
                Console.WriteLine("{0}: {1}", prop.Name, prop.Value);
            }

            // Exibindo o comando que acionou o evento

            var command = e.Properties.FirstOrDefault(prop => prop.Name == "TSQLCommand");

            if (command != null)
            {
                var xml = XDocument.Parse((string)command.Value)
                    .Elements("TSQLCommand")
                    .Elements("CommandText")
                    .FirstOrDefault();

                if (xml != null)
                {
                    Console.WriteLine("CommandText: {0}", xml.Value);
                }
            }

            Console.WriteLine();
        }
    }
}

E como os eventos serão exibidos neste exemplo:

Capturando eventos do SQL Server com .NET via SMO

Próxima semana, eu pretendo demonstrar como capturar/monitorar eventos em um nível mais específico, e na última semana deste mês, veremos como monitorar alguns eventos que vão além de alterações estruturais do banco de dados.

Script SQL de cidades e estados do Brasil, Argentina e Estados Unidos

Um problema chato quando se inicia muitos projetos internacionais é o fato de ter que ficar caçando cidades/estados dos países de atuação do projeto. Pelo menos para resolver esta questão estou disponibilizando alguns links para os scripts que encontrei pela internet:

Scripts de Cidades e Estados do Brasil:
http://sqlfromhell.wordpress.com/2009/05/21/cidades-e-estados/
(CSV e SQL originalmente para SQL Server)

Scripts de Cidades e Estados da Argentina:
http://kerzek.blogspot.com/2008/04/script-sql-de-provincias-y-localidades.html
(SQL originalmente para SQL Server, mas é possível migrar para outro banco de dados com um pouco de trabalho)

Scripts de Cidades e Estados dos Estados Unidos:
http://www.farinspace.com/us-cities-and-state-sql-dump/
(Originalmente para MySQL, mas é possível adaptar para outros bancos de dados)

Para uma relação mais geral de cidades por país:
http://geolite.maxmind.com/download/worldcities/
(Formato compatível com CSV, os países são identificados por ISO 3166)

Quem tiver outras fontes de dados e quiser compartilhar, fique a vontade para colocar os links destas fontes nos comentários.

Criando um arquivo de trace com .NET via SMO

Uma prática rápida para otimizar um ou vários bancos de dados é a criação de um arquivo de Trace por meio do SQL Server Profiler e utilizar este arquivo como carga para o Database Engine Tuning Advisor (DTA). O DTA, por sua vez, gerará uma relação de índices e estatísticas para os bancos de dados baseado no arquivo utilizado como carga.

Para a criação deste arquivo de trace, se utiliza normalmente o SQL Server Profiler com o template Tuning. Mas também podemos utilizar as bibliotecas do SMO para gerar este arquivo trace, como veremos a seguir.

Em relação a utilização do SMO, da mesma forma que utilizamos um objeto TraceServer como para capturar as consultas executadas no SQL Server, este objeto também será utilizado neste exemplo para o mesmo fim, só que em conjunto de um objeto TraceFile que escreverá a captura em um arquivo trace.

Então vamos ao código:

using System;
using System.Threading;
using Microsoft.SqlServer.Management.Common;
using Microsoft.SqlServer.Management.Trace;

namespace DemoSmo
{
    static class DemoTrace2
    {
        // Caminho do template de Tuning
        private const string TuningFile =
            @"C:\Program Files (x86)\Microsoft SQL Server\100\Tools\Profiler\Templates\Microsoft SQL Server\100\Tuning.tdf";
        
        /// <summary>
        /// Ref.: http://technet.microsoft.com/en-US/library/ms345134.aspx
        /// </summary>
        public static void Run()
        {
            // Caminho onde será salvo o arquivo de trace
            var file = string.Format(@"C:\Temp\Trace{0:yyyy-MM-dd HH-mm}.trc", DateTime.Now);

            var conn = new SqlConnectionInfo { ServerName = @"DALILAH" };

            // Criando o objeto TraceServer
            var traceReader = new TraceServer();

            // Iniciando a leitura do trace a partir do template Tuning do SQL Server Profiler
            traceReader.InitializeAsReader(conn, TuningFile);

            // Criando o objeto TraceFile
            var traceFile = new TraceFile();

            // Iniciando a escrita da captura no arquivo de trace
            traceFile.InitializeAsWriter(traceReader, file);

            // Executando a escrita dos traces
            var ts = new Thread(() => { while (traceFile.Write()) { } });

            ts.Start();

            // Esperar 30 segundos
            Thread.Sleep(30000);

            // Terminando a escrita do trace
            traceFile.Close();

            // Terminando a captura do trace
            traceReader.Close();
        }
        
    }
}

E o nosso resultado:

Arquivo de trace gerado com .NET via SMO

Capturando consultas executadas no SQL Server (Trace) com PowerShell via SMO

Diferente do artigo original “Capturando consultas executadas no SQL Server (Trace) com .NET via SMO”, não consegui utilizar no PowerShell Threads ou Jobs (Jobs do PowerShell e não do SQL Server) para criar uma estrutura de timeout simples para este exemplo.

Assim optei por limitar a quantidade de linhas geradas pelo Trace (no caso 10 linhas), ao invés de criar um timeout com Get-Date (que vou demonstrar no exemplo da próxima semana).

Sem mais, segue o script para trabalhar com Trace no SQL Server com PowerShell:

# Referências
[void][system.reflection.assembly]::loadfrom("C:\Program Files\Microsoft SQL Server\100\SDK\Assemblies\Microsoft.SqlServer.ConnectionInfo.dll")
[void][system.reflection.assembly]::loadfrom("C:\Program Files\Microsoft SQL Server\100\SDK\Assemblies\Microsoft.SqlServer.ConnectionInfoExtended.dll")

$template = "C:\Program Files (x86)\Microsoft SQL Server\100\Tools\Profiler\Templates\Microsoft SQL Server\100\Standard.tdf"

$conn = new-object Microsoft.SqlServer.Management.Common.SqlConnectionInfo

$conn.ServerName = ".\SQLEXPRESS"

#Criando o objeto TraceServer
$traceReader = new-object Microsoft.SqlServer.Management.Trace.TraceServer

#Iniciando a leitura do trace a partir do template Standard do SQL Server Profiler
$traceReader.InitializeAsReader($conn, $template)

$line = 0

#Lê até 10 comandos executados (enquanto $line < 10)
while ($traceReader.Read() -eq $TRUE -and $line -lt 10)
{
    $name = $traceReader.GetValue($traceReader.GetOrdinal("EventClass"));

    if ($name.Equals("SQL:BatchCompleted") -or $name.Equals("RPC:Completed")) {

        $spid = $traceReader.GetValue($traceReader.GetOrdinal("SPID"))

        $query = $traceReader.GetValue($traceReader.GetOrdinal("TextData"))

        $duration = $traceReader.GetValue($traceReader.GetOrdinal("Duration"))

        "-- SPID: $spid | Duration: $duration"

        "`n"

        Write-Host $query

        "`n `n"

        $line++
    }
}

#Fechando o trace de leitura
$traceReader.Close()

Capturando consultas executadas no SQL Server (Trace) com .NET via SMO

Como visto na semana passada, é possível utilizar o SQL Server Profiler para identificar as consultas que são executadas em uma instancia do SQL Server, além de outros dados, como o tempo de execução, quem esta executando e etc…

Mas seria possível automatizar este processo com SMO?

A resposta a esta pergunta é “Sim”, mas para um primeiro exemplo, veremos como criar e exibir Traces com o SMO, e na próxima semana veremos como automatizar o processo de criação de um Trace para Tuning (ótimo para quem vai utilizar o Database Engine Tuning Advisor) gerando um arquivo de trace compatível com o SQL Server Profiler.

Então vamos ao exemplo!

As DLLs responsáveis para a utilização das bibliotecas de trace são:
Microsoft.SqlServer.ConnectionInfo.dll
Microsoft.SqlServer.ConnectionInfoExtended.dll

Para este exemplo utilizei o Template Standard do SQL Server Profiler, mas é possível utilizar outros templates ou até mesmo criar novos templates pelo SQL Server Profiler.

E abaixo nosso código de utilização de trace:

using System;
using System.Threading;
using Microsoft.SqlServer.Management.Common;
using Microsoft.SqlServer.Management.Trace;

namespace DemoSmo
{
    static class DemoTrace1
    {
        private const string StandardFile =
            @"C:\Program Files (x86)\Microsoft SQL Server\100\Tools\Profiler\Templates\Microsoft SQL Server\100\Standard.tdf";

        public static void Run()
        {
            var conn = new SqlConnectionInfo { ServerName = @".\SQLEXPRESS" };

            // Criando o objeto TraceServer
            var traceReader = new TraceServer();

            // Iniciando a leitura do trace a partir do template Standard do SQL Server Profiler
            traceReader.InitializeAsReader(conn, StandardFile);

            // Criando uma Thread para a leitura do trace
            var ts =
                new Thread(
                    () =>
                    {
                        while (traceReader.Read())
                        {
                            // Identificando o tipo de evento
                            var name = traceReader.GetValue(traceReader.GetOrdinal("EventClass"));

                            if (!name.Equals("SQL:BatchCompleted") && !name.Equals("RPC:Completed")) continue;

                            // Identificando o SPID que esta executando o comando
                            var spid = traceReader.GetValue(traceReader.GetOrdinal("SPID"));

                            // Identificando informações do que é executado
                            var query = traceReader.GetValue(traceReader.GetOrdinal("TextData"));

                            // .. duração da execução
                            var duration = traceReader.GetValue(traceReader.GetOrdinal("Duration"));

                            Console.WriteLine("--SPID: {0} | Duration: {1}", spid, duration);

                            Console.WriteLine();

                            Console.WriteLine(query);

                            Console.WriteLine();
                        }
                    });

            // Iniciando a Thread
            ts.Start();

            // Esperando 30 segundos
            Thread.Sleep(30000);

            // Fechando o trace de leitura
            traceReader.Close();

            // Fechando a Thread
            ts.Join();
        }
    }
}

E por fim, o resultado da execução deste código:

Desculpe para quem não “manja” de Thread no C#, mas não conheço maneira melhor de criar este exemplo sem Thread.

Abaixo o artigo do Slavik Krassovsky, que utilizei para entender o funcionamento dos objetos de Trace do SMO:
http://technet.microsoft.com/en-US/library/ms345134.aspx