Pensar set-based, porque é tão difícil?
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! 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.”
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 )
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 .
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!
Post “Quentinho” hein. Realmente muito bom.
Valeu mano… ficou legal mesmo :-), tbm gostei de escrever.
Muito bom o post Fabiano, vou repassar para os desenvolvedores daqui da empresa!
Valeu Vlad, espero que eles também gostem!
Abs.
Muito bom!! Parabéns =)
Valeu! 😉
Show. Fabiano! Estou lendo seu livro e está ajudando em muito… Parabens!
Valeu Tiago,
Eu quero MUITO escrever um livro em português contemplando todo o tema otimização de consultas… talvez um dia saia do papel né? 🙂
Abs.
Março de 2018, li o artigo todo e achei interessantíssimo. Grato pelo conteúdo.
E pela frase turca “Não importa o quão longe você foi por uma estrada errada, volte!”.
Valeu Elcidio, feliz de saber que você gostou 🙂 !!!
Abs.
Fabiano