Arquivo

Arquivo do Autor

Pensar set-based, porque é tão difícil?

20 de setembro de 2012 11 comentários

Cá estou, no avião voltando pra casa depois de mais um trabalho de Tuning no SQL Server, processo que demorava 10 horas agora demora 24 minutos, cliente feliz e eu voltando pra casa pra curtir filho lindo e esposa maravilhosa. #SrNimbusRocks

 

Estou lendo um livro do Joe Celko chamado “Thinking in Sets” (pensando em sets) e eu gostaria de falar um pouco sobre isso.

Uma das dificuldades mais comuns para desenvolvedores que querem escrever códigos SQL que tenham boa performance, é abandonar o pensamento de linguagens procedurais (onde você diz tudo o que quer fazer) e começar a pensar em códigos declarativos (onde você apenas diz o que quer).

Desde pequenos (no meu caso com 16 anos) aprendemos que em programação devemos fazer tudo passo a passo. Quem lembra de quando seu professor de lógica de programação (se é que existe lógica nessa bagaça) disse: – “Boa noite turma, hoje vamos escrever nosso primeiro código!” e ele continuou dizendo: “- Quero que você pegue uma folha e escreva todos os passos necessários para trocar um pneu, ou fritar um ovo (no meu caso foi a do fritar o ovo)”. Eu tive essa aulinha no curso técnico de processamento de dados, lembro que escrevi algo parecido com:

 

1.       Pegar o ovo

2.       Abrir o ovo

3.       Colocar na frigideira

4.       Fritar o ovo

5.       Pegar o ovo da frigideira

6.       Colocar no prato

 

Escrevemos estes passos e achamos que estamos abafando… Dai o professor pega o papel e diz… ué, mas vai pegar o ovo da onde? Tá na geladeira? O ovo tá onde? Dai você diz… aaa … é verdade, vou corrigir me da ai… dai você adiciona:

 

1.       Pegar o ovo da geladeira

2.      

 

Dai o professor pega a folha e diz… ué, na sua casa você deixa a geladeira aberta?… Dai você diz… caramba (já começando a ficar irritado) é verdade.

 

1.       Abrir a geladeira

2.       Pegar o ovo da geladeira

3.      

 

Dai o professor pega a folha e diz… ué, e se não tiver mais ovo na geladeira? E se a geladeira estiver quebrada e o ovo estragado, e se a geladeira tiver um cadeado? E se você estiver com a mão ocupada com alguma coisa? Como vai pegar o ovo? … Dai você diz… Professor, na boa, vai a merda! Smile with tongue out E aprende a lição! Tem que prever TUDO, e dizer passo a passo o que deve ser feito.

 

É assim que aprendemos a programar, linha a linha, passo a passo! Porém, quando estamos falando de banco de dados, precisamos pensar que vamos trabalhar com um set, ou seja, um conjunto de linhas (melhor dizendo, um conjunto de elementos). É de responsabilidade do banco de dados decidir qual será a melhor maneira para ler os dados. Ainda que eles serão lidos linha a linha, quem decide a melhor maneira de fazer isso é o banco, não você. Por exemplo, o SQL Server pode optar por fazer uma soma, utilizando um algoritmo de hash, ou um join utilizando um merge e etc…

Lembre-se de que cada instrução (comando SQL) enviada para o banco de dados passa por uma série de validações (parse, binding, optimize…) e isso tem um custo. Por isso cursores e loops são tão ruims em relação a performance. Uma frase sensacional do Celko sobre cursores é a seguinte:

 

“A melhor técnica que você pode utilizar para melhorar a performance de um cursor é não usar cursor.”

Sarcastic smile Sensacional.

 

Quando você vê uma tabela chamada População você acha que nela existem populações, mas na verdade existem pessoas. Uma tabela de Floresta não contem florestas contem árvores… Temos que pensar em um todo, não no individuo. Ao invés de criar uma tabela chamada Abelhas para armazenar dados sobre abelhas, crie uma tabela chamada Colmeia! (é sério esse foi o único coletivo que eu conhecia quando estava escrevendo este artigo kkk Smile with tongue out)

 

Quer exemplos de como nós programadores pensamos em código procedural e não em sets? Vejamos…

 

Se eu disser, nesta mesa eu tenho 8 caixas de ovos (de galinha e codorna) fechadas e de tamanhos variados, quero que você olhe cada caixa e me entregue todas as caixas que contêm a mesma quantidade de ovos.

O que pensamos em fazer?

 

·    Contar quantos ovos tem em cada caixa, separar as caixas que tem a mesma quantidade e me entregar.

 

O que seria mais eficiente?

 

·    Olhar a quantidade exibida na embalagem, separar as caixas que tem a mesma quantidade e me entregar.

 

Reparou na diferença? … Uma solução conta todos os ovos, e a outra usa uma agregação… Uma solução vê os ovos e outra vê as caixas (um todo).

 

Vejamos outro exemplo que usa o caso clássico das tabelas de Pedidos e Itens de Pedidos.

 

Se eu disser, quero uma consulta que retorne todos os pedidos em que a quantidade de itens comprados é igual para todos os itens.

 

