Las fechas en SQL son la parte menos estándar del lenguaje: cada motor tiene sus funciones, y a veces dan resultados distintos para la misma pregunta. Este ejemplo resuelve cuatro tareas habituales en SQLite, PostgreSQL y MySQL, con la salida real de cada uno.
También te puede interesar
Todas las consultas usan la base de ejemplo «tienda» del curso (clientes, productos, pedidos y sus líneas). Las que funcionan en SQLite tienen un enlace para abrirlas en el editor de SQL, que la trae cargada.
Pedidos por mes
Se convierte la fecha en el texto AAAA-MM y se agrupa por él. En SQLite, con strftime:
SELECT strftime('%Y-%m', fecha) AS mes, COUNT(*) AS pedidos
FROM pedidos
GROUP BY mes
ORDER BY mes;Ábrelo en el editor y cámbialo →
resultado · SQLitemes | pedidos --------+-------- 2026-01 | 3 2026-02 | 2 2026-03 | 3 2026-04 | 2 2026-05 | 2 2026-06 | 2 2026-07 | 2 2026-08 | 2 2026-09 | 2 2026-10 | 2
En PostgreSQL, to_char(fecha, 'YYYY-MM'), y en MySQL, DATE_FORMAT(fecha, '%Y-%m'); los dos devuelven la misma tabla.
SELECT to_char(fecha, 'YYYY-MM') AS mes, COUNT(*) AS pedidos
SELECT DATE_FORMAT(fecha, '%Y-%m') AS mes, COUNT(*) AS pedidos
Días entre dos fechas
Cuántos días pasaron entre el alta de cada cliente y su primer pedido. SQLite no tiene un tipo fecha: la diferencia se calcula con julianday, que convierte la fecha en un número de días:
-- Días entre el alta de cada cliente y su primer pedido
SELECT c.nombre, c.alta, MIN(p.fecha) AS primer_pedido,
CAST(julianday(MIN(p.fecha)) - julianday(c.alta) AS INTEGER) AS dias
FROM clientes c
JOIN pedidos p ON p.cliente_id = c.id
GROUP BY c.id, c.nombre, c.alta
ORDER BY dias DESC
LIMIT 4;Ábrelo en el editor y cámbialo →
resultado · SQLitenombre | alta | primer_pedido | dias ---------------+------------+---------------+----- Ana Torres | 2025-01-14 | 2026-01-10 | 361 Luis Fernández | 2025-02-03 | 2026-01-15 | 346 Camila Rojas | 2025-02-27 | 2026-01-28 | 335 Diego Martínez | 2025-04-09 | 2026-02-19 | 316
En PostgreSQL, restar dos fechas ya da un número de días; en MySQL se usa DATEDIFF. Las dos dan los mismos 361, 346, 335 y 316 días:
-- PostgreSQL MIN(p.fecha) - c.alta AS dias -- MySQL DATEDIFF(MIN(p.fecha), c.alta) AS dias
Los últimos 90 días, y un mes después del 31 de enero
Para que el ejemplo dé siempre lo mismo se usa una fecha fija; en un programa real sería date('now', '-90 days'), CURRENT_DATE - INTERVAL '90 days' o DATE_SUB(CURDATE(), INTERVAL 90 DAY).
-- Pedidos de los 90 días anteriores al 20 de octubre de 2026
-- (en un programa de verdad, date('now', '-90 days'))
SELECT id, fecha, estado
FROM pedidos
WHERE fecha >= date('2026-10-20', '-90 days')
ORDER BY fecha;
SELECT date('2026-10-20', '-90 days') AS desde,
date('2026-01-31', '+1 month') AS un_mes_despues;Ábrelo en el editor y cámbialo →
resultado · SQLiteid | fecha | estado ---+------------+---------- 17 | 2026-08-05 | entregado 18 | 2026-08-21 | entregado 19 | 2026-09-09 | enviado 20 | 2026-09-23 | enviado 21 | 2026-10-02 | pendiente 22 | 2026-10-12 | pendiente desde | un_mes_despues -----------+--------------- 2026-07-22 | 2026-03-03
Las tres bases devuelven los mismos seis pedidos. Pero fíjate en la última columna: sumar un mes al 31 de enero da 3 de marzo en SQLite. PostgreSQL y MySQL dan otra cosa:
SELECT id, fecha, estado
FROM pedidos
WHERE fecha >= DATE '2026-10-20' - INTERVAL '90 days'
ORDER BY fecha;
SELECT (DATE '2026-10-20' - INTERVAL '90 days')::date AS desde,
(DATE '2026-01-31' + INTERVAL '1 month')::date AS un_mes_despues;resultado · PostgreSQL id | fecha | estado ----+------------+----------- 17 | 2026-08-05 | entregado 18 | 2026-08-21 | entregado 19 | 2026-09-09 | enviado 20 | 2026-09-23 | enviado 21 | 2026-10-02 | pendiente 22 | 2026-10-12 | pendiente desde | un_mes_despues ------------+---------------- 2026-07-22 | 2026-02-28
SELECT id, fecha, estado
FROM pedidos
WHERE fecha >= DATE_SUB('2026-10-20', INTERVAL 90 DAY)
ORDER BY fecha;
SELECT DATE_SUB('2026-10-20', INTERVAL 90 DAY) AS desde,
DATE_ADD('2026-01-31', INTERVAL 1 MONTH) AS un_mes_despues;resultado · MySQL+----+------------+-----------+ | id | fecha | estado | +----+------------+-----------+ | 17 | 2026-08-05 | entregado | | 18 | 2026-08-21 | entregado | | 19 | 2026-09-09 | enviado | | 20 | 2026-09-23 | enviado | | 21 | 2026-10-02 | pendiente | | 22 | 2026-10-12 | pendiente | +----+------------+-----------+ +------------+----------------+ | desde | un_mes_despues | +------------+----------------+ | 2026-07-22 | 2026-02-28 | +------------+----------------+
SQLite suma uno al mes (31 de febrero) y luego normaliza: febrero de 2026 tiene 28 días, así que se pasa 3 días a marzo. PostgreSQL y MySQL se quedan en el último día de febrero. Ninguno está mal; son reglas distintas, y conviene saberlo si calculas vencimientos.
El día de la semana
El 10 de enero de 2026 fue sábado. Cada motor lo numera a su manera:
SELECT fecha, strftime('%w', fecha) AS dia_semana
FROM pedidos WHERE id IN (1, 2, 3);Ábrelo en el editor y cámbialo →
resultado · SQLitefecha | dia_semana -----------+----------- 2026-01-10 | 6 2026-01-15 | 4 2026-01-28 | 3
resultado · PostgreSQL fecha | dow | isodow ------------+-----+-------- 2026-01-10 | 6 | 6 2026-01-15 | 4 | 4 2026-01-28 | 3 | 3
resultado · MySQL+------------+-----------+---------+ | fecha | dayofweek | weekday | +------------+-----------+---------+ | 2026-01-10 | 7 | 5 | | 2026-01-15 | 5 | 3 | | 2026-01-28 | 4 | 2 | +------------+-----------+---------+
| Función | Domingo | Lunes | Sábado |
|---|---|---|---|
SQLite strftime('%w', …) (texto) | 0 | 1 | 6 |
PostgreSQL EXTRACT(DOW …) | 0 | 1 | 6 |
PostgreSQL EXTRACT(ISODOW …) | 7 | 1 | 6 |
MySQL DAYOFWEEK(…) | 1 | 2 | 7 |
MySQL WEEKDAY(…) | 6 | 0 | 5 |
En SQLite, una fecha es texto
SQLite guarda las fechas como texto y las compara como texto. Funciona porque el formato AAAA-MM-DD ordena igual alfabética que cronológicamente, pero solo si todas siguen ese formato:
-- En SQLite una fecha es texto: si alguien la guarda con otro formato, compara mal INSERT INTO pedidos (id, cliente_id, fecha, estado) VALUES (23, 3, '2026-9-30', 'pendiente'); INSERT INTO pedidos (id, cliente_id, fecha, estado) VALUES (24, 3, '2026-02-30', 'pendiente'); SELECT id, fecha FROM pedidos WHERE fecha >= '2026-09-01' AND fecha < '2026-10-01' ORDER BY fecha; SELECT id, fecha, typeof(fecha) AS tipo, date(fecha) AS como_fecha FROM pedidos WHERE id IN (22, 23, 24);
Ábrelo en el editor y cámbialo →
resultado · SQLiteid | fecha ---+----------- 19 | 2026-09-09 20 | 2026-09-23 id | fecha | tipo | como_fecha ---+------------+------+----------- 22 | 2026-10-12 | text | 2026-10-12 23 | 2026-9-30 | text | NULL 24 | 2026-02-30 | text | 2026-03-02
El pedido 23 ('2026-9-30', sin el cero) no sale en septiembre, y date() ni siquiera lo reconoce. El 30 de febrero se guarda sin quejas. En PostgreSQL y MySQL la columna es de tipo DATE: el primero se normaliza a 2026-09-30 y el segundo se rechaza:
resultado · PostgreSQLERROR: date/time field value out of range: "2026-02-30"
LINE 1: ...os (id, cliente_id, fecha, estado) VALUES (24, 3, '2026-02-3...
^
id | fecha
----+------------
23 | 2026-09-30resultado · MySQLERROR 1292 (22007) at line 2: Incorrect date value: '2026-02-30' for column 'fecha' at row 1 +----+------------+ | id | fecha | +----+------------+ | 23 | 2026-09-30 | +----+------------+
2026-09-30 o 2026-09-30 14:05:00) y, si quieres asegurarte, añade una restricción como CHECK (fecha = date(fecha)), que rechaza todo lo que no sea una fecha válida en ese formato.Todas las consultas de esta página se ejecutaron antes de publicarla sobre la base de ejemplo «tienda» del curso, en contenedores limpios: SQLite 3.46.1 (el de Python 3.13), PostgreSQL 17 y MySQL 8.4. Los resultados y los mensajes de error están copiados de esa ejecución. Documentación oficial: funciones de fecha de SQLite.