This question deals with PostgreSQL 9.5 query performance.
The table is:
CREATE TABLE big_table
(
id integer NOT NULL,
flag bigint NOT NULL,
time timestamp with timezone NOT NULL,
val int,
primary key (id)
)
Consider the query:
SELECT *
FROM big_table
WHERE time > '0666-06-06 00:00:00+00' AND
flag & 2 = 2 AND
(val = 5 or val IS NULL)
ORDER by time desc
The goal is to make this query performance as good as possible while not using a union. I achieved the best performance using the index:
CREATE INDEX ind
ON big_table USING btree (time DESC, val)
WHERE (flag & 2::smallint = 2::smallint);
which makes the query use the index twice and use bitmap or between them. This is still much worse than if I could do val = ANY('{NULL,5}')
which is not possible.
This makes me consider to put some special value such as -1 instead of NULL which will allow me to do val = ANY('{-1,5}')
to run a full index scan with better performance.
So is using some special value instead of NULL better in such situations? Or does PostgreSQL has some optimization for situation where you want to do an "OR" between a non-NULL value and a NULL?
I read this great article but didn't see there a solution for what I just asked without using a union: https://www.cybertec-postgresql.com/en/avoid-or-for-better-performance/
I also searched for such question on Stack Overflow and here but the related questions either don't talk about performance or don't talk about non-NULL together with NULL. This similar question (and the answer it received) spoke about SQL Server and not PostgreSQL: Best way to write SQL Query that checks a column for non-NULL value or NULL
I also considered doing coalesce to get rid of null but have read that it makes the performance much worse.
The table has more than 4 million records. From them only 56 records have NULL in val column.
UPDATE: Original explain:
"Limit (cost=1112.32..1112.32 rows=2 width=519)"
" -> Sort (cost=1112.32..1112.32 rows=2 width=519)"
" Sort Key: time DESC"
" -> Bitmap Heap Scan on big_table (cost=1104.29..1112.31 rows=2 width=519)"
" Recheck Cond: (((time > '0666-06-06 00:00:00+00'::timestamp with time zone) AND (val = 3) AND ((flag & '2'::smallint) = '2'::smallint)) OR ((time > '0666-06-06 00:00:00+00'::timestamp with time zone) AND (val IS NULL) AND ((flag & '2'::smallint) = '2'::smallint)))"
" -> BitmapOr (cost=1104.29..1104.29 rows=2 width=0)"
" -> Bitmap Index Scan on ind (cost=0.00..552.14 rows=1 width=0)"
" Index Cond: ((time > '0666-06-06 00:00:00+00'::timestamp with time zone) AND (val = 3))"
" -> Bitmap Index Scan on ind (cost=0.00..552.14 rows=1 width=0)"
" Index Cond: ((time > '0666-06-06 00:00:00+00'::timestamp with time zone) AND (val IS NULL))"
Explain after changing (val = 3 or val IS NULL)
to coalesce(val,-1) = ANY('{3,-1}')
and creating the index:
CREATE INDEX ind2 ON big_table USING btree (coalesce(val,-1), time DESC) WHERE (flag & 2::smallint = 2::smallint);
is:
"Limit (cost=863.45..863.50 rows=20 width=519)"
" -> Sort (cost=863.45..863.99 rows=216 width=519)"
" Sort Key: time DESC"
" -> Bitmap Heap Scan on big_table (cost=11.08..857.71 rows=216 width=519)"
" Recheck Cond: ((COALESCE(val, '-1'::integer) = ANY ('{3,-1}'::integer[])) AND (time > '0666-06-06 00:00:00+00'::timestamp with time zone) AND ((flag & '2'::smallint) = '2'::smallint))"
" -> Bitmap Index Scan on ind2 (cost=0.00..11.03 rows=216 width=0)"
" Index Cond: ((COALESCE(val, '-1'::integer) = ANY ('{3,-1}'::integer[])) AND (time > '0666-06-06 00:00:00+00'::timestamp with time zone))"
Running original query but changing the order of time and val columns in the original index gives the following much better explain plan:
"Limit (cost=16.92..16.92 rows=2 width=519)"
" -> Sort (cost=16.92..16.92 rows=2 width=519)"
" Sort Key: time DESC"
" -> Bitmap Heap Scan on big_table (cost=8.89..16.91 rows=2 width=519)"
" Recheck Cond: (((val = 3) AND (time > '0666-06-06 00:00:00+00'::timestamp with time zone) AND ((flag & '2'::smallint) = '2'::smallint)) OR ((val IS NULL) AND (time > '0666-06-06 00:00:00+00'::timestamp with time zone) AND ((flag & '2'::smallint) = '2'::smallint)))"
" -> BitmapOr (cost=8.89..8.89 rows=2 width=0)"
" -> Bitmap Index Scan on ind (cost=0.00..4.44 rows=1 width=0)"
" Index Cond: ((val = 3) AND (time > '0666-06-06 00:00:00+00'::timestamp with time zone))"
" -> Bitmap Index Scan on ind (cost=0.00..4.44 rows=1 width=0)"
" Index Cond: ((val IS NULL) AND (time > '0666-06-06 00:00:00+00'::timestamp with time zone))"
And actually now doing the coalesce query actually slows down the query (even with columns reversed but replaced with coalesce in the index)!
val
withcoalesce(val,-1)
in your index.SELECT * FROM a JOIN b USING (id) WHERE a.id = 42 OR b.id = 42;
can be replaced withSELECT * FROM a JOIN b USING (id) WHERE id = 42;
- voiding the last part of it.