2014, um MVP de SQL Server a menos, mudanças e novidades

Bom Dia Pessoal,

Após mais de um ano sem blogar resolvi dar as caras e escrever alguma coisa por aqui. Dizem que o ano só começa na primeira segunda-feira após o carnaval, pois, mesmo quinta e sexta ainda tem muitos agitos por aí Brasil a fora. Pois bem, ano de 2014 realmente começando e estou aqui escrevendo o meu primeiro post de 2014.

Puxa quanta coisa andou mudando desde o último post e nesse pequeno início de ano em 2014 e vou compartilhar a seguir algumas das coisas que aconteceram que mudaram meus planos para esse ano.

O primeiro grande acontecimento nesse ano de 2014, ou melhor, em 31/12 de 2013 foi saber que eu não renovaria o título de MVP (que boa notícia para um reveillon). Confesso que não era uma ligação que eu esperava, mas ela acabou acontecendo. O interessante é que quando você vai renovar ninguém te liga e um email chega no dia 01 do seu ciclo (sou do ciclo de janeiro) que é exatamente o dia em que você é nomeado, mas se chegar um email no dia anterior e alguém de te ligar… Ah tiro e queda e comigo não foi diferente.

No momento em que vi o email e recebi a ligação da minha MVP LEAD, eu já sabia do que se tratava e em um pequeno espaço de tempo começaram a passar mil coisas pela minha cabeça. Afinal, porque eu não consegui renovar ? Tudo bem que deixei de blogar o ano de 2013 e minha participação nos fóruns Microsoft foram reduzidas a zero, mas isso não significa que eu não tenha contribuído. Hora foram vários vídeos no youtube e mais três eventos presenciais em que palestrei (duas edições do SQL Sat e mais uma edição do Codificando .NET e todas com as devidas comprovações).

Ainda assim, poderia dizer que a decisão foi injusta ? Indignar-me por achar que eu contribui mais que fulano que fez muito menos e ainda é MVP ou beltrano que não faz nada útil e ainda mantém o título ? Puxa, o que faltou ? Será que um vídeo a mais ia garantir ? Se eu tivesse feito mais palestras eu conseguiria o pontinho que faltava ? Será que uma presença maior na redes sociais teria bastado ? Bem, passou pela minha cabeça questionar, mas como disse para a minha MVP Lead na época, aquele não era o momento para fazer lamentações apenas agradecimentos. Injustiça ? Eu só poderia dizer isso se os critérios de obtenção e manutenção fossem conhecidos, mas como não são, não posso dizer que foi injusto. O fato é que tanto a conquista e a manutenção desse título são caminhos em uma estrada que não estão claramente definidos. Apenas os seguimos com a nossa intuição e colaborações para com a comunidade e alguém nos observa para ver se continuamos na trilha certa e por alguma razão eu deixei de ser notado.

Entretanto, vejamos por outra perspectiva que possa fazer sentido para a não renovação. Quando ganhei o título de MVP, minha marca registrada eram as enormes quantidades de participações no fórum (por muito tempo fiquei entre os primeiros TOP Posters) e as postagens no blog (que na época era do Windows Live). Tinha semanas que eram mais de 200 interações no fórum (sem contar o hercúlio trabalho de moderação) e minha média de postagem era um novo post a cada duas semanas no máximo. Tanta participação me rendeu o título e sua manutenção certamente. Entretanto, depois de um tempo é difícil acompanhar esse ritmo e ainda ter tanto assuntos para postagens. Resolvi diversificar. Apostei em novas participações. Postei vários vídeos, abracei os eventos presenciais, mas praticamente abandonei o fórum e o blog e enquanto eu pensava que essa era apenas uma mudança, o tomador da decisão pode ter interpretado que eu simplesmente estava abandonando a comunidade zerando minhas contribuições com esses canais.

Eu sempre soube que um dia esse dia chegaria, mas confesso que ainda tinha esperança de renovar mais uma vez. Já estava me preparando para visitar o Campus da Microsoft no próximo MVP Summit (que sem dúvida é um dos melhores benefícios de ser MVP) com a certeza de que provavelmente seria último. Infelizmente não deu. A renovação não chegou.

Independente disso, só tenho a agradecer a todos aqueles que me ajudaram, contribuiram e estiveram comigo e a Microsoft pelo tempo em que estivesse MVP (afinal ninguém é MVP pra sempre). A ajuda às comunidades me renderam incontáveis oportunidades como profissional e como pessoa. Ajudei a conhecidos, desconhecidos e até aqueles que não sabia que havia ajudado e isso sempre me fez muito bem independente das horas e da quantidade de trabalho que isso consumia.

Tive a oportunidade de ouvir de uma pessoa no meu primeiro SQL Sat que meus posts, minhas contribuições, minhas respostas ajudaram e incentivaram essa pessoa a mudar de vida, a sair de uma condição menos favorecida a realmente conseguir um emprego e ir em frente (e um dia eu conto a história toda). Tive a oportunidade de fazer amigos incríveis e conhecer um pouco mais de cada um além do SQL Server. Fui a Nova York, fui a Seattle e estive no Campus da Microsoft, conversei com gerentes de produto e dei opiniões para as versões futuras. Fui a vários CZ, a dois TechEDs e consegui encher um auditório com mais de 300 pessoas para escutar o que eu tinha a dizer. Fiz inúmeras consultorias para várias empresas importantes aumentando em muito o skill técnico, o networking e até o trato com as pessoas. Ministrei várias turmas de MOCs aqui em Bsb (acho que treinei boa parte da cidade) e isso rendeu várias outras coisas. Recebi diversos convites de trabalhos (dentre alguns que balançaram um bocado). Tive a oportunidade de palestrar no SQL Server DF e ter ilustre visita de pessoas de SP apenas para ver uma palestra minha. Puxa, se eu for pensar na gama de oportunidades e coisas boas que aconteceram, longe de mim lamentar alguma coisa. Pelo contrário, só agradeço a Microsoft por ter me proporcionado tudo isso.

Hoje esse título não me pertence mais, mas isso não me deixa triste e nem me faz me sentir menos. Sinto-me muito bem por ter feito parte desse seleto time de profissionais e desejo que outros que igualmente contribuem com as comunidades tenham a oportunidade de usufruir dos benefícios que eu tive e abraçar as oportunidades que me apareceram. A única coisa de que me arrependo foi não ter tido tempo pra baixar todos os softwares que eu queria rs, mas minha caixa de email está muito mais vazia (e os MVPs de SQL Server sabem bem o que quero dizer hehehe).

Na vida profissional estou mais afastados dos teclados e das queries. Mudei um pouco o rumo do barco. De analista, de DBA, de técnico passei a ser líder, gestor e coordenador. Dos problemas de espaço em disco, alto processamento, queries mal feitas pra resolver e scripts T-SQL, passei a ter de lidar com pessoas, orçamentos, projetos e questões políticas mais delicadas. Enquanto eu precisava entender apenas de SQL Server e seus agregados além de um pouco de Windows, agora tenho que conhecer (ainda que superficialmente) outras tecnologias como DB2, Netezza, Data Stage, etc. Uma mudança e tanto certamente, mas posso dizer que liderar uma equipe de banco de dados tão heterogênea também é um trabalho o qual me divirto um bocado na maior parte dos momentos. Adoro ir trabalhar e gosto muito da equipe a qual faço parte e sem dúvida é um trabalho o qual aprendo muito com todos.

Essa mudança do técnico para o gerencial realmente limita muito minhas participações na comunidade já que o foco mudou e isso era uma das dificuldades de me manter tão participativo. Entretanto, isso não significa que só porque perdi o título de MVP eu não vá mais contribuir. Estou de palestra marcada no SQL Saturday 284 em POA e aguardo vocês por lá. Tentarei fazer a melhor palestra possível. Poderei expor algumas das dificuldades que tivemos em um grande projeto de BI que estamos tocando.

O ano de 2014 também começou com uma excelente novidade. No final de janeiro descobri que vou ser papai pela segunda vez. Essa foi a primeira e talvez melhor notícia, pois, já era algo que estava planejando e aconteceu. Claro que na hora a gente fica assustado, mas que bom que aconteceu. Quem já é pai sabe a canseira que dá e os desafios que são, mas sem dúvida uma responsabilidade que rende boas alegrias e certamente muda tudo.

Também aproveitei esse ano para colocar em prática um hobby que eu havia deixado de lado tinha um tempão. Na minha adolescência, eu era fascinado por música eletrônica e atuei com DJ tocando em festas (e até cheguei a ser residente em uma boate). Épocas muito boas e sem dúvida estar mixando é terapia pra mim. Por que não retomar (ainda que apenas para ouvir meus próprios trabalhos) ? Já estou produzindo alguns SETs e compartilhando-os no soundcloud e no mixcloud pelos links http://soundcloud.com/gmasets e http://mixcloud.com/gmasets inclusive com as últimas compilações do fim de semana. Quem quiser aproveitar, blogar, comentar ou repassar será bem vindo.

Começo então 2014 por aqui. Obrigado a todos pelo carinho e vamos em frente.

[ ]s,

Gustavo

Introdução aos índices em bancos de dados e no SQL Server

