SQL Curtas — Intervalos de datas

Um dos problemas mais habituais em programação SQL é pedir dados que aconteçam no intervalo de duas datas. O tipo de dados dos campos de data/hora variam conforme o SGBD (DATE, TIME, DATETIME, DATETIME2, SMALLDATETIME, etc.), mas o problema descrito em baixo é semelhante em todos.

Problema: Necessito dos registos cujo campo CampoData está no intervalo 2016-01-01 (inclusive) a 2016-12-31 (inclusive).

Solução 1:

SELECT [...] FROM [...] WHERE YEAR(CampoData) = 2016;

Funciona, mas…

Esta é uma forma fácil (dado tratar-se do ano 2016 inteiro), mas infelizmente a maioria dos intervalos não correspondem a anos de calendário (ou a meses, acrescentando o MONTH() à condição, por exemplo). Temos então que encontrar uma solução melhor para qualquer intervalo de datas.

Solução 2:

SELECT [...] FROM [...] WHERE CampoData BETWEEN '2016-01-01' AND '2016-12-31';

Não funciona como queremos…

Será que esta funciona? Bem, na maioria dos casos não. Porquê? Primeiro temos que perceber como funciona exactamente o BETWEEN:

SELECT [...] FROM [...] WHERE Quantidade BETWEEN 10 AND 20;

Este exemplo retorna valores sempre que a Quantidade for “maior ou igual a 10” e “menor ou igual a 20”, o que para inteiros funciona como esperado. Mas se for com datas? Voltando ao exemplo anterior, pedir “maior ou igual a 2016-01-01” é o que precisamos, mas pedir “menor ou igual a 2016-12-31” dá um efeito inesperado.

Quando registamos uma data (e.g. 2016-12-31) esta terá sempre que ter associada uma determinada hora. Se não for especificada, corresponde a 00:00:00 (meia noite). Ora, ao indicar “menor ou igual a 2016-12-31”, estamos na realidade a dizer para incluir o segundo 00:00:00 do dia 31, mas excluir os outros 23h59m59s desse dia. Assim, o BETWEEN não serve para datas e esta não é uma solução válida.

Solução 3:

SELECT [...] FROM [...] WHERE CampoData >= '2016 -01-01' AND CampoData < '2017-01-01';

— Solução geral!

E finalmente temos a resposta correcta. Atenção ao “maior e igual” à primeira data, e ao “menor” (mas não igual) ao dia seguinte à segunda data. Assim, queremos tudo o que aconteceu “até” (mas não incluindo) o dia 2017-0101 00:00:00, ou seja, tudo até (inclusive) 2016-12-31 23:59:59,999999(9)…

Compatibilidade:

  • SGBD: SQL Server; Oracle; MySQL/MariaDB; Outros;
  • Nota 1: Em alguns SGBD o BETWEEN tem comportamento diferente do indicado, pelo que deve ser evitado;
  • Nota 2: A representação das datas nas queries (neste caso ‘AAAA-MM-DD’) poderá variar conforme a configuração regional do SGBD, e para efeitos de exemplo assume conversão implícita, que nem sempre funciona.

Documentação:

Publicado na edição 54 (PDF) da Revista PROGRAMAR.