Uma alternativa seria escrever uma consulta que varre a tabela de “Itens de Pedidos” e executa uma subquery para verificar se existe algum item de pedido diferente da quantidade do item lido… Algo mais ou menos assim:

 

select a.CodigoPedido from itens_pedidos a

where not exists (select * from itens_pedidos b

                             where a.CodigoPedido = b.CodigoPedido

                                 and a.quantidade <> b.quantidade)

 

Ou seja, se existir algum pedido com quantidade diferente, ele será desconsiderado da consulta. Eu poderia escrever um cursor para responder a consulta, mas não vou fazer isso Smile.

 

Outra forma mais interessante seria o seguinte:

 

select CodigoPedido from itens_pedidos

group by CodigoPedido

having min(quantidade) = max(quantidade)

 

Repararam na diferença?… Eu sei que é mais difícil pensar assim, encontrar soluções deste tipo não é fácil, mas acreditem, é possível.

 

Estudem e entendam as “window functions” no SQL Server, entendam como usar funções de agregação e evitem cursores, sempre.

 

Outra coisa interessante que sempre falo, é em relação a formatação de dados via SQL e ordenação.

 

Não faça formatação no SQL, please, deixe para formatar alguma coisa, na aplicação, banco de dados sofre para incluir uma “mascara” no CNPJ, faça isso na aplicação e não no banco.

Para ordenação eu digo o mesmo. Ordenação é na aplicação e não no banco! Você sabia que a clausula ORDER BY não faz parte do SELECT ? Pois é, no padrão ANSI/ISO da linguagem SQL (structured query language) não existe order by no select, no ISO o order by faz parte da sintaxe de cursores. Os fabricantes de bancos de dados criam uma extensão dessa opção para que você a utilize nos selects. Fica a dica, quase sempre podemos ordenar do lado da aplicação com pouquíssimo trabalho.

 

Fecho o artigo com um ditado Turco bem interessante:

“Não importa o quão longe você foi por uma estrada errada, volte!”

 

Abraços e bom código!

Desafio SQL Server – Performance

17 de setembro de 2012 29 comentários

UPDATE 18-09-2012: Este desafio é baseado em um cenário real que trabalhei, porém eu não me atendei a um fato importantíssimo… No cenário original o group by era efetuado em 23 colunas. Isso significa que se você tentasse criar um índice com base em todas as colunas do group by, o SQL batia o limite de 16 colunas como chave de um índice. O que quero dizer com isso é que não vale criar uma view indexada para ajudar nesta solução Smile

Quando criei este cenário de exemplo eu não me atentei a este ponto extremamente importante.

De qualquer forma, meus parabéns para o Evandro Camara que me mandou a solução que roda em menos de 1 segundo utilizando a view indexada. Vou considerar a resposta dele como válida, e estou mudando o desafio para… Não vale usar a view indexada… (ou seja, igual ao cenário original que trabalhei)… Eu poderia mudar o cenário para usar mais de 16 colunas no group by… daria na mesma…

Na solução do desafio falo melhor em relação ao beneficio da view indexada neste caso.

————————————————————————————————

Galera, depois do desafio de segurança da semana passada, vamos para um desafio sobre performance!

 

Este vai ser um pouco mais simples e vai ser no mesmo esquema do último desafio, ou seja, ganhador vai levar uma cópia do meu livro impressa.

 

A ideia do desafio é simples, temos uma consulta, e precisamos efetuar as modificações necessárias para melhorar a performance, você pode usar qualquer recurso disponível no SQL Server 2008 R2, a ideia é ter performance na LEITURA, ou seja, se precisar criar 50 índices (por favor, não crie!) pode criar, reescrever a consulta também vale, contanto é claro que os resultados sejam os mesmos.

É obrigatório usar MAXDOP 1 para evitar paralelismo.

 

Vejamos o cenário:

 

USE tempdb

GO

— Prepara ambiente

— Aproximadamente 2 minutos para rodar

— Tab com 287368 KB

IF OBJECT_ID(‘OrdersBig’) IS NOT NULL

  DROP TABLE OrdersBig

GO

CREATE TABLE OrdersBig (OrderID int NOT NULL IDENTITY(1, 1),

                        CustomerID int NULL,

                        OrderDate date NULL,

                        Value numeric (18, 2) NOT NULL)

GO

ALTER TABLE OrdersBig ADD CONSTRAINT xpk_OrdersBig PRIMARY KEY CLUSTERED  (OrderID)

GO

— Tab com 5 milhões de linhas

INSERT INTO OrdersBig(CustomerID, OrderDate, Value)

SELECT TOP 5000000

       ABS(CONVERT(Int, (CheckSUM(NEWID()) / 10000000))),

       CONVERT(Date, GetDate() ABS(CONVERT(Int, (CheckSUM(NEWID()) / 10000000)))),

       ABS(CONVERT(Numeric(18,2), (CheckSUM(NEWID()) / 1000000.5)))

  FROM sysobjects a, sysobjects b, sysobjects c, sysobjects d

GO

ALTER TABLE OrdersBig ADD CountCol VarChar(20)

GO

UPDATE TOP (50) PERCENT OrdersBig SET CountCol = ‘Count’

WHERE CountCol IS NULL