Boa noite pessoal,

Finalizando o ano de 2012, estou aqui postando meu último artigo desse ano. Hoje finalizei a edição do último vídeo de um conjunto de quatro vídeos explicando o que são índices, para que servem, sua estrutura interna entre outros aspectos.

Iniciei a elaboração desses vídeos pensando na dificuldade que é ter explicar o que são índices em curto período de tempo nos momentos como MCT, no desafio que é explicar a diferença entre índices clustered e nonclustered em sucessivas dúvidas em fórum e principalmente para montar um material de referência que sirva para detalhar melhor algumas das explicações que vejo por aí além de desmitificar alguns equívocos que vejo por aí a citar:

“Imagine uma árvore, a organização dos indices é semelhante a um formato de árvore, na estrutura da árvore os dados das tabelas resídem no nível folha, ou seja, imagine os dados espalhados por todas as folhas da árvore, para facilitar a busca nestas folhas é que existem os índices.”

“O índice Clustered, geralmente é definido juntamente com a chave primária da tabela. Uma característica importante deste tipo de indice é que ele determina a ordem da gravação (ordenação física) dos registros.”

Independente de certas ou erradas, são afirmações que dizem muito pouco ou praticamente nada para quem ainda não tenha sido apresentado ao assunto. Assim sendo, para aqueles que desejam uma introdução mais sólida sobre o assunto de forma a montar uma boa base para aprofundá-lo e ter argumentos em discussões futuras eu recomendo dar uma olhada nos vídeos abaixo:

Índices – Fundamentos, Estruturas & Usabilidade (Parte 1)

Índices – Fundamentos, Estruturas & Usabilidade (Parte 2)

Índices – Fundamentos, Estruturas & Usabilidade (Parte 3)

Índices – Fundamentos, Estruturas & Usabilidade (Parte 4)

Elogios, sugestões e críticas são muito bem vindas. Não deixe de comentar para que eu possa evoluí-los. No resto mais, um ótimo 2013 a todos.

[ ]s,

Gustavo

Índices e Foreing Keys

Boa Noite Pessoal,

Hoje vi uma dúvida interessante no fórum de SQL Server (MSDN / TechNET). A dúvida em si não era complicada. Bastava apenas identificar quais foreign keys estavam com índices criados ou não. Postarei um script para fazer isso logo mais, mas antes de propriamente apresentá-lo parei um pouco para pensar a respeito dessa necessidade. Se alguém deseja identificar quais FKs não tem índice é porque certamente pretenderá criar índices sobre elas, o que me remete a pensar se indexar todas as FKs é de fato um bom negócio. Sim, vejo muitas recomendações para fazê-lo em vários livros de performance e modelagem de dados. De fato, costuma ser uma boa coisa em muitas situações, mas se fosse bom sempre, então porque o SQL Server, o ORACLE, o DB2 e todos os outros não criam a FKs indexadas por padrão ?

Por que criar índices sobre Foreign Keys ?

A partir do momento em que uma FK é criada sobre uma coluna ou um grupo de colunas, é impositivo que essa coluna ou esse grupo possui um relacionamento com uma coluna ou grupo de colunas de outra tabela que possui essa coluna ou grupo compondo a chave primária. Uma vez que esse relacionamento é construído por essas constraints, em algum momento será necessário identificá-lo para montar as consultas e as junções (JOINs), para recuperar um determinado resultado que combine dados de duas ou mais tabelas.

As operações de JOIN de um ponto de vista sintático são bem conhecidas (INNER, OUTER, FULL, NATURAL, etc), mas fisicamente falando, elas costumam obedecer três algoritmos básicos (Nested Loops, Merge Join e Hash Join). Não vou explicar os algoritmos hoje, mas limito a dizer que não existe um melhor ou pior que o outro (embora alguns jurem que HASH JOIN não é uma boa coisa), apenas que o otimizador normalmente sabe quando é melhor utilizar um ou outro algoritmo em uma determinada situação. O fato é que a presença de índices pode auxiliar o otimizador na escolha desse algoritmo e por consequência retornar os dados mais rapidamente. Vejamos um rápido exemplo:

— Retira a nulabilidade
SET ANSI_NULL_DFLT_OFF ON

— Cria as tabelas
CREATE TABLE Pessoas (ID INT, Nome VARCHAR(50))
CREATE TABLE Saldos (DataSaldo DATE, IDPessoa INT, Valor SMALLMONEY, HashRegistro UNIQUEIDENTIFIER)

— Cria as constraints
ALTER TABLE Pessoas ADD PRIMARY KEY (ID)
ALTER TABLE Saldos ADD PRIMARY KEY (DataSaldo, IDPessoa)
ALTER TABLE Saldos ADD FOREIGN KEY (IDPessoa) REFERENCES Pessoas (ID)
ALTER TABLE Saldos ADD DEFAULT NewID() FOR HashRegistro

— Insere alguns registros de pessoas
INSERT INTO Pessoas VALUES
(1,‘Vinicius’),(2,‘Wagner’),(3,‘André’),(4,‘Gilson’),(5,‘Emerson’)

— Insere vários registros fictícios de saldos
;WITH Nums (Num) As (
    SELECT 0 As Num
    UNION ALL SELECT Num + 1 FROM Nums
    WHERE Num < 4500)

INSERT INTO Saldos (DataSaldo, IDPessoa, Valor)
SELECT DATEADD(d,N1.Num,‘2000-01-01’) As Data, N2.Num + 1 As IDPessoa, 1000 As Valor
FROM Nums As N1
CROSS JOIN Nums As N2
WHERE N2.Num <= 2
ORDER BY Data, IDPessoa
OPTION (MAXRECURSION 0)

— Insere um registro de saldo
INSERT INTO Saldos VALUES (‘2012-08-15’,4,5000, NEWID())

— Cria um índice sobre a coluna IDPessoa na tabela Saldos
CREATE INDEX IX_Pessoa ON Saldos (IDPessoa)

Agora que a tabela está criada, vejamos uma consulta que traga o nome e os registros de saldo do primeiro cliente:

SELECT P.Nome, S.* FROM Pessoas As P
INNER JOIN Saldos As S ON P.ID = S.IDPessoa
WHERE P.ID = 4

O plano de execução é exibido logo abaixo:

Índices e Foreign Keys (Index Seek)

Podemos ver que primeiro o índice IX_Pessoa foi utilizado para pesquisar os registros na tabela de Saldo com IDPessoa = 4 retornando um ponteiro para o único registro existente na tabela de Saldos com essa condição. Posteriormente, após achar os ponteiros no índice, foi feita uma operação de Lookup contra a tabela Saldos e a junção do ponteiro do índice IX_Pessoa com os registros foram consolidadas em uma operação de junção através do algoritmo Nested Loop. Para juntar os registros da tabela de pessoas com a tabela de Saldos, foi feita uma outra operação de JOIN através do algoritmo Nested Loops e por fim o resultado foi devolvido (muito rapidamente por sinal). Podemos ver que nesse caso, o uso do índice foi muito benéfico para acelerar a pesquisa e o resultado corrobora com a tese de que indexar FKs é uma boa idéia.

Uma segunda opinião

E se mudássemos a consulta ? E se ao invés de escolher IDPessoa = 4 escolhéssemos IDPessoa = 1 ? Vejamos:

SELECT P.Nome, S.* FROM Pessoas As P
INNER JOIN Saldos As S ON P.ID = S.IDPessoa
WHERE P.ID = 1

O plano de execução é exibido logo abaixo:

Índices e Foreign Keys (Clustered Index Scan)

Dessa vez os resultados foram um pouco diferentes da consulta anterior. Podemos ver que na tabela Pessoa, houve uma pesquisa (Seek) para recuperar a pessoa de ID = 1, mas na tabela Saldos, houve uma varredura (Scan) da tabela inteira (Clustered Index pq ela está clusterizada). O otimizador concluiu que era melhor ignorar o índice IX_Pessoa, pois, para favorecer a condição IDPessoa = 1, seria mais fácil ler a tabela inteira do que ir no índice e ir na tabela registro a registro. Se formos analisar por esse resultado, a criação de índices em FKs só tem a trazer desvantagens. O índice IX_Pessoa na tabela de saldos irá retardar as operações de escrita, irá ocupar mais espaço e não servirá para atender as consultas que utilizem a coluna IDPessoa. No entanto, será que o índice não serve mesmo para nada ?

SELECT P.Nome, COUNT(*) FROM Pessoas As P
INNER JOIN Saldos As S ON P.ID = S.IDPessoa
WHERE P.ID = 1
GROUP BY P.Nome

O plano de execução é exibido logo abaixo:

Índices e Foreign Keys (GROUP BY)

Embora o índice IX_Pessoa não seja útil para filtrar registros na tabela Saldos onde IDPessoa = 1, o fato de existir um índice  e o objetivo ser apenas uma contagem e não uma exibição das colunas, faz com que a consulta tenha de ser feita apenas sobre o índice e nesse caso, foi possível até fazer um seek e posteriormente uma agregação para posteriormente efetuar um JOIN com a tabela Pessoas. Podemos ver que o índice IX_Pessoa não é tão ruim afinal.

O papel do índice em relacionamentos fortes ou identificados

