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: