HiveBrain v1.2.0
Get Started
← Back to all entries
snippetsqlMinor

How to make this query use my multicolumn index?

Submitted by: @import:stackexchange-dba··
0
Viewed 0 times
thisquerymakemulticolumnhowindexuse

Problem

Currently, I have a view which is defined like this:

View "public.customer_list"
  Column   |          Type           | Modifiers | Storage  | Description 
-----------+-------------------------+-----------+----------+-------------
 id        | bigint                  |           | plain    | 
 name      | character varying(100)  |           | extended | 
 street    | character varying(100)  |           | extended | 
 zip       | character varying(10)   |           | extended | 
 city      | character varying(100)  |           | extended | 
 country   | character varying(3)    |           | extended | 
 phone     | character varying(100)  |           | extended | 
 mail      | character varying(100)  |           | extended | 
 rating    | integer                 |           | plain    | 
 salesnote | character varying(1800) |           | extended | 
View definition:
 SELECT c.id,
    c.name,
    a.street,
    a.zip,
    a.city,
    a.country,
    c.phone,
    c.mail,
    c.rating,
    c.salesnote
   FROM crm_customer c
     JOIN crm_address a ON a.id = c.address_id;


To better search through the List I created an index with the following:

CREATE INDEX crm_customer_big_index 
    ON crm_customer (name ASC, 
                     name text_pattern_ops, 
                     (id::text) text_pattern_ops, 
                     phone text_pattern_ops, 
                     mail text_pattern_ops, 
                     rating);


I search through this table with a varying where query mostly it looks like that:

SELECT * 
  FROM customer_list
 WHERE lower(name::text) LIKE 'env%' 
       AND rating = 3 
 ORDER BY name ASC 
 LIMIT 20 
 OFFSET 0;


But still my analyzer won't ever use an index. Is there a way to use one?

```
QUERY PLAN
----------------------------------------------------------------------------------------------------------------------------------------------------------------
Limit (cost=164.04..164.08 rows=15 width=129) (actual time=3

Solution

The expression lower(name::text) is not covered by your index, which also does not seem to be useful at all, at least for what we see in your question:


CREATE INDEX crm_customer_big_index 
    ON crm_customer (name ASC, 
                     name text_pattern_ops, 
                     (id::text) text_pattern_ops, 
                     phone text_pattern_ops, 
                     mail text_pattern_ops, 
                     rating);


This index would cover your query:

CREATE INDEX crm_customer_foo_index ON crm_customer (
  rating
, lower(name::text) text_pattern_ops
, address_id);  -- for the join


To optimize, I suggest to adapt your query additionally:

SELECT * 
FROM   customer_list
WHERE  lower(name::text) LIKE 'env%' 
AND    rating = 3 
ORDER  BY lower(name::text), name
LIMIT  20;


Also, if you are only interested in the first few characters of name character varying(100), make the index shorter and faster by just indexing those first few characters:

CREATE INDEX crm_customer_foo_index ON crm_customer (
     rating
   , lower(left(name::text, 7)) text_pattern_ops  -- example with local optimum
   , address_id);  -- for the join


The query must match the index expression:

WHERE  lower(left(name::text, 7)) LIKE 'env%' 
...
ORDER  BY lower(left(name::text, 7)), name


Not sure if the view is in the way somehow. But it should be transparent.

I wouldn't use varchar(n) at all. Just text:

  • Should I add an arbitrary length limit to VARCHAR columns?



About pattern matching and text_pattern_ops:

  • Pattern matching with LIKE, SIMILAR TO or regular expressions



Why did I put rating first in the index?

  • Multicolumn index and performance



There are query techniques to better capitalize on non-leading index columns. Consider the Postgres Wiki on "Loose indexscan".

Code Snippets

CREATE INDEX crm_customer_big_index 
    ON crm_customer (name ASC, 
                     name text_pattern_ops, 
                     (id::text) text_pattern_ops, 
                     phone text_pattern_ops, 
                     mail text_pattern_ops, 
                     rating);
CREATE INDEX crm_customer_foo_index ON crm_customer (
  rating
, lower(name::text) text_pattern_ops
, address_id);  -- for the join
SELECT * 
FROM   customer_list
WHERE  lower(name::text) LIKE 'env%' 
AND    rating = 3 
ORDER  BY lower(name::text), name
LIMIT  20;
CREATE INDEX crm_customer_foo_index ON crm_customer (
     rating
   , lower(left(name::text, 7)) text_pattern_ops  -- example with local optimum
   , address_id);  -- for the join
WHERE  lower(left(name::text, 7)) LIKE 'env%' 
...
ORDER  BY lower(left(name::text, 7)), name

Context

StackExchange Database Administrators Q#116705, answer score: 6

Revisions (0)

No revisions yet.