GO

UPDATE TOP (50) PERCENT OrdersBig SET CountCol = ‘CountDistinct’

WHERE CountCol IS NULL

GO

UPDATE OrdersBig SET CountCol = ‘CountDistinct_1’

WHERE CountCol IS NULL

GO

CHECKPOINT

GO

 

O comando acima criamos uma tabela chamada OrdersBig com 5 milhões de linhas, a consulta é a seguinte:

 

— Problema

/*

  Consulta demora 1 minutos e 41 segundos para rodar e o uso dos recursos são:

  CPU: 96503

  Reads: 75902646

  Writes: 100350

  Duration (ms): 101522

*/

CHECKPOINT

DBCC DROPCLEANBUFFERS

DBCC FREEPROCCACHE

GO

SET STATISTICS IO ON

SELECT a.CustomerID,

       a.CountCol,

       CASE a.CountCol

         WHEN ‘Count’ THEN COUNT(1)

         WHEN ‘CountDistinct’ THEN COUNT(DISTINCT a.OrderDate)

         WHEN ‘CountDistinct_1’ THEN COUNT(DISTINCT 1)

         ELSE NULL

       END AS Cnt,

       CASE (SELECT AVG(b.Value)

               FROM OrdersBig b

              WHERE b.CustomerID = a.CustomerID)

            WHEN 1000 THEN ‘Média = 1 mil’

            WHEN 2000 THEN ‘Média = 2 mil’

            WHEN 3000 THEN ‘Média = 3 mil’

            WHEN 4000 THEN ‘Média = 4 mil’

            WHEN 5000 THEN ‘Média = 5 mil’

            ELSE ‘Não é número exato’

       END AS Sts

  FROM OrdersBig AS a

 GROUP BY a.CustomerID, a.CountCol

 ORDER BY a.CustomerID

OPTION (MAXDOP 1)

SET STATISTICS IO OFF

GO

 

Antes de rodar a consulta estou rodando um FREEPROCCACHE, DROPCLEANBUFFERS e CHECKPOINT para forçar a compilação do plano e gerar leitura física.

 

A consulta cima roda em aproximadamente 1 minuto e 41 segundos e utiliza os recursos descritos acima (nos comentários).

 

O desafio consiste em melhorar a consulta para que ela rode no menor tempo possível e utilize menos recursos. A medida mais importante é, TEMPO, ou seja, quanto menos tempo para rodar a consulta melhor.

Eu consegui baixar o tempo da consulta acima para 7 segundos utilizando os seguintes recursos:

 

/*

  Consulta demora 7 segundos para rodar e o uso dos recursos são:

  CPU: 6552

  Reads: 22523

  Writes: 2

  Duration (ms): 7249

*/

 

Se você conseguir chegar próximo aos números acima me manda sua solução que vou testar e medir os resultados, os seus números com certeza serão um pouco diferente do meu já que crio os dados dinamicamente, os valores exibidos pra você podem ser diferentes dos exibidos pra mim.

Vou dar uma dica… que dica o que rapaz… se vira! kkk

Boa sorte e divirta-se.

Solução desafio de segurança no SQL Server

14 de setembro de 2012 16 comentários

Primeiramente quero agradecer a todos que se interessaram pelo desafio e perderam algum tempo tentando chegar na solução Smile. Eu gostei tanto que já tenho outro desafio preparado para publicação, mas vamos por partes…

 

Se você não sabe do que estou falando veja o desafio aqui.

 

A única pessoa que conseguiu responder o desafio mandou uma solução MUITO MELHOR que a minha Smile hahuahuuh… não era por menos, quem respondeu o desafio foi o grande (modo de dizer) Fabricio Catae (blog|twitter) o ninja!

Parabéns Catae você é o cara! No próximo desafio vou colocar um aviso do tipo “Aviso: Ninjas estão automaticamente desclassificados! huahuahua

 

Vamos analisar primeiro a minha solução, e depois veremos a do Catae.

 

Solução 1:

Minha solução consiste em trabalhar com as mensagens de erro do SQL Server para ver as informações que supostamente não deveríamos saber que existe. Isso é muito utilizado em SQL Injection, ou seja, se você não trata as mensagens de erro que envia para sua aplicação, tá muito errado, cuidado.

Primeiramente vejamos o plano de execução do select na view: (pra quem tá chorando nessa hora dizendo, aaaa mas não vale ver o plano e tals… não Sr. eu disse que vale tudo, ou seja, você conhece as tabelas, pode ver o plano logado com um usuário que te permissão para isso, mas para “ver” os dados que não poderíamos ver você precisa estar logado com o usuarioquenaopodenada)

 

clip_image001[4]

 

Vamos executar este plano passo a passo:

  1. Executa o Clustered Index Scan na tabela TabFuncionarios, ou seja, vai fazer um SCAN na tabela, pra quem ainda não entendeu, isso significa que o SQL vai passar por TODAS as linhas da tabela. É importante que você entenda isso.
  2. Nested Loops trabalhando como Anti Semi Join (left anti semi = not exists, right anti semi = exists). Um Anti Semi Join é um Join que retorna dados de apenas um dos lados do join, ou seja, neste caso apenas colunas da tabela TabFuncionarios serão retornadas.
  3. Para cada linha recebida pelo Scan na TabFuncionarios, o Nested Loops chama o Clustered Index Scan na tabela TabEsconderFuncionarios.
  4. Clustered Index Scan na tabela TabEsconderFuncionarios retorna as linhas da tabela TabEsconderFuncionarios para o Nested Loops para ver quem faz o join, e consequentemente eliminar as linhas que não queremos exibir (validação do not exisits).