Um relacionamento é caracterizado pela presença de uma PK em sua tabela Pai e uma FK referenciado as colunas da PK em uma tabela filha. Diz-se que o relacionamento é forte ou identificado quando as colunas da FK também fazem parte da PK na tabela filha. Vejamos o papel que o índice pode desempenhar nesse caso.

— Efetua duas cópias da tabela de Saldos
SELECT IDPessoa, DataSaldo, Valor, HashRegistro
INTO Saldos2 FROM Saldos
ORDER BY IDPessoa, DataSaldo

SELECT IDPessoa, DataSaldo, Valor, HashRegistro
INTO Saldos3 FROM Saldos
ORDER BY IDPessoa, DataSaldo

— Cria as constraints (PK apenas em Saldos2)
ALTER TABLE Saldos2 ADD PRIMARY KEY (IDPessoa, DataSaldo)
ALTER TABLE Saldos2 ADD FOREIGN KEY (IDPessoa) REFERENCES Pessoas (ID)
ALTER TABLE Saldos3 ADD FOREIGN KEY (IDPessoa) REFERENCES Pessoas (ID)

— Cria um índice em Saldos3
CREATE INDEX IX_Pessoa ON Saldos3 (IDPessoa)

Agora vejamos o impacto nas consultas que fizemos anteriormente:

SELECT P.Nome, S.* FROM Pessoas As P
INNER JOIN Saldos2 As S ON P.ID = S.IDPessoa
WHERE P.ID = 1

SELECT P.Nome, S.* FROM Pessoas As P
INNER JOIN Saldos3 As S ON P.ID = S.IDPessoa
WHERE P.ID = 1

O plano de execução é exibido logo abaixo:

20120815_04_FK

No primeiro caso, podemos ver que o fato da coluna IDPessoa ser a primeira coluna da chave primária (que por padrão é clusterizada) faz com que a leitura dos registros da tabela Saldos2 ainda seja uma pesquisa (Seek), pois, a chave primária composta por IDPessoa e DataSaldo impõe uma ordem dos registros por IDPessoa. Já no caso da tabela Saldos3, como não há nenhuma ordem e muitos registros para IDPessoa = 1, o índice é simplesmente ignorado e a tabela é completamente varrida (Table Scan). Proporcionalmente, podemos ver que a consulta em Saldos3 é duas vezes maior que em Saldos2 (67% vs 33% no mesmo Batch).

O papel do índice nas operações de exclusão

Até agora tivemos um foco muito grande no uso do índice nas FKs para consultas, mas ele tem papel nas operações de exclusão ou atualizações em cascata ?

— Efetua uma cópia da tabela de Saldos
SELECT * INTO Saldos4 FROM Saldos

— Cria as constraints
ALTER TABLE Saldos4 ADD PRIMARY KEY (DataSaldo, IDPessoa)
ALTER TABLE Saldos4 ADD FOREIGN KEY (IDPessoa) REFERENCES Pessoas (ID)

DELETE FROM Pessoas WHERE ID = 1

O plano de execução é exibido logo abaixo:

Índices e Foreign Keys (Exclusões)

Podemos ver que a exclusão ocorre na tabela Pessoas representada pela operação Clustered Index Delete. Também podemos ver que é feita uma verificação da existência de registros filhos nas tabelas Saldos, Saldos2, Saldos3 e Saldos4. Podemos ver que em todas as tabelas filhas é feita uma pesquisa para checagem da existência (algo muito parecido com a consulta que fez a contagem de registros nos exemplos anteriores). Especificamente na Saldos4, podemos ver que é feita uma varredura.

Índices e Foreign Keys (Exclusões)

A varredura na tabela Saldos4 é feita porque não há como saber se existem registros para IDPessoa = 5 senão lendo a tabela inteira uma vez que não exista nenhum índice capaz de auxiliar nessa pesquisa. Em virtude da tabela ser pequena, o custo no plano é de apenas 13% (1% a mais do que as demais tabelas de Saldo). Agora imagine se essa tabela fosse muito grande e muito larga. Uma varredura poderia tornar a exclusão absurdamente lenta, além de impor bloqueios por muito tempo e muitas vezes desnecessários.

Algumas conclusões

A conclusão que chego é provavelmente a que os fabricantes já chegaram, ou seja, como não sabemos o comportamento da seletividade dos valores nas colunas da chave estrangeira, não há como afirmar se a criação de índices em chaves estrangeiras é ou não um bom negócio. Então nesse caso, é melhor assumir a posição de não criar índices nas colunas das chaves estrangeiras e deixar a cargo do implementador efetuar essa avaliação. Excetuando as operações de exclusão e atualizações em cascata, a criação de índices está muito mais voltada para a consulta em si do que o fato de existir um relacionamento. Ainda assim algumas recomendações:

  1. Se você for efetuar exclusões nas tabelas pai com frequência e as tabelas filhas forem muito volumosas, indexe as FKs
  2. Se você for efetuar JOINs com o objetivo apenas de contar registros ou verificar existência (EXISTs), indexe as FKs
  3. Se você utilizar relacionamentos fortes ou identificados, indexe as FKs
  4. Se as ocorrência dos valores nas colunas das FKs tiverem muita repetição, ou seja, forem pouco seletivas e as condições acima não forem verdadeiras, avalie se criar índices é um bom negócio (possivelmente não será)
  5. Se os três primeiros itens não forem verdadeiros, olhe a criação do índice muito mais como um índice sobre um coluna comum, pois, nesse caso, a coluna ser ou não FK não fará diferença

E como descobrir quais FKs não estão indexadas ?

Seria simples utilizar o exemplo anterior que possui colunas simples, mas vou criar mais três tabelas com chaves compostas e assim podermos avaliar a eficácia do meu script. Embora as tabelas induzam um relacionamento identificado, deixarei algumas colunas de fora propositalmente

— Retira a nulabilidade
SET ANSI_NULL_DFLT_OFF ON

— Cria mais três tabelas
CREATE TABLE SaldosSuspeitos (DataSaldo DATE, IDPessoa INT, DataInclusao DATE)
CREATE TABLE SaldosLimite (DataSaldo DATE, IDPessoa INT, DataInclusao DATE)
CREATE TABLE SaldosDevedores (DataSaldo DATE, IDPessoa INT, DataInclusao DATE)

— Adiciona as Constraints
ALTER TABLE SaldosSuspeitos ADD PRIMARY KEY (DataSaldo, IDPessoa)
ALTER TABLE SaldosSuspeitos ADD FOREIGN KEY (DataSaldo, IDPessoa) REFERENCES Saldos (DataSaldo, IDPessoa)
ALTER TABLE SaldosLimite ADD FOREIGN KEY (DataSaldo, IDPessoa) REFERENCES Saldos (DataSaldo, IDPessoa)
ALTER TABLE SaldosDevedores ADD FOREIGN KEY (DataSaldo, IDPessoa) REFERENCES Saldos (DataSaldo, IDPessoa)

— Adiciona um índice na FK para a tabela SaldosDevedores
CREATE INDEX IX_Saldo ON SaldosDevedores (DataSaldo, IDPessoa)
CREATE INDEX IX_DataSaldo ON SaldosLimite (DataSaldo)

Visualmente sabemos que a tabela Saldo4 não tem índice na coluna IDPessoa e tem FK para essa coluna. Sabemos também que a tabela SaldosLimite tem a FK com as colunas DataSaldo e IDPessoa, mas possui um índice apenas sobre a coluna DataSaldo e portanto não tem sua FK indexada. Segue a consulta:

;WITH FKsNI (ObjectId, FKId) As (
SELECT DISTINCT
    parent_object_id, constraint_object_id
FROM sys.foreign_key_columns As FC
WHERE NOT EXISTS (
    SELECT * FROM sys.index_columns As IC
    WHERE
        FC.parent_object_id = IC.object_id AND
        FC.constraint_column_id = IC.index_column_id AND
        FC.parent_column_id = IC.column_id))

SELECT
    OBJECT_NAME(FKsNI.ObjectId) As Objeto,
    OBJECT_NAME(FKsNI.FKId) As FK,
    C.Name As Coluna,
    FC.constraint_column_id As Ordem
FROM FKsNI
INNER JOIN sys.foreign_key_columns As FC ON
    FKsNI.FKId = FC.constraint_object_id AND
    FKsNI.ObjectId = FC.parent_object_id
INNER JOIN sys.columns As C ON
    FC.parent_object_id = C.object_id AND
    FC.parent_column_id = C.column_id
ORDER BY Ordem

O resultado sai exatamente conforme o esperado:

Objeto FK Coluna Ordem
Saldos4 FK__Saldos4__IDPesso__25869641 IDPessoa 1
SaldosLimite FK__SaldosLimite__2B3F6F97 DataSaldo 1
SaldosLimite FK__SaldosLimite__2B3F6F97 IDPessoa 2

Agora vem a pergunta ? Depois que você rodar a consulta e identificar as FKs que não estão indexadas, você vai criar os índices ? Depende de cada um.

[ ]s,

Gustavo

WEBCAST – Concorrência de dados no SQL Server (depois da apresentação)

Bom Dia Pessoal,

Hoje faz uma semana que fiz a apresentação do webcast “Concorrência de dados no SQL Server”. Já tinha um tempinho que eu não fazia apresentações e deixando alguns apertos (microsoft, atualização do live meeting, vms, etc) consegui iniciá-lo sem problemas (um leve atraso de dez minutos).

Foram praticamente duas horas de apresentação e terminamos um pouco depois das 23h e eu estava sem a água do lado, mas correu tudo bem. Eu não consegui ver a lista de participantes (apenas dei um rápida visualizada, mas não deu pra memorizar). De qualquer forma eu agradeço a todos que acompanharam (Crespi, Raionan, Sulamita eu vi vocês lá). Também agradeço ao Felipe e a Andressa pelo suporte e espero poder apresentar em situações futuras (se o tempo permitir).

Nos bastidores, foi um webcast difícil de montar. Se pesquisar o funcionamento dos outros SGBDs como o Oracle e o DB2 já é difícil, se simular o efeito do Double Reads gastou metade de uma madrugada (até DEADLOCK no SELECT eu consegui), o que realmente deu trabalho foi fazer o PPT animado. Enfim, foi trabalhoso, mas faço de bom grado para a comunidade. Críticas e sugestões são bem vindas (não deu tempo no Webcast).

Aos que não puderam assistir não tem problema. Acredito que o time do Virtual Pass irá disponibilizar a palestra em breve, mas eu já me antecipei e coloquei a palestra no meu canal do Youtube (em vídeos picados). A quem se interessar, segue o link:

Concorrência de Dados
http://www.youtube.com/playlist?list=PL70D92D06E08E6653&feature=plcp

Aos que desejarem os scripts e a base utilizados, vocês podem obter em:

http://cid-f4f5c630410b9865.office.live.com/self.aspx/ProjetosSQLServer/20120322_Concorrencia.rar

Só não entendi porque o Camtasia deu um zoom indesejado em algumas partes do vídeo, mas enfim… Espero que não ocorra na próxima. Agora é preparar o PPT do SQL Saturday 127.

[ ]s,

Gustavo

SQL Saturday 127 – Rio de Janeiro aí vou eu

Oi Pessoal,

Há alguns dias recebi a grata notícia de que serei um dos palestrantes do SQL Saturday 127 que irá ocorrer no dia 14/04. Não preciso nem dizer que fiquei feliz com a oportunidade de estar novamente tão próximo da comunidade levando um pouco da vivência, dos apertos, dos erros e da experiência com o produto. O formato presencial me agrada muito, pois, me lembra a sala de aula nos momentos de MCTs, mas de uma forma mais aberta sem as restrições do MOC ou imposição de temas. O SQL Saturday 100 foi excelente e se o 127 for na mesma linha será um grande evento.

Na minha sessão falarei sobre o processo de ETL e algumas técnicas para escalá-lo de forma incremental e alternativas no SQL Server para processos ETL Near Real Time. O foco será mais forte no SQL Server, mas algumas técnicas são independentes de SGBD. Atualmente estou participando nas definições de infraestrutura de um grande projeto de Business Intelligence e embora BI não seja o meu maior foco de atuação, essa experiência está sendo bem engrandecedora. As definições de uma infraestrutura de BI dependem naturalmente da arquitetura da solução e o contato com arquitetos, consultores, desenvolvedores de ETL, etc é natural. É um excelente aprendizado no qual eu pretendo compartilhar um pedacinho nesse evento (se o tempo permitisse a gente falava por horas).

Será um sábado inteiro de SQL Server e se você é entusiasta do produto, trabalha com ele ou depende dele de alguma forma, essa é sem dúvida uma ótima oportunidade, até porque o evento é gratuito e as vagas são limitadas. Se você pretende participar, não deixe de obter mais informações no site oficial do evento em: http://www.sqlsaturday.com/127/eventhome.aspx

A agenda do evento está com muito boas palestras e naturalmente algum conflito irá acontecer durante as escolhas (eu mesmo tive muita dificuldade). Eis a minha escolha pessoal:

  • 09:15 – Marcelo Sincic – Dimensionando Hardware e Storage para SQL Server
  • 11:15 – Diego Nogare – Pontapé inicial de Business Intelligence na Nuvem
  • 14:15 – Gustavo Maia Aguiar – Algoritmos e alternativas para cargas incrementais
  • 16:15 – Felipe Ferreira – Melhores práticas para o seu Data Warehouse

Claro que também é uma excelente oportunidade para encontrar alguns dos vários conhecidos da comunidade de SQL Server bem como conhecer aqueles que ainda não tive a oportunidade. Então anote aí na agenda. Dia 14/04 é dia de SQL Saturday.

Espero vê-los por lá.

[ ]s,

Gustavo

WEBCAST – Concorrência de dados no SQL Server

Olá Pessoal,

Começando a colocar os planos de SQL Server no ano 2012 em ação e a iniciativa de apostar um pouco mais em vídeo nesse ano, estou indo para mais uma gravação.

O VirtualPassBR está com uma iniciativa de transmitir WebCasts todas as terças e quintas e tive o prazer de ser convidado para fazer o primeiro de uma série de eventos que virão.

No dia 15 desse mês, falarei sobre concorrência em banco de dados com um foco maior no SQL Server embora deva dar algumas palavrinhas sobre esse assunto em outros SGBDs. O assunto realmente me desperta bastante interesse.

A concorrência é cruel

Entender a concorrência por dados é peça na chave na construção de aplicações que possam escalar. Meia dúzia de usuários podem conviver mesmo que a aplicação tenha erros, mas dificilmente veremos um grande banco transacionando sem nos atentarmos para o assunto concorrência. Infelizmente há muitos implementadores que desconhecem esse assunto e confiam na mão invisível (o Adam Smith do banco) ou apelam para o NOLOCK. Pretendo explorar um pouco mais assunto e tentarei cobrir essas brechas.

Falarei dos principais problemas envolvidos em situações concorrentes, os modelos de concorrência, os níveis de isolamento e algumas situações que acompanho no fórum.

Se você deseja conhecer um pouco mais sobre esse assunto faça já a inscrição clicando aqui. Vejo vocês por lá.

[ ]s,

Gustavo

Iniciando 2012

Olá Pessoal,

Dizem que o ano só começa efetivamente depois do carnaval. Eu gostaria de ser um dos felizardos que pode dizer isso (alguém aí pode ?), mas não é o caso. De fato apenas hoje após alguns dias do término do carnaval, estou conseguindo colocar o meu primeiro post. Desde que comecei a me preparar para palestrar para o TechED 2011 que não consegui parar. Tanto esforço rendeu um bom feedback, pois, as avaliações do TechED foram muito satisfatórias para minha primeira apresentação em um evento desse porte. Na cola nem deu tempo de descansar, pois, iniciou-se a preparação para o SQL Saturday na sua edição de número 100 aqui no Brasil. Evento fantástico onde tive a oportunidade de conhecer e fazer novas amizades além de reencontrar as já existentes. Eu realmente adoro iniciativas com a comunidades, pois, não há nada mais gratificante do que ver a vontade em compartilhar alcançar lugares que nem mesmo imaginávamos. Na lista desses eventos entraram ainda o MVP Open Days e o Community Zone que também são excelentes oportunidades, mas consomem um pouquinho.

Pensei que depois do SQL Saturday eu iria diminuir o ritmo, mas no meu ambiente atual, acabei tendo uma baixa de um dos melhores DBAs da minha equipe e aí e passei a dar mais de mim para compensar essa baixa que espero resolver o quanto antes. Para piorar, coloque um pequeno problema de saúde e aí de fato, posso dizer que já tem algum tempo que eu não parava mesmo e ainda sem estar por aqui.

Pois bem, felizmente, depois de quase 30 dias de férias (como é bom poder tirar tanto tempo de férias e viajar ainda por cima) e praticamente ausente de emails, MSN, blogs, comunidades e iniciativas SQL Server estou de volta. Começando o ano de 2012 ainda um pouco devagar (estamos apenas alguns dias depois da quarta-feira de cinzas), estou com as baterias recarregadas e pronto para contribuir. Os anos de 2008 e 2009 dediquei praticamente 100% ao fórum e ao blog e alguns Webcasts. A partir de 2009, já comecei a utilizar outros tipos de canais como artigos técnicos para revistas, mas apenas em 2011 realmente diversifiquei. Além das palestras presenciais (que gostei muito de fazer), consegui gravar vídeos para o Centro de Treinamento de SQL Server, fazer um WebCast para o Virtual Pass e ainda colocar os vídeos no meu canal no Youtube.

Esse ano eu pretendo investir mais nesse tipo de iniciativa. Não que eu não queira ou não vá continuar blogando ou respondendo threads nos fóruns do MSDN e do Technet, mas é que vejo um alcance muito interessante em outros canais e pretendo explorá-los um pouco mais e não me restringir somente aos mais tradicionais.

Para iniciar acabei de postar o primeiro vídeo de uma coleção que pretendo postar abordando diversos assuntos sobre SQL Server e banco de dados. Os primeiros vídeos serão focados no assunto indexação e pretendo fazer uma introdução bem detalhada no assunto sem naturalmente a prentensão de esgotá-lo.

