Domanda

table (simplified)

                                        Table "public.events"
      Column      |            Type             |                       Modifiers                        
------------------+-----------------------------+--------------------------------------------------------
 id               | integer                     | not null default nextval('events_id_seq'::regclass)
 duration         | integer                     | not null
 start_at         | timestamp without time zone | 
Indexes:
    "events_pkey" PRIMARY KEY, btree (id)
    "my_idx" gist (tsrange(start_at, end_at(events.*), '[)'::text))

function

CREATE FUNCTION end_at(rec events)
  RETURNS timestamp without time zone
  IMMUTABLE
  LANGUAGE SQL
AS $$
  SELECT $1.start_at + ($1.duration * ('00:00:01'::interval));
$$;

what I am already doing successfully

The index is used for queries like this:

-- check if current time is within the start and end times
-- of event
where localtimestamp <@ tsrange(start_at, events.end_at, '[)')

And it works well.

what I want to do

I want to query for events where the current time is after they have ended. Ways I know of how to do this:

  • where tsrange(localtimestamp, localtimestamp, '[]') >> tsrange(start_at, events.end_at, '[)'). I'm pretty sure this is the semantics I want, and explain analyze says it's using the index, but it's a bit ugly and I'm wondering if there's a better way to express this (and also am vaguely uncertain it's the semantics I want, as I am new to ranges).
  • where localtimestamp > upper(tsrange(start_at, events.end_at, '[)')) + a btree index on upper(tsrange(start_at, events.end_at, '[)')). This will work well, but requires keeping another index around.
  • where localtimestamp > events.end_at. + a btree index on events.end_at. Same situation as above.

Is there a more elegant (or correct) way to achieve the first bullet point above?

Any other ideas for how to go about this?

È stato utile?

Soluzione

Postgres doesn't support any operators for this. As pointed out by @evan-carroll, it probably could and should.

So, the best solution is

where tsrange(localtimestamp, localtimestamp, '[]') >> tsrange(start_at, events.end_at, '[)')

Altri suggerimenti

I want to query for events where the current time is after they have ended. Ways I know of how to do this:

You can see the GiST operators here. You can see the list in range_ops,

&& &> &< >> << <@ -|- = @> @>

As you indicated the one you probably want is <<, >>. I would use <<.

Is it ugly? Yes. The range type is great if you're working with ranges on both sides, here you're not. You're not even storing the range type on the table. I would recommend you do that.

I'm not sure this index condition even works,

"my_idx" gist (tsrange(start_at, end_at(events.*), '[)'::text))

Have you tested that? I mean, PostgreSQL can verify the start_at works, my assumption is that it's not smart enough to work with end_at with events.* (what if one of the values (columns) used in the end_at is changed from table creation to call? Even if never happens, how would PostgreSQL know that). I would store the tsrange on the table and drop all those associated columns.

Ignore the below, looking into it.

Also you can consider making it shorter by not constructing the rhs range.

SELECT tsrange('yesterday', 'tomorrow') @> timestamp without time zone 'now';
SELECT tsrange('yesterday', 'tomorrow') @> 'now'::timestamp without time zone;

Or the like if you prefer that format. Or the following, which seems like an anti-pattern.

SELECT tsrange('yesterday', 'tomorrow') @> '[now,now]';

Or you can use tstzrange (which is probably an all around better idea),

SELECT tstzrange('yesterday', 'tomorrow') @> now();

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a dba.stackexchange
scroll top