Bom, agora que sabemos que a PRIMEIRA COISA que o plano faz é varrer a tabela TabFuncionarios, podemos tentar aplicar um filtro nesta tabela para tentar gerar um erro de conversão… Por exemplo:

O que acontece se eu rodar o seguinte comando?

SELECT *

  FROM vw_TabFuncionarios

WHERE CONVERT(Int, Nome) = 0

Msg 245, Level 16, State 1, Line 1

Conversion failed when converting the varchar value ‘Fabiano’ to data type int.

 

Plano da consulta:

clip_image002[4]

O que acontece é que o SQL tenta converter a coluna Nome para Int e gera um erro de conversão. Ótimo, estamos chegando perto…

Repare que no plano de execução da consulta acima o SQL Server executa (Predicate) o CONVERT(Int, Nome) = 0 antes de fazer o join, ou seja, o SQL vai tentar fazer a conversão para TODAS as linhas da tabela, no caso acima na primeira linha lida o erro já é exibido, porém podemos incluir mais um filtro no valor que já conhecemos para ignorar o “Fabiano” por exemplo:

SELECT *

  FROM vw_TabFuncionarios

WHERE CONVERT(Int, Nome) = 0

   AND Nome NOT IN (‘Fabiano’)

 

Msg 245, Level 16, State 1, Line 1

Conversion failed when converting the varchar value ‘Gilberto’ to data type int.

 

Plano da consulta:

clip_image003[4]

 

Repare que agora a conversão deu erro no Gilberto, vamos inclui-lo na lista do IN:

SELECT *

  FROM vw_TabFuncionarios

WHERE CONVERT(Int, Nome) = 0

   AND Nome NOT IN (‘Fabiano’, ‘Gilberto’)

 

Msg 245, Level 16, State 1, Line 1

Conversion failed when converting the varchar value ‘Luciano’ to data type int.

 

Já conseguimos visualizar que existe um funcionário chamado Luciano Smile with tongue out, e pra saber qual é o salario dele? … Basta concatenar a coluna salario com o Nome e tentar converter tudo para Int…

SELECT *

  FROM vw_TabFuncionarios

WHERE CONVERT(Int, Nome + CONVERT(VarChar(30), Salario)) = 0

   AND Nome NOT IN (‘Fabiano’, ‘Gilberto’)

 

Msg 245, Level 16, State 1, Line 1

Conversion failed when converting the varchar value ‘Luciano25999.00’ to data type int.

 

Feito, conseguimos ver que o Luciano ganha R$25.999,00 por mês, tai o motivo dele estar na tabela EsconderFuncionarios J. Para ver quanto o Ivan ganha, basta incluir o Luciano na lista do IN…

Tem uma coisa importante que talvez você não tenha notado… Repare que para que isso tudo funcione o SQL Server primeiro tem que fazer a validação do NOT IN, e depois fazer a validação da conversão do nome para int = 0. Ou seja,

No WHERE, ele tem que executar primeiro isso “NomeNOTIN(‘Fabiano’,‘Gilberto’)” e depois “CONVERT(Int,Nome)= 0”, veja que ainda que nós tenhamos escrito o código especificando primeiro o CONVERT e depois o NOT IN, o SQL Server executou o NOT IN antes, vejamos isso no plano:

clip_image004[4]

O otimizador de consultas que decide quem ele deve executar primeiro, leia mais sobre isso neste post do Itzik. Ok, mas dai vem a pergunta, como forçar a ordem de execução das expressões e ter certeza de que nossa solução vai funcionar independente da versão do SQL Server que estou usando?  (No SQL Server 2000 a consulta acima não funciona).

Para forçar a ordem de execução das expressões e evitar (ou forçar) um erro devemos usar o CASE, portanto a consulta final ficaria assim:

— Rodar a consulta no escopo do UsuarioQueNaoPodeNada

EXEC AS USER = ‘UsuarioQueNaoPodeNada’

GO

— Quanto o Luciano ganha?

SELECT *

  FROM vw_TabFuncionarios

WHERE (CASE

          WHEN (Nome NOT IN (‘Fabiano’, ‘Gilberto’)) AND (CONVERT(Int, Nome + CONVERT(VarChar, Salario) ) = 0) THEN 0

          ELSE 1

        END) = 0

 

Solução 2 (by Fabrício Catae a.k.a. Ninja):

A solução do Catae é sensacional porque ela não retorna os dados utilizando as mensagens de erro do SQL. A ideia é simples (depois eu você vê é claro), ela consiste em gerar um lock exclusivo nas linhas da tabela TabEsconderFuncionarios e depois ler a view utilizando o hint ReadPast.

