Deberá crear una tabla de ayuda y completarla con todas las fechas desde start para end , luego simplemente LEFT JOIN con esa mesa:
SELECT d.dt AS date,
COUNT(*) AS total,
SUM(attitude = 'positive') AS positive,
SUM(attitude = 'neutral') AS neutral,
SUM(attitude = 'negative') AS negative
FROM dates d
LEFT JOIN
messages m
ON m.posted_at >= d.dt
AND m.posted_at < d.dt + INTERVAL 1 DAYS
AND spam = 0
AND duplicate = 0
AND ignore = 0
GROUP BY
d.dt
ORDER BY
d.dt
Básicamente, lo que necesita aquí es un origen de fila ficticio.
MySQL es el único sistema principal que carece de una forma de generarlo.
PostgreSQL implementa una función especial generate_series para hacer eso, mientras que Oracle y SQL Server puede usar recursividad (CONNECT BY y recursivo CTE s, en consecuencia).