user402516
user402516

Reputation: 528

Query on a time range ignoring the date of timestamps

I'm trying to query a purchases table in my rails database (Postgres) and I want to query on time ranges.

For example, I'd like to know how many purchases were made between 2 PM and 3 PM across all dates.

There is a created_at column in this table but I don't see how to accomplish this without searching for a specific date as well.

I've tried:

Purchases.where("created_at BETWEEN ? and ?", Time.now - 1.hour, Time.now)

But this ultimately will just search for today's date with those times.

Upvotes: 5

Views: 4493

Answers (2)

Erwin Brandstetter
Erwin Brandstetter

Reputation: 656331

Use a simple cast to time:

Purchases.where("created_at::time >= ?
             AND created_at::time <  ?", Time.now - 1.hour, Time.now)

This way you can easily test against arbitrary times.

Consider:

  • Which border to include / exclude. Common practice is to include the lower border and exclude the upper. BETWEEN includes both.

  • The exact data type (timestamp or timestamp with time zone) and how that interacts with your local time zone:

  • Ignoring time zones for Postgres timestamps

Index

If you run these queries a lot and need them fast and your table isn't very small, you will want to create a functional index for performance:

CREATE INDEX tbl_created_at_time_idx ON tbl (cast(created_at AS time));

I used the standard SQL syntax cast() instead of the Postgres syntactical shortcut ::. This is required for indices.

This index works for a timestamp [without time zone] since the cast is IMMUTABLE as required for an index. But it does not work for a timestamp with time zone, for the same reasons. You can fix this, but you need to define what you need exactly. Use the AT TIME ZONE construct to define the time zone to extract the time for. For instance, to measure against UTC time:

CREATE INDEX tbl_created_at_time_idx2
ON tbl(cast(created_at AT TIME ZONE 'UTC' AS time));

Similar question dealing with dates ignoring the year:

Upvotes: 4

Philip Hallstrom
Philip Hallstrom

Reputation: 19879

You need to extract just the hour portion from created_at using PostgreSQL's date_part/extract function.

SELECT EXTRACT(HOUR FROM TIMESTAMP '2001-02-16 20:38:40');
Result: 20

For example, something like this:

Purchases.where(["EXTRACT(HOUR FROM created_at) BETWEEN ? AND ?", 13, 14])

Upvotes: 6

Related Questions