Pra quem não conhecia, o hint READPAST é similar ao famoso NOLOCK, a ideia é não ficar bloqueado por uma sessão que está com lock exclusivo nas linhas que você esta tentando ler. Porém diferentemente do NOLOCK (quer permite leitura suja para não ficar bloqueado) o READPAST “pula” as linhas que estão com lock por linha (KEY LOCK). Voltando ao nosso cenário, se as linhas da tabela TabEsconderFuncionarios estiverem bloqueadas com xLock (lock exclusivo) e RowLock (lock por linha) ao ler os dados da view, o SQL Server “entende” que não existe nada na tabela e consequentemente retorna tudo.

Vejamos na prática:

 

— Sessão 1

BEGIN TRAN

— Gerar lock exclusivo em tudo o que lê, ou seja,

— nas linhas Fabiano e Gilberto da TabFuncionarios

— e nas linhas ID 3 e 4 da TabEsconderFuncionarios

SELECT * FROM vw_TabFuncionarios WITH (xlock, rowlock)

WHERE Nome IN (‘Fabiano’, ‘Gilberto’)

GO

Em uma sessão eu inicio uma transação, e leio a view utilizando dois hints, o xlock para forçar a o lock exclusivo, e o rowlock para forçar o lock por linha (lembre-se o READPAST só “ignora” locks de linha).

Podemos consultar os locks gerado pela consulta na view utilizando o seguinte código:

— Sessão 1

— Consultar locks

— Gerou os locks esperados

SELECT OBJECT_NAME(partitions.OBJECT_ID) AS ObjectName,

       dm_tran_locks.resource_type,

       dm_tran_locks.resource_description,

       dm_tran_locks.request_mode

  FROM sys.dm_tran_locks

  LEFT OUTER JOIN sys.partitions

    ON partitions.hobt_id = dm_tran_locks.resource_associated_entity_id

WHERE request_session_id = @@spid

   AND resource_type = ‘KEY’

ORDER BY 1

 

clip_image005[4]

Conforme podemos observar o select na view gerou 2 locks na tabela TabFuncionarios (para os IDs 1 e 2, repare no where por nome) e 2 locks na tabela TabEsconderFuncionarios (nos IDs 3 e 4)

Se eu abrir outra sessão e tentar rodar um select na vw_TabFuncionarios eu vou ficar bloqueado pro causa dos locks exclusivos gerados na sessão 1, porém se eu rodar o código com o hint READPAST, o SQL vai ler as linhas de IDs 3 e 4 da tabela TabFuncionarios e ao tentar fazer o join com a TabEsconderFuncionarios ele não vai encontrar nada lá (porque ele “pula” as linhas bloqueadas), e consequentemente retorna os dados… Vejamos:

— Sessão 2

— Abrir outra sessão e ler os dados da view com READPAST hint

— Como os IDs da TabEsconderFuncionarios estão com lock na linha (importante que lock de linha)

— ao fazer o join para ver quem NOT EXISTS o SQL não ve nada, ou seja, posso ver todas as linhas

SELECT * FROM vw_TabFuncionarios WITH(readpast)

 

clip_image006[4]

Conforme podemos observar, conseguimos visualizar todos os dados da tabela.

É importante que o SQL Server gere os locks POR LINHA, se na sessão 1 o SQL Server gerar os locks por página ou tabela, esquece que não vai funcionar…

Se você esta se perguntando, como evitar esse comportamento… a resposta é… você poderia criar view com with(nolock) nas tabelas e dai ao tentar ler a view utilizando o READPAST o SQL Server ira gerar a seguinte mensagem de erro:

 

Msg 650, Level 16, State 1, Line 5

You can only specify the READPAST lock in the READ COMMITTED or REPEATABLE READ isolation levels.

 

Bom, é isso, espero que tenham gostado, quer mais? … J

Fique de olho que na semana que vem vou publicar outro desafio, desta vez mais simples, porém relacionado a performance.

Abs.

Categorias:Desafio, Virtual PASS BR

Aviso: Desafio de segurança no SQL, amanhã (14-09) posto o resultado

13 de setembro de 2012 Deixe um comentário

 

Galera, aviso pra quem está participando do desafio que bloguei na semana passada:

https://blogfabiano.com/2012/09/04/desafio-de-segurana-no-mssql-voc-consegue/

Na sexta-feira (15-09) no horário do almoço, vou publicar a resposta ok? Smile Se você ainda não tentou, corre que da tempo.

Abs.

Categorias:Virtual PASS BR

Desafio de segurança no MSSQL … Você consegue?

4 de setembro de 2012 22 comentários

UPDATE 14-09-2012 a resposta para este desafio foi publicada aqui:  https://blogfabiano.com/2012/09/14/soluo-desafio-de-segurana-no-sql-server/

Pessoal bora participar de um desafio de segurança no SQL Server e concorrer a uma cópia impressa do meu livro? Deixa eu explicar melhor.

 

Um dos muitos benefícios em relação ao uso de views no SQL Server é que com elas conseguimos “esconder” as tabelas do usuário final, podemos por exemplo definir que determinado usuário só tem acesso a view e não as tabelas que a view acessa. Ou seja, ele consegue executar um “select * from vw_funcionarios“ (vw_funcionarios é a view) mas não consegue executar um “select * from tab_funcionarios“ (tab_funcionarios é a tabela).

 

