Dicas de T-SQL – Função Minutos Úteis

Olá Pessoal,

Nesse post quero mostrar como conseguimos melhorar o desempenho de uma função que calcula os minutos úteis e o impacto na performance ao utilizar um loop com o comando WHILE. O intuito aqui também será mostrar como às vezes é importante pensar como podemos fazer uma mesma tarefa, mas de uma forma mais inteligente e eficiente.

OBS: Vou colocar o link para DOWNLOAD dos scripts no final do post.


Função Minutos Úteis – COM WHILE:

Em mais um caso do dia a dia, um dos nossos clientes informou que estava com muita lentidão em uma função que calcula os minutos úteis entre duas datas. Você poderia utilizar essa função por exemplo para calcular o tempo de espera/fila em minutos úteis.

Segue abaixo o código da função que chamei de “fncMinutos_Uteis_While”:

Analisando o código da função, verificamos que ela utiliza um loop com o WHILE para somar um minuto na data inicial até que ela atinja a data final.

Observações e Limitações:

  • Horário Útil -> 08 às 18 horas (17:59:59 horas mais especificamente), ou seja, são 10 horas úteis por dia;
  • Desconsidera sábados e domingos;
  • NÃO desconsidera horário de almoço;
  • NÃO desconsidera feriados;
  • Como o código dessa função é bem simples, isso facilita alguns tipos de ajustes. Por exemplo, para alterar o horário útil, basta alterar o IF dentro do WHILE. Ou para incluir o horário de almoço ou algum feriado, bastaria incluir mais alguns IFs dentro do WHILE também.

“Beleza Luiz, vamos fazer alguns testes pra ver se essa função funciona mesmo?” Bora!!!

Teste 1: Pela manhã temos 4 horas úteis, ou seja, 4 * 60 = 240 minutos;

Teste 2: O horário das 06 às 08 horas não é útil, por isso o resultado é o mesmo do teste anterior;

Teste 3: Das 12 às 18 horas temos 6 horas úteis, ou seja, 6 * 60 = 360 minutos;

Teste 4: O horário das 18 às 23 horas não é útil, por isso o resultado é o mesmo do teste anterior;

Teste 5: Não temos horas úteis das 18:30 às 20:00 horas. Com isso, o resultado é 0;

Teste 6: Das 08:30 às 09:17 temos 47 minutos úteis;

Teste 7: Das 17:00 horas de uma sexta-feira (17/01/2020) até as 09:30 horas de segunda-feira (20/01/2020), temos 150 minutos úteis (60 minutos na sexta e 90 minutos na segunda);

Teste 8: Por fim, temos um dia inteiro útil (10 horas úteis) e mais duas horas do outro dia, ou seja 600 + 120 = 720.

“Show Luiz!!! Mas calma aí, se eu passar um intervalo de um mês, a função vai somar minuto a minuto de cada um desses dias, é isso mesmo? Isso não é muito custoso?”

Pois é! Agora imagine o tempo que será necessário para executar essa função para milhares de registros em uma query! Na versão otimizada irei explicar melhor uma forma de resolver esse problema.

Teste de Performance:

Em primeiro lugar, vamos criar uma tabela de teste com 2 mil registros com um campo ID e outro com uma data.

Agora vamos fazer um SELECT nesses 2 mil registros e executar a função com o WHILE para cada um deles.

O tempo total de execução foi de mais de 11 segundos para apenas 2 mil registros. Agora imagine essa função com o WHILE sendo executada 1 milhão de vezes, isso iria demorar demaaaaais!!!

“Eitaaa!!! E agora???” Veremos como podemos melhorar isso!!! #gogogo


Tabela Dias Úteis:

O primeiro passo para a nossa função otimizada será utilizar uma tabela chamada “Dias_Uteis”. Essa tabela é muito importante na otimização, sendo uma das responsáveis por eliminar a necessidade de utilizar o loop com o WHILE e melhorar consideravelmente a performance da função. Nós também desconsideramos alguns feriados nela.

