Contenido de esta guía
Cómo seleccionar registros entre dos fechas
Para filtrar una columna que contiene fecha y hora, usa WHERE fecha >= inicio AND fecha < fin_exclusivo. Si el informe debe incluir del 10 al 12 de septiembre, el límite superior es el 13 de septiembre a las 00:00. Así entran todas las horas del día 12, incluidas sus fracciones de segundo.
Antes de escribir la consulta, identifica si guardas un día (DATE), una fecha con hora o un instante con zona horaria. Un mismo texto de entrada puede requerir límites diferentes según ese tipo.
Ejemplo completo: del 10 al 12 de septiembre, ambos días incluidos
Un informe de actividad debe incluir cualquier evento registrado durante esos tres días. Estos datos ficticios añaden filas justo antes del inicio, en ambos extremos y sin fecha. Son los casos que permiten detectar un filtro incorrecto aunque parezca funcionar con los registros del centro.
Ejecuta la preparación una sola vez en una conexión de SQLite de pruebas. Guardamos texto con el mismo formato, precisión y zona temporal para que su orden al comparar cadenas coincida con el cronológico. En una aplicación, valida ese contrato al guardar los datos.
CREATE TABLE eventos_fechas_demo (
id INTEGER PRIMARY KEY,
creado_en TEXT
);
INSERT INTO eventos_fechas_demo (id, creado_en) VALUES
(1, '2026-09-09 23:59:59.999'),
(2, '2026-09-10 00:00:00.000'),
(3, '2026-09-12 14:30:00.000'),
(4, '2026-09-12 23:59:59.999'),
(5, '2026-09-13 00:00:00.000'),
(6, NULL);SELECT id, creado_en
FROM eventos_fechas_demo
WHERE creado_en >= '2026-09-10 00:00:00.000'
AND creado_en < '2026-09-13 00:00:00.000'
ORDER BY creado_en, id;| id | creado_en |
|---|---|
| 2 | 2026-09-10 00:00:00.000 |
| 3 | 2026-09-12 14:30:00.000 |
| 4 | 2026-09-12 23:59:59.999 |
La fila 1 queda antes del inicio. La fila 2 entra por la comparación >=. Las filas 3 y 4 pertenecen al último día solicitado. La fila 5 ya corresponde al siguiente día y no entra. La fila 6 contiene NULL: las comparaciones no resultan verdaderas y el filtro la descarta.
Descargar ejemplo completo en SQL
BETWEEN con fechas: cuándo sirve y cuándo pierde horas
BETWEEN incluye ambos extremos. Para una columna DATE sin hora, un límite del 10 al 12 puede expresar exactamente esos tres días. El problema aparece al aplicar ese mismo límite final a una columna que también guarda horas: el día 12 a medianoche no representa todas las horas del día 12.
SELECT id, creado_en
FROM eventos_fechas_demo
WHERE creado_en BETWEEN '2026-09-10 00:00:00.000'
AND '2026-09-12 00:00:00.000'
ORDER BY creado_en, id;Con nuestros datos, esta consulta devuelve solo la fila 2. Las filas 3 y 4 son posteriores a la medianoche del 12. Añadir 23:59:59 tampoco incluye todas las fracciones posibles. El siguiente día como límite exclusivo evita depender de la precisión.
| Necesidad | Límites | Detalle |
|---|---|---|
| Días completos sobre DATE | Inicio y final inclusivos | BETWEEN puede ser adecuado si no hay hora. |
| Días completos sobre fecha y hora | Inicio inclusivo; siguiente día exclusivo | Incluye todas las horas del último día. |
| Turno de 09:00 a 17:00 | Desde 09:00, antes de 17:00 | El evento de las 17:00 pertenece al turno siguiente. |
| Dos instantes exactos, ambos incluidos | Desde el primero hasta el segundo, inclusive | BETWEEN sirve si esos dos instantes son los límites reales. |
Cómo escribirlo en PostgreSQL, MySQL, SQL Server y SQLite
Las consultas siguientes aplican el mismo periodo a una tabla de tu aplicación llamada eventos. Son variantes de referencia para columnas de fecha y hora sin conversión de zona; no se ejecutan todas en SQLite. En código de aplicación, enlaza parámetros con el tipo adecuado mediante el controlador, en lugar de concatenar entradas del usuario.
PostgreSQL: columna timestamp without time zone
SELECT id, creado_en
FROM eventos
WHERE creado_en >= TIMESTAMP '2026-09-10 00:00:00'
AND creado_en < TIMESTAMP '2026-09-13 00:00:00'
ORDER BY creado_en, id;Si la columna es timestamp with time zone, los límites deben representar los instantes de inicio y fin del periodo en la zona de negocio. PostgreSQL distingue ambos tipos y su interpretación: consulta su documentación de fecha y hora.
MySQL: columna DATETIME
SELECT id, creado_en
FROM eventos
WHERE creado_en >= CAST('2026-09-10 00:00:00' AS DATETIME)
AND creado_en < CAST('2026-09-13 00:00:00' AS DATETIME)
ORDER BY creado_en, id;La conversión se aplica a las constantes. Para una columna TIMESTAMP, revisa también la zona de la sesión. Los literales temporales de MySQL y sus tipos definen cómo se interpretan las entradas.
SQL Server: columna datetime2
DECLARE @inicio datetime2(3) = '2026-09-10T00:00:00.000';
DECLARE @fin_exclusivo datetime2(3) = '2026-09-13T00:00:00.000';
SELECT id, creado_en
FROM eventos
WHERE creado_en >= @inicio
AND creado_en < @fin_exclusivo
ORDER BY creado_en, id;El separador T hace explícito el formato ISO 8601 admitido por datetime2. Si los valores representan zonas u offsets, revisa el modelo con datetimeoffset.
SQLite: texto homogéneo o instantes numéricos
El ejemplo inicial usa texto homogéneo. Si guardas Unix timestamps, compara con límites numéricos expresados en la misma unidad: segundos y milisegundos no son intercambiables. No mezcles textos con espacios, con T o con distintos offsets y esperes que su comparación como cadenas refleje siempre los mismos instantes. SQLite documenta las representaciones admitidas en sus funciones de fecha y hora.
Índices, parámetros y zonas horarias
Un índice ordinario sobre creado_en puede ser útil cuando comparas directamente la columna con los dos límites. Envolver cada fila en DATE(creado_en) cambia la expresión que debe resolver el motor; puede requerir otro plan o un índice sobre esa expresión. No garantiza un recorrido completo ni que el rango sea siempre más rápido: comprueba el plan con EXPLAIN y datos representativos.
Para un informe del día local con datos almacenados en UTC, calcula las dos medianoches locales y conviértelas a UTC antes de consultar. Un día de calendario no siempre dura 24 horas cuando cambia el horario. Evita obtener el final sumando 86.400 segundos sin considerar esa regla.
Si una pantalla recibe «hasta el día 12», transforma ese día en el comienzo del día 13 en la zona acordada. Documenta que el parámetro final es exclusivo. Esto permite reutilizar periodos consecutivos sin contar dos veces una fila situada en su frontera.
Ejercicio: recuperar solo el 12 de septiembre
Usa eventos_fechas_demo. Debes obtener las filas 3 y 4, excluir la medianoche del día 13 y mantener un orden estable. Piensa primero en los dos límites.
Ver solución y explicación
SELECT id, creado_en
FROM eventos_fechas_demo
WHERE creado_en >= '2026-09-12 00:00:00.000'
AND creado_en < '2026-09-13 00:00:00.000'
ORDER BY creado_en, id;El inicio incluye la medianoche del 12; el final excluye la medianoche del 13. El mismo patrón sirve para un día, una semana o un intervalo elegido por el usuario, siempre que calcules correctamente las fronteras.
Preguntas frecuentes sobre filtros de fechas
¿Cómo incluyo los registros sin fecha?
Añade OR creado_en IS NULL al grupo de condiciones del rango. Si hay otros filtros, como un cliente o un estado, encierra el rango y esa alternativa entre paréntesis para que se apliquen juntos. Incluye los registros sin fecha solo si el informe los necesita.
¿Cómo comparo dos columnas de fecha?
Si los tipos y la interpretación temporal son compatibles, usa una condición como fin >= inicio. Eso comprueba el orden de los valores. Para calcular cuántos días u horas los separan, consulta la guía de diferencia entre fechas.
¿Puedo comparar fechas guardadas como DD/MM/AAAA?
No confíes en su orden como texto: el día aparece antes que el año. Convierte y valida esos datos con las funciones del motor; después almacénalos con un tipo de fecha o una representación coherente. Cambiar cómo se muestran no cambia necesariamente cómo se almacenan.
Qué debes recordar
- Con horas, incluye el inicio y excluye el comienzo del periodo siguiente.
- Prueba filas en ambos límites y justo fuera del rango.
- Alinea tipos, formatos, precisión y zona temporal.
- BETWEEN incluye extremos; el significado del extremo final lo defines tú.
Continúa aprendiendo
Fuentes y alcance de la comprobación
Los datos, consultas y resultados del ejemplo reproducible y del ejercicio se han comprobado en SQLite. Las variantes de otros motores se han contrastado con su documentación; no se presentan como ejecutadas en esos servidores.