Utilizando a view, também conseguimos definir quais colunas serão visíveis para o usuário garantindo que ele somente veja os dados que ele realmente pode ver.

 

O desafio que estou propondo é o seguinte:

Vou descrever um cenário que usa views para “esconder” dados sigilosos, aparentemente o cenário é seguro e quero que você “hackeie” o SQL Server para conseguir visualizar os dados que supostamente você não deveria ver.

 

Vamos lá, o ambiente é o seguinte, tenho duas tabelas uma chamada TabFuncionarios que contêm os dados de todos os funcionários de uma empresa, e tenho outra tabela chamada TabEsconderFuncionarios que contêm os IDs dos funcionários que não quero que sejam retornados pela view. Desta forma dinamicamente consigo criar uma tela para configurar quais usuários são visíveis pela view.

 

Vejamos o schema do banco:

 

USE tempdb
GO
IF OBJECT_ID('vw_TabFuncionarios') IS NOT NULL
  DROP VIEW vw_TabFuncionarios
GO
IF OBJECT_ID('TabEsconderFuncionarios') IS NOT NULL
  DROP TABLE TabEsconderFuncionarios
GO
IF OBJECT_ID('TabFuncionarios') IS NOT NULL
  DROP TABLE TabFuncionarios
GO
CREATE TABLE TabFuncionarios(ID      Int PRIMARY KEY,
                             Nome    VarChar(200),
                             Salario Numeric(18,2));
GO
CREATE TABLE TabEsconderFuncionarios(ID_Funcionario Int PRIMARY KEY REFERENCES TabFuncionarios(ID))
GO

Ou seja, tenho a TabFuncionarios que contem os dados dos funcionários, e a TabEsconderFuncionarios que contêm o ID correspondente a tabela TabFuncionarios.

 

Vamos inserir alguns dados de exemplo:

 

-- Inserir 4 funcionarios
INSERT INTO TabFuncionarios(ID, Nome, Salario)
VALUES(1, 'Fabiano', 1900),(2, 'Gilberto',2050),(3, 'Luciano', 25999),(4, 'Ivan', 9500)
GO
-- Esconder os funcionário "Luciano" e "Ivan" que ganham MUITO bem! 😉
INSERT INTO TabEsconderFuncionarios (ID_Funcionario) VALUES (3), (4)
GO

Ou seja, temos 4 funcionários, e os funcionários que quero esconder estão na tabela TabEsconderFuncionarios. Agora precisamos apenas criar uma view que somente retorne os usuários que não estão na tabela TabEsconderFuncionarios:

 

IF OBJECT_ID('vw_TabFuncionarios') IS NOT NULL
  DROP VIEW vw_TabFuncionarios
GO
-- Criar a view para exibir somente funcionários que ganham pouco
CREATE VIEW vw_TabFuncionarios
WITH SCHEMABINDING
AS
SELECT ID,
       Nome,
       Salario
  FROM dbo.TabFuncionarios
 WHERE NOT EXISTS(SELECT 1 
                    FROM dbo.TabEsconderFuncionarios
                   WHERE TabEsconderFuncionarios.ID_Funcionario = TabFuncionarios.ID)
GO

-- Consultar os dados da view
SELECT * 
  FROM vw_TabFuncionarios
GO

Temos o seguinte:

clip_image001[4]

 

Ou seja, funciona! Agora seria apenas o caso de criar um usuário que só tem permissão de select na view e pronto.

 

CREATE LOGIN UsuarioQueNaoPodeNada WITH PASSWORD = '102030', CHECK_POLICY = OFF
GO
CREATE USER UsuarioQueNaoPodeNada FOR LOGIN UsuarioQueNaoPodeNada;
GO
GRANT SELECT ON vw_TabFuncionarios TO UsuarioQueNaoPodeNada
GO

Portanto o desafio é, logando com o usuário “UsuarioQueNaoPodeNada” como descobrir os funcionários escondidos, e depois disso, quanto eles ganham?

As regras são: Não tem regras! Vale tudo! (contanto que você esteja logado com o UsuarioQueNaoPodeNada)

 

O ganhador será divulgado aqui no meu Blog e vou enviar a cópia do meu livro pelo correio (eu pago o frete é claro!), se você conseguir, me manda um e-mail no fabiano_amorim arroba(que não é do boi) bol ponto com ponto br.

 

O primeiro que me mandar o e-mail será o ganhador. Depois é claro, vou postar a solução, e explicar tudo com detalhes…

 

Boa sorte e have fun!

 

Abs.

Categorias:Virtual PASS BR

Fabiano vs preços abusivos nos aeroportos…

3 de setembro de 2012 17 comentários

Eu sempre reclamo dos preços abusivos, ridículos e cretinos que se cobram de qualquer coisa que você pense em comprar nos aeroportos brasileiros.

Sim, eu disse aeroportos Brasileiros porque sei que fora do Brasil não existe esta palhaçada. Me lembro bem que quando eu estava na Índia (sim estou comparando o Brasil com a Índia) comprei uma batata Pringles por 1.99USD, aqui deve custar uns 15 reais… no mercado custa 7. Nos EUA ? aaa nem vou nem comparar com os EUA…