Segue abaixo o script de criação da tabela:

OBS: Nesse exemplo, inseri apenas os dias úteis dos anos 2019 e 2020. Você pode inserir um período maior ajustando as variáveis abaixo.


Função Minutos Úteis – SEM WHILE (Otimizada):

Agora vamos para a versão otimizada da função que chamei de “fncMinutos_Uteis”. Não vou explicar todos os passos dela, mas deixei o código com muitos comentários para tentar facilitar o entendimento.

Basicamente, nós trocamos o WHILE por um SELECT na tabela “Dias_Uteis” e criamos uma lógica para fazer alguns cálculos considerando os horários dos extremos (08 e 18 horas).

Observações e Limitações:

  • Horário Útil -> 08 às 18 horas (17:59:59 horas mais especificamente), ou seja, são 10 horas úteis por dia;
  • Desconsidera sábados e domingos;
  • Desconsidera feriados.
  • NÃO desconsidera horário de almoço.
  • Como o código dessa função NÃO é tão simples, isso pode dificultar algum tipo de ajuste. Contudo, depois você pode entender o código e fazer as alterações necessárias.

“Legal Luiz, mas o resultado será o mesmo da função anterior?” Vamos repetir o teste e conferir isso agora!

Conforme era esperado, o resultado ficou igual a função anterior! Show!

Teste de Performance:

Por fim, vamos fazer o teste final para validar se todas essas alterações irão trazer alguma melhoria na performance da função.

O tempo de execução que antes era de mais de 11 segundos, agora caiu pra quase 0 segundos. WOW!!!!


Conclusão:

As ideias que pretendo deixar como conclusão são:

  • CUIDADO ao utilizar funções no SQL Server com muitos registros, pois isso pode afetar a performance das queries.
  • Podemos ver como um LOOP executado milhares de vezes pode ser muito custoso para o SQL Server.
  • Conseguimos observar também como às vezes podemos obter grandes ganhos na performance ao reescrever uma rotina de uma forma mais otimizada. Claro que nem sempre será possível fazer esse tipo de alteração, mas tente questionar um pouco mais o: “mas sempre foi assim e está funcionando…”.
  • Função COM WHILE:
  • Função SEM WHILE:
  • Nesse caso, tivemos uma redução de mais de 11 segundos e a função passou a executar quase instantaneamente! Agora imagine uma tela no sistema que demorava mais de 10 segundos passar a retornar de forma instantânea, os usuários e clientes agradecem!!!

A DICA FINAL É: PENSE FORA DA CAIXA!!!


Melhorias futuras:

Segue abaixo alguns pontos que podem ser melhorados no futuro nessa função:

  • Desconsiderar o horário de almoço, quando for necessário;
  • Parametrizar o intervalo do horário útil para facilitar alterações no código. Com isso, você poderia alterar mais facilmente o seu horário útil. Por exemplo, de 08 às 19 horas, ou 07 às 22 horas, e por aí vai.
  • Fique à vontade para sugerir melhorias também =)

Download:

Segue abaixo os links para DOWNLOAD dos scripts utilizados no post:

  • [fncMinutos_Uteis]

https://github.com/luizvitorf/SQLServer/blob/master/Scripts/T-SQL/%5BfncMinutos_Uteis%5D.sql

  • [fncMinutos_Uteis_While]

https://github.com/luizvitorf/SQLServer/blob/master/Scripts/T-SQL/%5BfncMinutos_Uteis_While%5D.sql

  • [fncMinutos_Uteis] – Tabela Dias Uteis e Testes

https://github.com/luizvitorf/SQLServer/blob/master/Scripts/T-SQL/%5BfncMinutos_Uteis%5D%20-%20Tabela%20Dias%20Uteis%20e%20Testes.sql


Espero que tenha gostado e que isso também possa ser útil no seu dia a dia. Até o próximo post!

Me siga no LinkedIn e YouTube para ficar por dentro das novidades.

Abraço,

Luiz Vitor França Lima

Consultor SQL Server

Deixe uma resposta