Reputation: 787
I have a table with a field called recordTime
of type timestamp without time zone
. I want to select today's rows only (i.e. since midnight). The records are sourced globally and arrive as UTC, so I need to select rows based on midnight UTC.
There seems to be a myriad of ways of doing this including ...
WHERE date_trunc('day', recordTime) = current_date ;
WHERE date_trunc('day', recordTime) = date_trunc('day', current_date at time zone 'utc')
WHERE date_trunc('day', recordTime) = date_trunc('day', current_timestamp at time zone 'utc')
WHERE recordTime >= '17-May-2024 00:00:00'
Which of these is best practice - or maybe a different method entirely? (Bearing in mind that performance seems similar in all cases.)
Upvotes: 2
Views: 258
Reputation: 658392
Requires Postgres 12 or later:
WHERE recordtime >= date_trunc('day', now(), 'UTC') AT TIME ZONE 'UTC'
AND recordtime < date_trunc('day', now(), 'UTC') AT TIME ZONE 'UTC' + interval '1 day';
The 2nd line is optional if there cannot be rows past "today" in the table. Would waste only very little performance in that case. But another filter can lead to different (inferior) query plan as Postgres (incorrectly) estimates fewer result rows.
The definition of "today" depends on the current time zone. Provide that explicitly to get immutable results.
date_trunc('day', now(), 'UTC')
gets the start of the current day in the time zone UTC. The result it type timestamptz
. Convert to the local timestamp
with the AT TIME ZONE
construct See:
now()
is Postgres short syntax for standard SQL current_timestamp
. See:
Include the starting 00:00, but exclude the ending 00:00.
Don't use BETWEEN
with timestamp types.
Make the expression sargable. I.e., compare the bare column to a (computed) constant, not the other way round. Better in any case, but essential to use an index on recordtime
- which you should have unless the table is trivially small.
If you are sure the timezone
setting of the current session is 'UTC'.
One way to make sure is to set it yourself in the same transaction:
BEGIN;
SET timezone = 'UTC';
...
WHERE recordtime >= current_date
AND recordtime < current_date + 1;
You can add integer
to a date
to add days. And date
is coerced to the column type timestamp [without time zone]
cleanly in the expression.
But if time zones can be involved in any way, it's typically better to work with timestamptz
columns to begin with. Then, to get rows for "today in the time zone UTC", use the convenient variant of date_trunc()
taking the time zone as 3rd parameter (since Postgres 12) like demontrated at the top.
Avoid mixed-case identifier in Postgres if at all possible. Else, you introduce uncertainty whether it's really recordtime
or "recordTime"
. See:
Upvotes: 3
Reputation: 165396
WHERE date_trunc('day', recordTime) = date_trunc('day', current_timestamp at time zone 'utc')
I don't know if this is best practice, but it has the advantage of being explicit about its conversions. It also avoids the gotcha of accidentally matching midnight the next day.
You can shorten it to:
WHERE recordTime::date = (current_timestamp at time zone 'utc')::date;
WHERE recordTime >= '17-May-2024 00:00:00'
Incorrect. This will match every recordTime after May 17th, including May 18th, May 19th, etc.
You'd instead write...
WHERE '17-May-2024'::date <= recordTime and recordTime < '18-May-2024'::date
This will match 17-May-2024 00:00 and will not match 18-May-2024 00:00.
You have to input the date manually, but that's a simple matter if you're running the query from a program.
Watch out using between
. For example, where recordTime between '17-May-2024'::date and '18-May-2024'::date
. This will match both dates. where recordTime between '17-May-2024'::date and '17-May-2024'::date
is correct, but odd. It's better to write it out long-hand.
current_date
is the date according to the current time zone, but it has no time zone information, so that's no good here. Also don't use synonyms like today
and tomorrow
.
test=# set timezone='Asia/Tokyo';
SET
test=# select current_date, 'today'::timestamp, 'tomorrow'::timestamp;
current_date | timestamp | timestamp
--------------+---------------------+---------------------
2024-05-18 | 2024-05-18 00:00:00 | 2024-05-19 00:00:00
(1 row)
test=# set timezone='America/Los_Angeles';
SET
test=# select current_date, 'today'::timestamp, 'tomorrow'::timestamp;
current_date | timestamp | timestamp
--------------+---------------------+---------------------
2024-05-17 | 2024-05-17 00:00:00 | 2024-05-18 00:00:00
(1 row)
Finally, the best thing for performance is to ensure recordTime
is indexed.
Upvotes: 0
Reputation: 127416
Today starts at midnight 00:00 and ends "tomorrow" also at midnight. Just use today and tomorrow:
WHERE recordTime BETWEEN 'today'::timestamp AND 'tomorrow'::timestamp
It's fairly straight forward and easy to read and understand. You can even do some additional interval calculations on these values, if needed.
Upvotes: 0