Falando em preço de coisas fora do Brasil lembrei do preço do McDonalds de Londres… da uma olhada:

clip_image002

Sim meu caro, são 3.99 libras… vamos fazer vezes 3?… 12,00 reais… alguém ai já pagou 12 reais em uma promoção do McDonalds ? Sad smile

Bom, voltando pro assunto dos aeroportos… Cá estou no aeroporto de Campinas-SP e fui comprar um lanche para “almoçar” (haha, almoçar no aeroporto?… a tá) e me deparei com a seguinte “promoção”:

clip_image004

Olha que coisa boa, um misto + uma bebida + um chocolate por APENAS R$ 19,90… não quero discutir apenas o fato de pagar CARÍSSIMO por uma merda de lanche esquentado no Micro-ondas (sim, é daqueles congelados que você pega e entrega pra tiazinha por no micro). O que quero discutir é o fato dessa palhaçada de promoções que vemos por ai… De R$ 2.999,00 por R$ 999,00… aaa para né… 2371986% de desconto? … tá loco? Quem faz isso? … Cobra a porcaria do preço justo e boa… para de chamar o consumidor de idiota fazendo isso… voltando ao aeroporto…

Eu é claro, fui aproveitar a promoção, já que o valor normal por TUDO isso seria de R$ 26,90… Mas como eu não queria o chocolate, e SABIA que tinha palhaçada nesse preço pedi só um misto e uma coca… O preço?

clip_image005

Ou seja, pra chegar no valor “fora” da promoção o chocolate de 70gr teria que custar R$8,10… aaa vamos parar de palhaçada né…

O que podemos fazer com isso?… Eu sempre falo pra minha esposa, se eu fosse advogado, eu seria MUITO rico, porque eu ia processar TODOS esses safados…

Será que não tem ninguém nesse país com vontade de fazer as coisas serem sérias… de verdade, sem essas palhaçadas? …

Depois eu falo que não vejo a hora de ir embora daqui tem gente que não me entende…

Pra fechar o post, vejam que sensacional o preço deste tênis, fiz questão de tirar a foto… Façam as contas… 5 x 59,98 é igual a ???

clip_image007

Categorias:Pessoal

SQLSat147 em Recife-PE, Video e Arquivos…

29 de agosto de 2012 3 comentários
image

Pessoal, no sábado passado dia 25/08 aconteceu o SQL Saturday 147 em Recife-PE.

O evento foi muito bom, mesmo com a baixa no número de inscritos versus o número de participantes… No Brasil, infelizmente temos um problema grave de se inscrever em eventos e depois não se preocupar se realmente vamos participar ou não…

No caso do SQL Sat, muito gente se inscreve só porque é de graça, e somente depois de fazer a inscrição vai pensar se realmente vai participar ou não… Sou a favor de cobrar 15 reais para participar do evento, e reverter o dinheiro para alguma obra de caridade… Desta forma tentamos minimizar os malas que se inscrevem só por se inscrever…

Abaixo você pode fazer o download dos arquivos que utilizei na minha sessão:

https://skydrive.live.com/redir?resid=52EFF7477E74CAA6!2052

Caso você tenha perdido, pode assistir minha sessão online… Novamente o @leomatos_sql fez o GRANDE favor de gravar minha apresentação, e eu já subi pro youtoba… segue o vídeo abaixo:

SQLSat147–Fabiano Amorim

 

http://www.youtube.com/watch?v=edAUgQ7XBe0

Errata:

Durante minha apresentação, ao falar de Non-SARGs eu dei a entender que você pode usar a function SubString em uma coluna na clausula WHERE que o SQL Server mesmo assim conseguiria fazer um seek em um possível índice… Não é bem assim ok ? … Foldable expressions que eu mencionei, são expressões que o SQL Server executa em tempo de compilação para conseguir fazer uma estimativa mais precisa da cardinalidade (quantidade de linhas que serão retornadas) do filtro.

Em alguns casos o SQL Server consegue sim fazer algumas “mágicas” para conseguir usar um possível índice mesmo que você manipule a coluna utilizada para filtro… que é o caso de uso de CONVERT ou ISNULL (nem sempre QO consegue fazer o seek)… Continuo dizendo o seguinte… se mexer na coluna do WHERE, OLHE NO PLANO para ter certeza de que o SQL Server conseguiu fazer uso do índice…

Se ficar alguma dúvida, me avisa que explico isso melhor…

Abs.

Série Mentes Brilhantes – Parte 17

6 de agosto de 2012 1 comentário

O que fazer quando você quer dar uma impressão inicial de que MUITAS linhas serão retornadas, e de repente…

Haha pegadinha do malandro! Just kidding Isso não é um produto cartesiano…

 

SELECT  Orders.OrderID,
Orders.CustomerID,
Orders.OrderDate,
Customers.CompanyName,
Employees.Title
FROM Orders
CROSS JOIN Customers
LEFT OUTER JOIN Employees
ON Orders.EmployeeID = Employees.EmployeeID
INNER JOIN Shippers
ON Shippers.ShipperID = Orders.ShipVia
AND Orders.CustomerID = Customers.CustomerID

Obs.: Fiz uma pequena alteração para deixar o código ainda mais tenso…rs…

O original é um código que meu irmão Marcos Paulo me mandou, ele pegou essa belezinha sendo gerado pelo LINQ.

SELECT  Orders.OrderID,
Orders.CustomerID,
Orders.OrderDate,
Customers.CompanyName
FROM Orders
CROSS JOIN Customers
WHERE Orders.CustomerID = Customers.CustomerID

Quer contribuir para essa série e fazer a felicidade de muitos? Me manda sua belezura para fabiano at amorim arroba (que não é do boi) dominio do bol ponto com ponto br

Abs.

Categorias:Mentes Brilhantes

Série Mentes Brilhantes – Parte 16

24 de julho de 2012 4 comentários

Pois é galera, para nossa felicidade a famosa série mentes brilhantes vai voltar… Open-mouthed smile

A série mente brilantes é baseada em códigos que um amigo de um primo do meu cunhado  viu por ai e me contou. Just kidding

Pra quem ainda não viu segue o link com os outros 15 itens da série: https://blogfabiano.com/category/mentes-brilhantes/

Espero que se divirtam!

A de hoje é:

O que fazer quando você quer que sua rotina só rode depois da meia noite prevendo mudança de fuso-horário?

Fácil faça uma validação com IF…

DECLARE @Dt DateTime = GetDate()

IF DATEPART(hour, @Dt) >= 24
BEGIN
  PRINT 'Entrou no IF'
END

Sarcastic smile

Categorias:Mentes Brilhantes

Statistics used in a cached query plan

3 de julho de 2012 18 comentários

Today I was working on the material for the on-demand (online) training we’ll offer in next few days at Sr.Nimbus when I find-out a very interesting thing.

“The statistics used in a query plan are stored in a cached execution plan, and you can see them using the special trace flag 8666 (there is always a traceflag).”

It is also possible to identify which statistics were used in a query plan compilation if you run the query using traceflags 9292 and 9204 as Query Optimizer Guru Paul White (blog|twitter) showed here.

What I’m saying is that you can also see in the XMLPlan the used statistics.

Let’s see a small sample of what I’m saying…

First let’s clear the cache plan memory area:

 1: DBCC FREEPROCCACHE()

Now let’s run a small query on the Northwind database:

 1: USE Northwind

 2: GO

 3: SELECT Employees.FirstName, COUNT(Orders.OrderID)

 4:   FROM Orders

 5:  INNER JOIN Employees

 6:     ON Orders.EmployeeID = Employees.EmployeeID

 7:  WHERE Employees.FirstName = 'Steven'

 8:  GROUP BY Employees.FirstName

 9: GO

This is the plan:

clip_image001

Running the same query with TF 8666 on, we’ve the following information in the xml query plan (right click in the select operator and select properties):

clip_image002

This new element InternalInfo in the XML plan is documented on the current schema http://schemas.microsoft.com/sqlserver/2004/07/showplan/showplanxml.xsd

The information we can see here are confuse and used by Microsoft people to debug query plans, but, one specific tag is more interesting than others… This is the ModTrackingInfo tag. There we can see the statistics used information, let’s dig in:

clip_image003

In the image above we can see two “Recompile” tags (collapsed), in fact we’ve one tag for each table in the query (in our sample, table orders and table employees).

clip_image004

Isn’t that nice or what?

Could be this be even more interesting? Yes.

All this information are also stored in the cached plans. That means if you query the Cached Plans DMVs with the TF turned on, we’ll be able to use xquery to query the used statistics.

For instance:

 1: DBCC TRACEON(8666)

 2: GO

 3: WITH XMLNAMESPACES ('http://schemas.microsoft.com/sqlserver/2004/07/showplan' as p)

 4: SELECT qt.text AS SQLCommand,

 5:        qp.query_plan,

 6:        StatsUsed.XMLCol.value('@FieldValue','NVarChar(500)') AS StatsName

 7: FROM sys.dm_exec_cached_plans cp

 8:     CROSS APPLY sys.dm_exec_query_plan(cp.plan_handle) qp

 9:     CROSS APPLY sys.dm_exec_sql_text (cp.plan_handle) qt

 10:     CROSS APPLY query_plan.nodes('//p:Field[@FieldName="wszStatName"]') StatsUsed(XMLCol)

 11:  WHERE qt.text LIKE '%Steven%'

 12:    AND qt.text NOT LIKE '%sys.%'

 13: GO

 14: DBCC TRACEOFF(8666)

 15: GO

Will Return:

clip_image001[6]

I’m sure you can do better xquery than I to query this from cacheplan.

Now the boring part of the this… First of all and the most important. This traceflag IS NOT DOCUMENTED so please be very very very careful to use it, and use only for tests purposes, never use it in a production environment. Also when I tried to use it on SQL Server 2008R2 sometimes I’ve got nasty errors generating dump and closing my session.

Since I‘m aware, it works fine on SQL Server 2012, at least I didn’t received any error on using it.

Let me know if you try it on an SQL 2005 instance… And also about bugs and discovers you may find with it Smile.

Happy query.

Categorias:Não categorizado