HOUR
La función HOUR extrae el componente de hora de un valor TIME, DATETIME o TIMESTAMP y lo devuelve como un entero. Para valores DATETIME, el rango es de 0 a 23 (formato de 24 horas). Sin embargo, para valores TIME puros, el rango puede ser mucho mayor porque MySQL permite valores TIME que superen las 24 horas (hasta 838 horas), ya que TIME representa una duración, no solo un momento del día.
El análisis por hora es una herramienta poderosa para entender el comportamiento de usuarios y sistemas. ¿A qué hora compran más los clientes? ¿Cuándo se satura el servidor? ¿En qué franja horaria es más efectivo enviar emails? Todas estas preguntas se responden con HOUR y un buen GROUP BY.
Sintaxis
HOUR(expresion_tiempo)El argumento puede ser un valor TIME, DATETIME, TIMESTAMP o una cadena con formato de hora válido. Devuelve un entero que representa la hora. Para DATETIME, el rango es 0-23. Para TIME, puede ser de 0 a 838.
Comportamiento básico
En su uso más común con valores DATETIME, HOUR devuelve la hora en formato de 24 horas:
SELECT
HOUR('2025-08-15 09:30:00') AS maniana,
HOUR('2025-08-15 14:45:00') AS tarde,
HOUR('2025-08-15 23:59:59') AS noche,
HOUR('2025-08-15 00:00:00') AS medianoche;| maniana | tarde | noche | medianoche |
|---|---|---|---|
| 9 | 14 | 23 | 0 |
La medianoche es la hora 0, el mediodía es la hora 12, y la última hora del día es la 23. No existe la hora 24 en un DATETIME.
HOUR con valores TIME
Con valores TIME puros, HOUR puede devolver valores mayores que 23 porque MySQL usa TIME tanto para momentos del día como para duraciones:
SELECT
HOUR('10:30:00') AS hora_normal,
HOUR('100:30:00') AS duracion_larga,
HOUR('838:59:59') AS maximo_time;| hora_normal | duracion_larga | maximo_time |
|---|---|---|
| 10 | 100 | 838 |
Esto es relevante si trabajas con columnas TIME que almacenan duraciones en lugar de horas del día. En la mayoría de aplicaciones web, trabajarás con DATETIME y el rango será siempre 0-23.
Caso práctico: análisis de tráfico por hora
El análisis horario más habitual es determinar en qué horas del día se concentra la actividad. Supongamos que tienes una tabla de accesos a tu tienda online:
SELECT
HOUR(fecha_acceso) AS hora,
COUNT(*) AS visitas,
COUNT(DISTINCT id_usuario) AS usuarios_unicos
FROM log_accesos
WHERE DATE(fecha_acceso) = '2025-01-15'
GROUP BY HOUR(fecha_acceso)
ORDER BY hora;| hora | visitas | usuarios_unicos |
|---|---|---|
| 0 | 45 | 32 |
| 1 | 22 | 18 |
| 2 | 15 | 12 |
| 6 | 89 | 67 |
| 7 | 234 | 178 |
| 8 | 567 | 423 |
| 9 | 890 | 645 |
| 10 | 1023 | 756 |
| 11 | 978 | 698 |
| 12 | 745 | 534 |
| 13 | 812 | 589 |
| 14 | 934 | 678 |
| 15 | 876 | 623 |
| 16 | 723 | 512 |
| 17 | 654 | 467 |
| 18 | 534 | 389 |
| 19 | 423 | 312 |
| 20 | 356 | 267 |
| 21 | 278 | 198 |
| 22 | 189 | 134 |
| 23 | 112 | 78 |
Este perfil horario muestra dos picos claros: uno por la mañana entre las 9 y las 11, y otro por la tarde entre las 14 y las 15. La información entre las 3 y las 5 de la madrugada es tan baja que ni aparece en los resultados. Estos datos son fundamentales para decidir cuándo lanzar campañas o cuándo programar el mantenimiento del servidor.
Caso práctico: detectar horas pico de ventas
Para una tienda online, saber las horas pico de ventas permite dimensionar la infraestructura y el equipo de soporte:
SELECT
HOUR(fecha_venta) AS hora,
COUNT(*) AS ventas,
SUM(total) AS ingresos,
ROUND(AVG(total), 2) AS ticket_medio
FROM ventas
WHERE YEAR(fecha_venta) = 2024
AND MONTH(fecha_venta) = 12
GROUP BY HOUR(fecha_venta)
ORDER BY ingresos DESC
LIMIT 5;| hora | ventas | ingresos | ticket_medio |
|---|---|---|---|
| 10 | 87 | 15660.00 | 180.00 |
| 14 | 82 | 14760.00 | 180.00 |
| 11 | 78 | 13260.00 | 170.00 |
| 20 | 73 | 13140.00 | 180.00 |
| 9 | 68 | 11560.00 | 170.00 |
Las 10 de la mañana son la hora con más ingresos, seguidas de las 14h y las 11h. Dato interesante: las 20h (8 de la noche) aparecen en el top 5, lo que sugiere que muchos clientes compran por la noche desde casa después del trabajo.
Caso práctico: filtrar por horario laboral
Muchos análisis necesitan separar las operaciones dentro del horario laboral de las que ocurren fuera de él. HOUR hace esto trivial:
SELECT
CASE
WHEN HOUR(fecha_registro) BETWEEN 9 AND 17 THEN 'Horario laboral (9-18h)'
WHEN HOUR(fecha_registro) BETWEEN 18 AND 22 THEN 'Tarde-noche (18-23h)'
ELSE 'Madrugada (23-9h)'
END AS franja,
COUNT(*) AS registros,
ROUND(COUNT(*) * 100.0 / SUM(COUNT(*)) OVER(), 1) AS porcentaje
FROM pedidos
WHERE YEAR(fecha_registro) = 2024
GROUP BY
CASE
WHEN HOUR(fecha_registro) BETWEEN 9 AND 17 THEN 'Horario laboral (9-18h)'
WHEN HOUR(fecha_registro) BETWEEN 18 AND 22 THEN 'Tarde-noche (18-23h)'
ELSE 'Madrugada (23-9h)'
END
ORDER BY registros DESC;| franja | registros | porcentaje |
|---|---|---|
| Horario laboral (9-18h) | 3250 | 62.5 |
| Tarde-noche (18-23h) | 1460 | 28.1 |
| Madrugada (23-9h) | 490 | 9.4 |
Este desglose confirma que la mayoría de pedidos se realizan en horario laboral, pero casi el 28% llega por la tarde-noche. Si tu servicio de atención al cliente cierra a las 18h, estás dejando sin soporte a casi un tercio de tus compradores.
Caso práctico: rendimiento del equipo por turno
Si tu empresa trabaja por turnos, HOUR te permite evaluar la productividad de cada turno:
SELECT
CASE
WHEN HOUR(fecha_cierre) BETWEEN 6 AND 13 THEN 'Turno mañana'
WHEN HOUR(fecha_cierre) BETWEEN 14 AND 21 THEN 'Turno tarde'
ELSE 'Turno noche'
END AS turno,
COUNT(*) AS tickets_cerrados,
ROUND(AVG(TIMESTAMPDIFF(MINUTE, fecha_apertura, fecha_cierre)), 0) AS tiempo_resolucion_min
FROM tickets_soporte
WHERE YEAR(fecha_cierre) = 2024
AND estado = 'cerrado'
GROUP BY
CASE
WHEN HOUR(fecha_cierre) BETWEEN 6 AND 13 THEN 'Turno mañana'
WHEN HOUR(fecha_cierre) BETWEEN 14 AND 21 THEN 'Turno tarde'
ELSE 'Turno noche'
END;| turno | tickets_cerrados | tiempo_resolucion_min |
|---|---|---|
| Turno mañana | 4520 | 35 |
| Turno tarde | 3890 | 42 |
| Turno noche | 1240 | 58 |
El turno de mañana resuelve más tickets y en menos tiempo. El turno de noche es más lento, lo cual tiene sentido: menor volumen pero también menor personal disponible para escalar problemas.
HOUR con NULL
HOUR devuelve NULL si la entrada es NULL:
SELECT
HOUR(NULL) AS resultado;| resultado |
|---|
| NULL |
Si tu tabla tiene columnas DATETIME que admiten nulos, las filas con valor NULL no se incluirán en las agrupaciones por hora, lo cual es generalmente el comportamiento deseado.
Combinación con otras funciones
HOUR se combina con MINUTE para crear franjas horarias más precisas, y con DATE para análisis cruzados de día y hora:
SELECT
DATE(fecha_venta) AS dia,
HOUR(fecha_venta) AS hora,
COUNT(*) AS ventas
FROM ventas
WHERE fecha_venta >= '2024-12-23'
AND fecha_venta < '2024-12-26'
GROUP BY DATE(fecha_venta), HOUR(fecha_venta)
HAVING COUNT(*) > 10
ORDER BY dia, hora;| dia | hora | ventas |
|---|---|---|
| 2024-12-23 | 10 | 23 |
| 2024-12-23 | 11 | 19 |
| 2024-12-23 | 14 | 21 |
| 2024-12-24 | 9 | 31 |
| 2024-12-24 | 10 | 45 |
| 2024-12-24 | 11 | 38 |
| 2024-12-24 | 12 | 27 |
Este análisis combinado día-hora muestra cómo el 24 de diciembre concentra un volumen de ventas muy superior al 23, especialmente en la franja de 9 a 12 de la mañana, justo antes de las compras de última hora navideñas.
En el siguiente artículo veremos MINUTE para extraer los minutos.
Escrito por Eduardo Lázaro