É nesse ponto e com esse vídeo que começo 2012. Vejamos o que consigo fazer  e até onde ir para ajudar essas pessoas. Feedbacks e sugestões de temas são bem vindos.

[ ]s,

Gustavo

Exportando consultas para documentos XML

Boa Noite Pessoal,

Essa será possivelmente uma das minhas últimas postagens de 2011, então vamos para uma dica rápida. Hoje falarei sobre como exportar uma tabela ou consulta para um documento XML através do BCP e do CLR. Já vi algumas postagens que tratam esse assunto, mas proponho um pouco mais que isso. Demonstrarei a exportação mantendo a identação dos elementos no documento XML. Para começar nada melhor que algumas tabelas de exemplo

— Cria um banco para testes
CREATE DATABASE ExportXML
GO

— Muda o contexto
USE ExportXML
GO

— Cria as tabelas
CREATE TABLE Clientes (
    ClienteID INT NOT NULL,
    ClienteNome VARCHAR(100) NOT NULL)

CREATE TABLE Contas (
    ClienteID INT NOT NULL,
    ContaID INT NOT NULL,
    ContaAgencia VARCHAR(6) NOT NULL,
    ContaNumero VARCHAR(9) NOT NULL)

CREATE TABLE Lancamentos (
    ClienteID INT NOT NULL,
    ContaID INT NOT NULL,
    Valor SMALLMONEY NOT NULL,
    Tipo CHAR(1) NOT NULL,
    Data DATE NOT NULL)

— Insere os registros
INSERT INTO Clientes VALUES (1,‘Ronaldo’)
INSERT INTO Clientes VALUES (2,‘Adalton’)
INSERT INTO Clientes VALUES (3,‘Daniela’)
INSERT INTO Clientes VALUES (4,‘Roberta’)

INSERT INTO Contas VALUES (1,1,‘3590-4’,‘97532-8’)
INSERT INTO Contas VALUES (1,2,‘3590-4’,‘856413-0’)
INSERT INTO Contas VALUES (1,3,‘3590-4’,‘948766-0’)
INSERT INTO Contas VALUES (2,1,‘1004-9’,‘974322-8’)
INSERT INTO Contas VALUES (2,2,‘1004-9’,‘15649-X’)
INSERT INTO Contas VALUES (3,1,‘2944-0’,‘7561-2’)

INSERT INTO Lancamentos VALUES (1,1,6500,‘C’,‘20111220’)
INSERT INTO Lancamentos VALUES (1,1,1200,‘D’,‘20111221’)
INSERT INTO Lancamentos VALUES (1,1,2500,‘D’,‘20111221’)
INSERT INTO Lancamentos VALUES (1,2,3500,‘C’,‘20111221’)
INSERT INTO Lancamentos VALUES (1,2,2600,‘C’,‘20111221’)
INSERT INTO Lancamentos VALUES (2,1,3200,‘D’,‘20111222’)
INSERT INTO Lancamentos VALUES (2,1,1100,‘D’,‘20111222’)
INSERT INTO Lancamentos VALUES (2,1,1900,‘C’,‘20111222’)
INSERT INTO Lancamentos VALUES (2,2,5800,‘D’,‘20111223’)
INSERT INTO Lancamentos VALUES (2,2,3200,‘D’,‘20111223’)
INSERT INTO Lancamentos VALUES (2,2,9800,‘C’,‘20111223’)

Agora que temos uma massa de testes, segue um consulta para estruturar esses dados em XML.

SELECT
    Cliente.ClienteNome As Nome,Conta.ContaAgencia As Agencia,
    Conta.ContaNumero As Numero,Lancamento.Data As Data,
    Lancamento.Valor, Lancamento.Tipo
FROM Clientes As Cliente
    LEFT OUTER JOIN Contas As Conta ON Cliente.ClienteID = Conta.ClienteID
    LEFT OUTER JOIN Lancamentos As Lancamento
        ON Conta.ClienteID = Lancamento.ClienteID AND Conta.ContaID = Lancamento.ContaID
FOR XML AUTO, ROOT(‘Lancamentos’)

Posto apenas um parte do documento XML produzido com esse comando:

<Lancamentos>
  <Cliente Nome=Ronaldo>
    <Conta Agencia=3590-4Numero=97532-8>
      <Lancamento Data=2011-12-20Valor=6500.0000Tipo=C/>
      <Lancamento Data=2011-12-21Valor=1200.0000Tipo=D/>
      <Lancamento Data=2011-12-21Valor=2500.0000Tipo=D/>
    </Conta>
    <Conta Agencia=3590-4Numero=856413-0“>
      <Lancamento Data=2011-12-21Valor=3500.0000Tipo=C/>
      <Lancamento Data=2011-12-21Valor=2600.0000Tipo=C/>
    </Conta>
    <Conta Agencia=3590-4Numero=948766-0>
      <Lancamento />
    </Conta>
  </Cliente>
  <Cliente Nome=Adalton>
    <Conta Agencia=1004-9Numero=974322-8>

Para facilitar os trabalhos, vou encapsultar a instrução SELECT em uma View.

CREATE VIEW vLancamentoXML (ResultadoXML) As
SELECT
    Cliente.ClienteNome As Nome,Conta.ContaAgencia As Agencia,
    Conta.ContaNumero As Numero,Lancamento.Data As Data,
    Lancamento.Valor, Lancamento.Tipo
FROM Clientes As Cliente
    LEFT OUTER JOIN Contas As Conta ON Cliente.ClienteID = Conta.ClienteID
    LEFT OUTER JOIN Lancamentos As Lancamento
        ON Conta.ClienteID = Lancamento.ClienteID AND Conta.ContaID = Lancamento.ContaID
FOR XML AUTO, ROOT(‘Lancamentos’)

Agora façamos uma tentativa de exporar esse conteúdo para um arquivo através do BCP. A instrução abaixo em um PROMPT de comando é capaz de fazê-lo.

BCP “ExportXML.dbo.vLancamentoXML” out “C:\Lancamentos.xml” -T -c

Eis uma parte do resultado do arquivo em XML

<Lancamentos><Cliente Nome=”Ronaldo”><Conta Agencia=”3590-4″ Numero=”97532-8″><Lancamento Data=”2011-12-20″ Valor=”6500.0000″ Tipo=”C”/>

O documento XML foi criado e pode ser plenamente utilizado, pois, é um XML bem formado, mas a identação foi completamente perdida na geração do documento. Se esse XML for processado por uma aplicação, não faz diferença ele estar identado ou não, mas se uma pessoa for abrí-lo, a falta de identação pode ser um grande inconviente.

Uma vez que o BCP foi projetado para exportar texto e não XML, não há parâmetros que possam fazer a identação ficar correta. Então precisaremos dar um jeito do texto já ser exportado de forma identada. Criei aqui uma função capaz de fazê-lo.

CREATE FUNCTION dbo.RetornaXMLGRID (@XML XML)
RETURNS @Registros TABLE (Registro VARCHAR(MAX))
As
BEGIN
    DECLARE
