MySQL Query - Records between Today and Last 30 Days
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
Filtering MySQL rows for “today and the last 30 days” sounds simple, but the correct query depends on what your column stores and what the business rule actually means. The best solution usually compares the raw date column against calculated boundaries so the query stays both correct and index-friendly.
Decide Whether You Mean Dates or a Rolling Window
There are two common interpretations of this requirement:
- all rows from the current calendar date back through 30 calendar days
- all rows from the last 30 times 24 hours from the current moment
Those are not the same thing. If it is 4:00 PM right now, a rolling 30-day window excludes rows from 31 days ago at 9:00 AM, while a calendar-based report may still include that whole date if it falls inside the reporting range.
Defining the time window first prevents subtle bugs later.
Querying a DATE Column
If the column stores a DATE, there is no time component to worry about. In that case, BETWEEN is easy to read and usually correct.
This includes:
- today
- every full date back to 30 days ago
For pure date data, that is often all you need.
Querying a DATETIME or TIMESTAMP Column
If the column stores a timestamp, use a half-open range instead of BETWEEN. That avoids awkward end-of-day bugs and makes the intent much clearer.
This query means:
- start at midnight 30 days ago
- include every timestamp today
- stop just before midnight tomorrow
That is usually the cleanest SQL translation of “today and the last 30 days.”
If you truly want the last 30 times 24 hours from the current instant, use NOW() instead:
That is a different business rule, so avoid swapping CURDATE() and NOW() without thinking through the impact.
Keep the Query Index-Friendly
A common beginner solution is to wrap the column in DATE():
The logic is fine, but performance can suffer because MySQL may not be able to use a normal index on created_at efficiently once the column is wrapped in a function.
Prefer calculating the boundary values and comparing them directly to the raw column:
That pattern is usually easier for the optimizer to work with.
Parameterized Example from Application Code
If the number of days comes from the application, compute the range in code and pass it safely as parameters instead of building SQL strings manually.
This keeps the query safe, makes testing easier, and avoids accidental formatting problems.
Time Zones Matter
The phrase “today” only makes sense in a specific time zone. If the database stores UTC timestamps but the report is meant for a user in Toronto, then CURDATE() on the database server may not reflect the date boundary the user expects.
Pick one rule and stick to it:
- store and query everything in UTC
- or convert user-local day boundaries into UTC before running the query
Without that rule, reports tend to break near midnight or around daylight saving transitions.
Check the Execution Plan
When these queries run on large tables, verify that MySQL is actually using your index:
If the table is also filtered by another field such as status, a composite index may help:
The right index depends on the full query shape, not just on the date predicate.
Common Pitfalls
- Using
NOW()when the report really means full calendar dates including all of today. - Wrapping the indexed timestamp column in
DATE()and making the filter harder to optimize. - Using
BETWEENon a timestamp range without thinking about the end-of-day boundary. - Ignoring time zone rules when deciding what “today” should mean.
- Treating
DATE,DATETIME, andTIMESTAMPcolumns as if they should all use the exact same filter.
Summary
- Decide first whether you want calendar days or a rolling 30-day window.
- For
DATEcolumns,BETWEENwithCURDATE()is often correct and readable. - For
DATETIMEorTIMESTAMP, a half-open range is usually the safer pattern. - Compare raw columns against calculated bounds instead of calling functions on the column.
- Use
EXPLAINand appropriate indexes when the table is large.

