Filtros de fechas · Ejemplos con horas

Filtrar entre dos fechas en SQL: incluye todo el último día

Construye un rango correcto, comprueba sus límites y adapta la consulta al tipo de fecha que guarda tu base de datos.

Actualizado: 29 septiembre 2026Ejemplos comprobados en SQLite
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.

SQLite · datos para reproducir el ejemplo
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);
SQLite · filtro con límite final exclusivo
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;
Resultado: tres eventos dentro del periodo
idcreado_en
22026-09-10 00:00:00.000
32026-09-12 14:30:00.000
42026-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.

SQLite · comparación que deja fuera parte 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.

Elige el rango según lo que quieras incluir
NecesidadLímitesDetalle
Días completos sobre DATEInicio y final inclusivosBETWEEN puede ser adecuado si no hay hora.
Días completos sobre fecha y horaInicio inclusivo; siguiente día exclusivoIncluye todas las horas del último día.
Turno de 09:00 a 17:00Desde 09:00, antes de 17:00El evento de las 17:00 pertenece al turno siguiente.
Dos instantes exactos, ambos incluidosDesde el primero hasta el segundo, inclusiveBETWEEN 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

PostgreSQL · límites tipados
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

MySQL · límites convertidos a 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

SQL Server · variables con tipo 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
SQLite · solución del ejercicio
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.