With PostgreSQL v15.2, I'm using the following table definition:
CREATE TABLE IF NOT EXISTS postgres_air_bitemp.frequent_flyer_transaction(
frequent_flyer_transaction_key integer NOT NULL DEFAULT
nextval('postgres_air_bitemp.frequent_flyer_transaction_frequent_flyer_transaction_key_seq'
::regclass),
frequent_flyer_id integer NOT NULL,
level text ,
booking_leg_id integer,
award_points integer,
status_points integer,
effective temporal_relationships.timeperiod NOT NULL,
asserted temporal_relationships.timeperiod NOT NULL,
row_created_at timestamp with time zone NOT NULL DEFAULT now(),
CONSTRAINT frequent_flyer_transaction_pk PRIMARY KEY (frequent_flyer_transaction_key),
CONSTRAINT frequent_flyer_transaction_frequent_flyer_id_assert_eff_excl EXCLUDE USING gist (
effective WITH &&,
asserted WITH &&,
frequent_flyer_id WITH =)
)
I'm seeing the following query plan for this query:
airlines=# explain analyze select * from
postgres_air_bitemp.frequent_flyer_transaction t
where frequent_flyer_id=39189 and now()<@asserted and now()<@effective;
QUERY PLAN
# ----------------------------------------------------------------------------------------------------------------------------------------------------------------------
Bitmap Heap Scan on frequent_flyer_transaction t (cost=4.69..87.91 rows=21 width=74) (actual time=0.097..0.099 rows=1 loops=1)
Recheck Cond: (frequent_flyer_id = 39189)
Filter: ((now() <@ (asserted)::tstzrange) AND (now() <@ (effective)::tstzrange))
Heap Blocks: exact=1
-> Bitmap Index Scan on frequent_flyer_transaction_frequent_flyer_id_assert_eff_excl (cost=0.00..4.68 rows=21 width=0) (actual time=0.077..0.077 rows=1 loops=1)
Index Cond: ((frequent_flyer_id = 39189) AND ((asserted)::tstzrange @> now()) AND ((effective)::tstzrange @> now()))
Planning Time: 0.333 ms
Execution Time: 0.172 ms
(8 rows)
The query plan is correctly using the btree_gist index, but then it does a bitmap index scan on the frequent_flyer_id filtered by the bitemporal time ranges, and finally does a recheck on the frequent_flyer_id condition. Why are the subsequent steps necessary after the initial btree_gist index scan?
I was expecting to see a query plan that consisted solely of an index scan using the btree_gist index. Are the extra steps necessary because gist indexes are lossy, and therefore the subsequent steps are to check for false positives?
frequent_flyer_idfirst, does it make a difference?(frequent_flyer_id WITH =, effective WITH &&, asserted WITH &&), that made no difference in the generated query plan. Isn't it the case that the bitmap index scan on the frequent_flyer_id, followed by the filter on bitemporal time ranges, followed by recheck condition on frequent_flyer_id are required to check for false positives because gist indexes are lossy?