@strXML VARCHAR(MAX)
    SET @strXML = CAST(@XML As VARCHAR(MAX))
    SET @strXML = REPLACE(@strXML,‘<‘,‘+|’)
    SET @strXML = REPLACE(@strXML,‘<‘,‘-|’)

    DECLARE @T TABLE (
        Pos INT IDENTITY(1,1), Elemento VARCHAR(1000))

    INSERT INTO @T SELECT
        node.value(N’fn:local-name(.[1])’, N’varchar(1000)’)
    FROM @xml.nodes(N’//*’) T(Node)

    ;WITH Niveis (Elemento, Nivel) As (
        SELECT Elemento, MIN(Pos)
        FROM @T GROUP BY Elemento)

    DELETE FROM @T
    FROM @T As T
    INNER JOIN Niveis As N ON
        T.Elemento = N.Elemento AND T.Pos > N.Nivel

    DECLARE @i INT = 0
    DECLARE @f INT = (SELECT MAX(Pos) FROM @T)
    DECLARE @Elemento VARCHAR(1000) =

    WHILE @i <= @f – 1
    BEGIN
        SET
@Elemento = (
            SELECT Elemento FROM @T WHERE Pos = @i + 1)

        SET @strXML = REPLACE(@strXML,‘+|’ + @Elemento +
‘ ‘,CHAR(10) + REPLICATE(‘ ‘,@i) + ‘+|’ + @Elemento + ‘ ‘)
        SET @strXML = REPLACE(@strXML,‘+|’ + @Elemento + ‘-|’,CHAR(10) + REPLICATE(‘ ‘,@i) + ‘+|’ + @Elemento + ‘-|’)
        SET @strXML = REPLACE(@strXML,‘+|’ + @Elemento + ‘/-|‘,CHAR(10) + REPLICATE(‘ ‘,@i) + ‘+|’ + @Elemento + ‘/-|’)
        SET @strXML = REPLACE(@strXML,‘+|/’ + @Elemento + ‘-|’,CHAR(10) + REPLICATE(‘ ‘,@i) + ‘+|/’ + @Elemento + ‘-|’)
        SET @i += 1
    END
   
    SET @strXML = REPLACE(@strxml,CHAR(10),‘</e><e>’)
    SET @strXML = ‘<E><e>’ + @strXML + ‘</e></E>’
   
    SET @XML = CAST(@strXML As XML)
   
    INSERT INTO @Registros
    SELECT REPLACE(REPLACE(E.e.value(‘.’,‘VARCHAR(MAX)’),‘+|’,‘<‘),‘-|’,‘>’)
    FROM @XML.nodes(‘/E/e’) E(e)
   
    DELETE FROM @Registros WHERE Registro =
   
    RETURN
END

Agora tentemos o BCP novamente com algumas adaptações:

BCP “SELECT * FROM dbo.RetornaXMLGRID((SELECT ResultadoXML FROM vLancamentoXML))” queryout “C:\Lancamentos.xml” -T -c -d ExportXML

O comando SELECT ResultaadoXML FROM vLancamentoXML é apenas para retornar um texto (ou um tipo XML) no formato de um documento XML. É possível substituir essa parte do comando por qualquer consulta XML. O documento agora veio formatado após abrir o arquivo XML:

<Lancamentos>
<Cliente Nome=”Ronaldo”>
  <Conta Agencia=”3590-4″ Numero=”97532-8″>
   <Lancamento Data=”2011-12-20″ Valor=”6500.0000″ Tipo=”C”/>
   <Lancamento Data=”2011-12-21″ Valor=”1200.0000″ Tipo=”D”/>
   <Lancamento Data=”2011-12-21″ Valor=”2500.0000″ Tipo=”D”/>
  </Conta>
  <Conta Agencia=”3590-4″ Numero=”856413-0″>
   <Lancamento Data=”2011-12-21″ Valor=”3500.0000″ Tipo=”C”/>
   <Lancamento Data=”2011-12-21″ Valor=”2600.0000″ Tipo=”C”/>
  </Conta>
  <Conta Agencia=”3590-4″ Numero=”948766-0″>
   <Lancamento/>
  </Conta>
</Cliente>
<Cliente Nome=”Adalton”>
  <Conta Agencia=”1004-9″ Numero=”974322-8″>

Um teste para garantir que o XML só precisa ser váido por ser feito com outra consulta:

SELECT * FROM dbo.RetornaXMLGRID((
SELECT db.name As banco, t.name As tabela, c.name As coluna FROM sys.databases As db INNER JOIN sys.tables As t ON db.database_id = db_id() INNER JOIN sys.columns As c ON t.object_id = c.object_id for xml auto, elements))

Parte do resultado é exibido abaixo:

<db>
<banco>ExportXML
</banco>
  <t>
   <tabela>Clientes
   </tabela>
    <c>
     <coluna>ClienteID
     </coluna>
    </c>
    <c>
     <coluna>ClienteNome
     </coluna>
    </c>
  </t>
  <t>
   <tabela>Contas
   </tabela>
    <c>
     <coluna>ClienteID
     </coluna>

Acho que já são visíveis algumas limitações desse método. Primeiro porque com tantas conversões, essa alternativa não será escalável quando houver documentos XML muito grandes. Segundo, porque se houver elementos com o mesmo nome pertencentes a níveis diferentes, essa função também irá falhar. Isso torna claro que o T-SQL é poderoso, mas não foi feito para resolver todos os problemas. Tentemos uma abordagem com o CLR para esse mesmo fim.

using System;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using System.Xml;
using Microsoft.SqlServer.Server;

public partial class StoredProcedures
{
    [Microsoft.SqlServer.Server.SqlProcedure]
    public static void UspGerarArquivoXML(SqlString cmdSQL, SqlString local)
    {
        SqlConnection sc = new SqlConnection(“Context Connection=true”);
        SqlCommand sCmd = new SqlCommand(cmdSQL.ToString(), sc);

        // Abre a conexão
        sc.Open();

        // Declara um objeto XmlReader
        XmlReader xr = sCmd.ExecuteXmlReader();

        // Fecha a conexão
        sc.Close();

        // Declara um documento XML
        XmlDocument xd = new XmlDocument();

        // Inicia o documento
        xd.Load(xr);

        // Salva o documento
        xd.Save(local.ToString());
    }
};

Agora que o código em C# está pronto, basta colocá-lo em uma pasta qualquer (C:\SQLCLR\UspGeraArquivoXML.cs) e compilá-lo com o comando de PROMPT

“C:\Windows\Microsoft.NET\Framework\V2.0.50727\csc.exe” /out:C:\SQLCLR\UspGeraArquivoXML.dll /target:library C:\SQLCLR\UspGeraArquivoXML.cs

Os próximos passos são habilitar o CLR e importar o ASSEMBLY:

EXEC sp_configure ‘clr enabled’,1
GO

RECONFIGURE WITH OVERRIDE
GO

USE ExportXML
GO

ALTER DATABASE ExportXML SET TRUSTWORTHY ON

CREATE ASSEMBLY ExportarArquivosXML
FROM ‘C:\SQLCLR\UspGeraArquivoXML.dll’
WITH PERMISSION_SET = EXTERNAL_ACCESS
GO

Agora que o ASSEMBLY foi gerado, basta criar a procedure e chamá-la:

— Cria a stored procedure
CREATE PROCEDURE UspGerarArquivoXML
    @cmdSQL NVARCHAR(4000),
    @local NVARCHAR(4000)
As
EXTERNAL
NAME [ExportarArquivosXML].[StoredProcedures].[UspGerarArquivoXML]
GO

Por fim, podemos executar a SP com a consulta e o local do arquivo (é importante que a consulta tenha o nó ROOT e que o local tenha as devidas permissões).

— Exporta o Resultado para XML
EXEC dbo.UspGerarArquivoXML
    @cmdSQL =
    SELECT
        Cliente.ClienteNome As Nome,Conta.ContaAgencia As Agencia,
        Conta.ContaNumero As Numero,Lancamento.Data As Data,
        Lancamento.Valor, Lancamento.Tipo
    FROM Clientes As Cliente
        LEFT OUTER JOIN Contas As Conta ON Cliente.ClienteID = Conta.ClienteID
        LEFT OUTER JOIN Lancamentos As Lancamento
        ON Conta.ClienteID = Lancamento.ClienteID AND Conta.ContaID = Lancamento.ContaID
        FOR XML AUTO, ROOT(”Lancamentos”)’
,
    @local = ‘C:\SQLCLR\LancamentosCLR.xml’

O documento já vem identado, mas visivelmente o esforço é muito menor do que escrever código T-SQL para isso.

[ ]s,

Gustavo

Mapeando dependências de objetos para colunas

Boa Noite Pessoal,

Hoje vi no fórum de SQL Server da Microsoft uma necessidade relativamente comum que é a identificação de que objetos referenciam determinadas colunas de determinadas tabelas. É bem comum perguntarmos sobre essa dependências especialmente quando precisamos eliminar ou alterar uma ou outra coluna e sabemos que essas colunas são referenciadas por diversas views, procedures, functions, etc. O melhor para evitar tais situações é usar a opção SCHEMA BINDING sempre que possível, pois, ela evitará que uma ou outra coluna seja excluída e prejudique os objetos que delas dependam. É uma opção mais segura, mas ainda assim, para uma mudança é necessário mapear e isso o SCHEMA BINDING não faz.

Há muito tempo atrás, publiquei os artigos “Mapeando dependências entre tabelas” e “Mapeando dependências entre objetos” que trata sobre dependências com compatibilidade para o SQL Server 2005 (no 2008, os scripts funcionam, mas há outros objetos mais diretos). Ainda assim, não me atentei para uma necessidade de mapeamento para colunas. Vejamos então como podemos fazer isso:

CREATE TABLE Categorias (
    CodigoCategoria INT NOT NULL,
    NomeCategoria VARCHAR(15) NOT NULL,
    Descricao VARCHAR(MAX) NULL
    CONSTRAINT PK_Categorias PRIMARY KEY (CodigoCategoria))

CREATE TABLE Produtos (
    CodigoProduto INT NOT NULL,
    NomeProduto VARCHAR(40) NOT NULL,
    CodigoFornecedor INT NULL,
    CodigoCategoria INT NULL,
    QuantidadePorUnidade VARCHAR(25) NULL,
    PrecoUnitario MONEY NULL,
    UnidadesEstoque SMALLINT NULL,
    UnidadesPedidas SMALLINT NULL,
    NivelDeReposicao SMALLINT NULL,
    Descontinuado BIT NOT NULL,
    CONSTRAINT PK_Produtos PRIMARY KEY (CodigoProduto))

CREATE TABLE DetalhesPedido (
    NumeroPedido INT NOT NULL,
    CodigoProduto INT NOT NULL,
    PrecoUnitario MONEY NOT NULL,
    Quantidade SMALLINT NOT NULL,
    Desconto REAL NOT NULL,
    CONSTRAINT PK_DetalhesPedido PRIMARY KEY (NumeroPedido,CodigoProduto))

CREATE TABLE Pedidos (
    NumeroPedido INT NOT NULL,
    CodigoCliente INT NULL,
    CodigoFuncionario INT NULL,
    DataPedido DATE NULL,
    DataEntrega DATE NULL,
    DataEnvio DATE NULL,
    Frete MONEY NULL,
    CONSTRAINT PK_Pedidos PRIMARY KEY (NumeroPedido))

CREATE TABLE Clientes (
    CodigoCliente INT NOT NULL,
    NomeEmpresa VARCHAR(40) NOT NULL,
    NomeContato VARCHAR(30) NULL,
    CargoContato VARCHAR(30) NULL,
    Endereco VARCHAR(60) NULL,
    Cidade VARCHAR(15) NULL,
    Regiao VARCHAR(15) NULL,
    CEP CHAR(8) NULL,
    Pais VARCHAR(15) NULL,
    Telefone VARCHAR(24) NULL,
    Fax VARCHAR(24) NULL,
CONSTRAINT PK_Clientes PRIMARY KEY (CodigoCliente))

Agora que já criamos algumas tabelas, o próximo passo é criar algumas views, procedures, functions e triggers para criar o mapeamento dos objetos e das colunas desses objetos.

CREATE VIEW vProdutos As
SELECT NomeCategoria, NomeProduto
FROM Categorias As C
INNER JOIN Produtos As P ON C.CodigoCategoria = P.CodigoCategoria
GO

CREATE FUNCTION dbo.CalculaTotalPedido (@NumeroPedido INT)
RETURNS MONEY
As
BEGIN
DECLARE
@Ret MONEY
SET @Ret = (SELECT Frete FROM Pedidos WHERE NumeroPedido = @NumeroPedido)
SET @Ret = @Ret + (
    SELECT SUM(Quantidade * PrecoUnitario * (1 – Desconto))
    FROM DetalhesPedido WHERE NumeroPedido = @NumeroPedido)

RETURN (@Ret)
END
GO

CREATE PROCEDURE UspRetornaClientesPedidos
As
SELECT NomeEmpresa, NomeContato, Cidade, Pais, CEP
FROM Clientes As C
LEFT OUTER JOIN Pedidos As P ON C.CodigoCliente = P.CodigoCliente
GO

CREATE TRIGGER trgEstoque ON DetalhesPedido
FOR INSERT, UPDATE, DELETE
As
BEGIN

    — Efetua os incrementos
    UPDATE Produtos SET UnidadesEstoque += D.Quantidade
    FROM Produtos As P
    INNER JOIN DELETED As D ON P.CodigoProduto = D.CodigoProduto

    — Efetua os decrementos
    UPDATE Produtos SET UnidadesEstoque -= I.Quantidade
    FROM Produtos As P
    INNER JOIN INSERTED As I ON P.CodigoProduto = I.CodigoProduto

END

Visualmente podemos ver que a view, a function, a procedure e a trigger fazem referência para algumas colunas de algumas triggers. Utilizando a view de catálogo sys.sql_dependencies podemos mapear as dependências entre os objetos.

SELECT
    OBJECT_NAME(referenced_major_id) As ObjetoReferenciado,
    OBJECT_NAME(object_id) As Objeto
FROM sys.sql_dependencies
ORDER BY ObjetoReferenciado

O resultado de fato mostra as dependências:

Objeto Referenciado Objeto
Categorias vProdutos
Categorias vProdutos
Clientes UspRetornaClientesPedidos
Clientes UspRetornaClientesPedidos
Clientes UspRetornaClientesPedidos
Clientes UspRetornaClientesPedidos
Clientes UspRetornaClientesPedidos
Clientes UspRetornaClientesPedidos
DetalhesPedido CalculaTotalPedido
DetalhesPedido CalculaTotalPedido
DetalhesPedido CalculaTotalPedido
DetalhesPedido CalculaTotalPedido
Pedidos CalculaTotalPedido
Pedidos UspRetornaClientesPedidos
Pedidos CalculaTotalPedido
Produtos trgEstoque
Produtos vProdutos
Produtos vProdutos
Produtos trgEstoque

Podemos ver todos os mapeamentos, mas o fato de haver duplicidades é no mínimo intrigante. Usar o DISTINCT resolveria isso fácil, mas se há repetição há alguma razão para isso. Vamos pesquisar algumas outras colunas que provocam essa repetição:

SELECT
    OBJECT_NAME(referenced_major_id) As ObjetoReferenciado,
    OBJECT_NAME(object_id) As Objeto,
    column_id, referenced_minor_id

FROM sys.sql_dependencies
ORDER BY ObjetoReferenciado

Eis o resultado:

Objeto Referenciado Objeto Column_ID Referenced_minor_id
Categorias vProdutos 0 1
Categorias vProdutos 0 2
Clientes UspRetornaClientesPedidos 0 1
Clientes UspRetornaClientesPedidos 0 2
Clientes UspRetornaClientesPedidos 0 3
Clientes UspRetornaClientesPedidos 0 6
Clientes UspRetornaClientesPedidos 0 8
Clientes UspRetornaClientesPedidos 0 9
DetalhesPedido CalculaTotalPedido 0 1
DetalhesPedido CalculaTotalPedido 0 3
DetalhesPedido CalculaTotalPedido 0 4
DetalhesPedido CalculaTotalPedido 0 5
Pedidos CalculaTotalPedido 0 1
Pedidos UspRetornaClientesPedidos 0 2
Pedidos CalculaTotalPedido 0 8
Produtos trgEstoque 0 1
Produtos vProdutos 0 2
Produtos vProdutos 0 4
Produtos trgEstoque 0 7

O column_id não ajudou muito, mas a coluna Referenced_minor_id já é uma boa pista, pois, é visível que ela provoca a duplicidade. Se olharmos o Books OnLine, essa coluna é mais específica (Referenced_minor_id – 1, referenced_minor_id is a column ID; or if not a column, it is 0.). Só precisamos então fazer um JOIN com a sys.columns e um filtro para garantir que estamos sempre falando de colunas.

SELECT
    OBJECT_NAME(referenced_major_id) As ObjetoReferenciado,
    OBJECT_NAME(D.object_id) As Objeto,
    C.name As Coluna
FROM sys.sql_dependencies As D
INNER JOIN sys.columns As C ON
    D.referenced_major_id = C.object_id AND D.referenced_minor_id = C.column_id
WHERE class_desc = ‘OBJECT_OR_COLUMN_REFERENCE_NON_SCHEMA_BOUND’
ORDER BY ObjetoReferenciado

Eis o resultado:

Objeto Referenciado Objeto Coluna
Categorias vProdutos CodigoCategoria
Categorias vProdutos NomeCategoria
Clientes UspRetornaClientesPedidos CodigoCliente
Clientes UspRetornaClientesPedidos NomeEmpresa
Clientes UspRetornaClientesPedidos NomeContato
Clientes UspRetornaClientesPedidos Cidade
Clientes UspRetornaClientesPedidos CEP
Clientes UspRetornaClientesPedidos Pais
DetalhesPedido CalculaTotalPedido NumeroPedido
DetalhesPedido CalculaTotalPedido PrecoUnitario
DetalhesPedido CalculaTotalPedido Quantidade
DetalhesPedido CalculaTotalPedido Desconto
Pedidos CalculaTotalPedido NumeroPedido
Pedidos UspRetornaClientesPedidos CodigoCliente
Pedidos CalculaTotalPedido Frete
Produtos trgEstoque CodigoProduto
Produtos vProdutos NomeProduto
Produtos vProdutos CodigoCategoria
Produtos trgEstoque UnidadesEstoque

Podemos ver que todas as colunas foram retornadas conforme o esperado. Não somente as colunas usadas em SELECTs e UPDATEs, mas também as colunas utilizadas em JOINs (e como isso faz diferença). Será que realmente funciona para outros casos ? Vejamos um objeto com a opção SCHEMA_BINDING (requerida em views indexadas inclusive).

CREATE VIEW vProdutos2
WITH SCHEMABINDING
As
SELECT
NomeCategoria, NomeProduto
FROM dbo.Categorias As C
INNER JOIN dbo.Produtos As P ON C.CodigoCategoria = P.CodigoCategoria
GO

Se tentarmos a consulta anterior, infelizmente a view vProdutos2 não será retornada. A razão é que o vínculo é com schema binding e aí precisamos alterar o predicado.

SELECT
    OBJECT_NAME(referenced_major_id) As ObjetoReferenciado,
    OBJECT_NAME(D.object_id) As Objeto,
    C.name As Coluna
FROM sys.sql_dependencies As D
INNER JOIN sys.columns As C ON

    D.referenced_major_id = C.object_id AND D.referenced_minor_id = C.column_id
WHERE class_desc IN (
    ‘OBJECT_OR_COLUMN_REFERENCE_NON_SCHEMA_BOUND’,
    ‘OBJECT_OR_COLUMN_REFERENCE_SCHEMA_BOUND’)
ORDER BY ObjetoReferenciado

Eis o resultado:

Objeto Referenciado Objeto Coluna
Categorias vProdutos CodigoCategoria
Categorias vProdutos NomeCategoria
Categorias vProdutos2 CodigoCategoria
Categorias vProdutos2 NomeCategoria
Clientes UspRetornaClientesPedidos CodigoCliente
Clientes UspRetornaClientesPedidos NomeEmpresa
Clientes UspRetornaClientesPedidos NomeContato
Clientes UspRetornaClientesPedidos Cidade
Clientes UspRetornaClientesPedidos CEP
Clientes UspRetornaClientesPedidos Pais
DetalhesPedido CalculaTotalPedido NumeroPedido
DetalhesPedido CalculaTotalPedido PrecoUnitario
DetalhesPedido CalculaTotalPedido Quantidade
DetalhesPedido CalculaTotalPedido Desconto
Pedidos CalculaTotalPedido NumeroPedido
Pedidos UspRetornaClientesPedidos CodigoCliente
Pedidos CalculaTotalPedido Frete
Produtos trgEstoque CodigoProduto
Produtos vProdutos NomeProduto
Produtos vProdutos CodigoCategoria
Produtos trgEstoque UnidadesEstoque
Produtos vProdutos2 CodigoProduto
Produtos vProdutos2 NomeProduto

Agora sim funcionou. Para mapeamentos simples, essa view é uma mão na roda. Fica aí a dica.

[ ]s,

Gustavo

MVP Open Day, TechED & Community Zone

Oi Pessoal,

Após duas semanas da finalização desses três eventos, finalmente consegui um tempinho para contar um pouquinho o resultado desses três excelentes eventos. Dia 28/09 tivemos o MVP Open Day com uma grade de palestras muito interessantes sobre vários produtos Microsoft (Visual Studio, SQL Server, ALM, Windows Phone, etc). Em virtude do NDA não posso abrir muitos detalhes, mas eu diria apenas que o ano de 2012 é um ano bastante promissor para a plataforma de servidores (Windows Server, Hyper-V, etc). Fora as palestras foi ótimo encontrar alguns amigos no evento e compartilhar o happy hour com alguns dos profissionais que tanto contribuem para a comunidade. Não sei o que o futuro reserva, mas espero poder estar no MVP Open Day de 2012, 2013 e por aí vai. Só não aproveitei mais o evento, porque em virtude do TechED tive que ir embora cedo já que precisava dar uma boa revisada nas demos da palestra.

MVPs Brazucas

Dia 29/09 começava o primeiro dia do TechED. A grade de palestras estava interessante, mas com alguns conflitos que não pude evitar. Fiquei chateado de não poder prestigiar vários amigos com palestras muito interessante (BI, Azure, etc). Bem, infelizmente a vida é feita de escolhas e tive que optar pelas sessões que mais agregavam no momento. O grande destaque desse dia para mim foi a palestra do Nilton Pinheiro sobre HADR no Denali. O HADR é sem dúvida uma implementação fantástica e finalmente a Microsoft colocou algo para competir com o Oracle Data Guard no cenário de HA. O DB2 ficou para trás, pois, o HADR dele ainda não possui a funcionalidade de múltiplos sites Standby. Estou na torcida para que o próximo SQL Server depois do Denali finalmente traga algo para competir com o Oracle RAC e o Pure Scale do DB2. O tema foi abordado pelo Nilton com maestria, de forma clara e muito bem apresentada além de responder minhas dúvidas sobre o sistema de quórum do HADR. Tive a oportunidade de conversar com ele na hora do almoço antes da palestra para saber o que viria, e realmente atendeu bem minhas expectativas. Também pude aproveitar para assistir a palestra de troubleshooting do Fabio Gentile e da Renata Festa. Já conhecia o Gentile de alguns chamados no Premier e o assunto muito me interessava. Destaque para as novas DMVs do 2008R2 (SP1) que finalmente possibilitam obter dados de discos Mount Point (não aguentava mais a xp_fixeddrives). Muito boa, mas não espera menos do time de PFEs. Foi bom para atualizar algumas coisas que eu ainda não tinha fuçado.

Dia 30/09 era o grande dia. Eu já tinha participado do TechED em 2010 na mesa do Ask The Experts (que lamento inclusive ter sido retirada do evento em 2011), mas nunca tinha participado como palestrante. Na noite anterior havia finalizado todas as minhas demos e em termos de conteúdo estava tudo certo. Cheguei na sala 30 minutos antes, conversei com os operadores, fiz os testes de som, etc. Minha sessão iniciava às 10:10 e às dez horas eu já estava a postos. Naquela hora contando por alto tinha umas 50 pessoas na sala só. Por um lado fiquei meio chateado, pois, a capacidade da sala era no mínimo cinco vezes maior, mas por outro lado, até que o frio na barriga diminui com pouca gente. Quando foi 10:05 (cinco minutos antes), de repente a sala começa a encher e rapidamente os lugares são ocupados e aí sim fiquei mais feliz por ter mais interessados, mas sem dúvida comecei a ficar inquieto, pois, nunca havia feito uma sessão presencial para tantas pessoas (nas minhas contas deu umas 260, mas acho que foi mais). Pois bem, iniciada as formalidades, comecei a falar do ISO, evolução do T-SQL, novidades do 2008 R2 e chegamos na parte do Denali. Controlei o tempo e consegui apresentar tudo o que eu gostaria. Faltou só mais 5 minutinhos para falar das referências, etc, mas de qualquer forma gostei muito da experiência.

DBP302 - T-SQL: O que você deve saber do Microsoft SQL Server 2008 R2 e as novidades do SQL Server Code-Named Denali

Aproveito para agradecer a todos os presentes na sessão que participaram e aqueles que contribuiram com perguntas, sugestões, etc. A todos os que compareceram o meu muito obrigado. Aqueles que não puderam estar presentes, o PPT com os scripts pode ser baixado no meu Skydrive (20111017_TechED_DBP302_TSQL_2008R2_NewFeaturesDenali.rar)

DBP302 – T-SQL: O que você deve saber do Microsoft SQL Server 2008 R2 e as novidades do SQL Server Code-Named “Denali”
https://skydrive.live.com/?cid=F4F5C630410B9865&id=F4F5C630410B9865%21148

Após a minha sessão, mal saí da sala e já tive de voltar para assistir a sessão do Fabiano e do Luti sobre Cenários de otimização com o SQL Server “Denali” e 2008. Sessão bem interessante sobre estatísticas, cache bloat, parameter sniffing, etc. Foi um belo apanhado geral sobre alguns detalhes do otimizador de consultas além de uma excelente sessão. Na seqüência (e na mesma sala inclusive), veio a palestra do Catae e Pimenta sobre Raio-X do SQL Server: Arquitetura interna do gerenciador de banco de dados. No ano passado eu assisti uma palestra do Catae e do Gentile sobre troubleshooting e gostei bastante, pois, sempre se aprende algo novo além de conhecer alguns detalhes nas entranhas do SQL Server. O que mais gosto mesmo é das perguntas pós apresentação. Essa sessão falou bastante sobre as decisões que otimizador tem de tomar para montar um plano de execução (em especial na avaliação de índices). Se tivessem trocado a ordem da sessão do Fabiano e do Luti com a do Catae e do Pimenta eu acho que teria ficado perfeito, pois, os temas eram complementares, mas a sessão do Fabiano e do Luti entrou mais a fundo no otimizador. Para finalizar bem o evento, assisti a palestra da Viviane Ribeiro e do Ruy Pimentel sobre Soluções de Alta disponibilidade e Disaster Recovery para o SQL Server. Essa já é uma sessão “permanente” no TechED, pois, já a vi na agenda no anos anteriores, mas esse ano com outros apresentadores. Embora a sessão tenha abordado as implementações de alta disponibilidade de uma forma geral, houve um grande foco da apresentação no Failover Clustering. Achei interessante, pois, das técnicas de alta disponibilidade, a que mais trabalho no dia a dia é o Failover Clustering. Foi muito proveitoso ver como é gerenciado a parte de clustering de uma grande empresa como a DELL. Tive a oportunidade de ficar no mesmo quarto que o Ruy durante o evento e trocamos várias figurinhas sobre o assunto (DTC, distribuição de instâncias, divisão de memória, etc). O que mais valeu a pena pra mim foram os detalhes de como “emular” múltiplas instâncias Default no mesmo cluster. Esse macete já tinha valido a sessão e espero implementar no ambiente de um dos clientes muito em breve.

Palestrantes do TechED (boa parte de SQL Server)

No dia 01/10, terminei minha saga em São Paulo e eventos Microsoft com o Community Zone. Nem preciso dizer que eu adoro participar e contribuir com a comunidade Microsoft e esse evento foi bem direcionado para contribuidores. Foi ótimo para conhecer algumas pessoas do fórum que a gente conversa, lê blog, vê webcast, mas nunca conhece pessoalmente (de uma forma geral, os três dias foram assim). Aproveitei a ocasião e pude para discutir algumas iniciativas e eventos futuros para a comunidade de SQL Server. Alinhamentos gerais e boas coisas por vir…

DSC02622

De volta a Brasília e muita coisa pra estudar, organizar e entregar, mas o próximo encontro já está marcado. Dia 26/11 teremos o SQL Saturday que promete bastante. Mais um evento presencial com vários especialistas em SQL Server (e o melhor que é gratuito). Vou marcar presença. Se você quer se inscrever não perca tempo. Nos vemos lá.

Welcome to SQLSaturday #100  – Bem Vindo ao SQLSaturday #100
http://www.sqlsaturday.com/100/eventhome.aspx

[ ]s,

Gustavo