El primer día del mes siempre es el 1; el último depende del mes y del año. Para eso MySQL tiene LAST_DAY():
SELECT LAST_DAY('2026-02-10'); -- 2026-02-28
SELECT LAST_DAY(CURDATE()); -- último día del mes actual
Acepta fechas y fechas con hora, y devuelve siempre una fecha. Si le pasas algo que no es una fecha válida, devuelve NULL.
Lo que sale de ahí
-- Días que tiene el mes
SELECT DAY(LAST_DAY('2026-02-01')); -- 28
-- ¿Es bisiesto?
SELECT DAY(LAST_DAY('2028-02-01')) = 29; -- 1
-- Último día del mes pasado
SELECT LAST_DAY(CURDATE() - INTERVAL 1 MONTH);
El número de días del mes es lo que más uso: para repartir un importe mensual por días, para saber cuántas columnas tiene un cuadrante o para validar que el día 31 que llega en un formulario existe.
El mes pasado completo
El informe mensual que se lanza cualquier día del mes siguiente:
SELECT * FROM pedidos
WHERE fecha >= DATE_FORMAT(CURDATE() - INTERVAL 1 MONTH, '%Y-%m-01')
AND fecha < DATE_FORMAT(CURDATE(), '%Y-%m-01');
Desde el día 1 del mes pasado hasta antes del día 1 de este. Aquí no aparece LAST_DAY, aunque sería lo primero que se te ocurre, y es a propósito: BETWEEN … AND LAST_DAY(…) funciona si la columna es DATE, pero si es DATETIME se deja fuera casi todo el último día del mes. Me pasó, y lo cuento en DATE o DATETIME en MySQL: el error del BETWEEN que se come el último día.