Reputation:
To create my table and index I use the following code:
CREATE TABLE IF NOT EXISTS users (
id SERIAL NOT NULL,
name VARCHAR(512) NOT NULL,
PRIMARY KEY (id));
CREATE INDEX users_name_idx ON users (lower(name::varchar(16)));
My question - is users_name_idx
index used in the following queries:
SELECT * FROM users WHERE LOWER(name) LIKE 'somename%'
?SELECT * FROM users ORDER BY name
?Upvotes: 3
Views: 998
Reputation: 247445
Your index can be used by none of your queries, because the expression is not the same as in the queries:
test=> \d users
Table "laurenz.users"
Column | Type | Nullable | Default
--------+------------------------+----------+-----------------------------------
id | integer | not null | nextval('users_id_seq'::regclass)
name | character varying(512) | not null |
Indexes:
"users_pkey" PRIMARY KEY, btree (id)
"users_name_idx" btree (lower(name::character varying(16)::text))
test=> SET enable_seqscan = off;
test=> EXPLAIN SELECT * FROM users WHERE LOWER(name) LIKE 'somename%';
QUERY PLAN
---------------------------------------------------------------------------
Seq Scan on users (cost=10000000000.00..10000000012.10 rows=1 width=520)
Filter: (lower((name)::text) ~~ 'somename%'::text)
(2 rows)
test=> EXPLAIN SELECT * FROM users ORDER BY name;
QUERY PLAN
-----------------------------------------------------------------------------------
Sort (cost=10000000016.39..10000000016.74 rows=140 width=520)
Sort Key: name
-> Seq Scan on users (cost=10000000000.00..10000000011.40 rows=140 width=520)
(3 rows)
For the index to be used, you would have to use the same expression, including the type cast, in your queries.
In addition to that, unless your column has collation C
, your index cannot be userd in LIKE
queries. You'd gave to use the text_pattern_ops
operator class.
I guess that the reason behind creating such an index is to reduce the size of the index, which is a commendable thing to do.
I would recommend an index like this:
CREATE INDEX ON users (lower(name::varchar(16)) text_pattern_ops);
Then use this query:
SELECT * FROM users
WHERE lower(name) LIKE 'somename%'
AND lower(name::varchar(16)) LIKE substr('somename%', 1, 16);
The second condition could be lossy if somename
is longer than 15 characters, but it can use the index. The first condition filters out the false positives.
Unfortunately, there is no trick like this when it comes to ordering.
Upvotes: 2