Thread: PG won't use index on ORDER BY
Hi all! I have problems getting PG to use an index when sorting. I have a simple table create table person( id serial primary key, firstname varchar, lastname varchar ); I create an index: CREATE INDEX person_lowerfullname_idx ON person((lower(COALESCE(firstname, '')) || lower(COALESCE(lastname, ''))) varchar_pattern_ops); And this query refuses to use that index: select id from person order by (lower(COALESCE(firstname, '')) || lower(COALESCE(lastname, ''))) ASC limit 1; If I add an index: CREATE INDEX person_lowerfirstname_idx ON person(lower(firstname)); The following query will use that index for sorting and cut-off: select id from person order by (lower(firstname) ) ASC limit 1; Any hints or explaination on why the "concat-index" won't be used? PS: I have tried to issue a "set enable_seqscan to off;" to ensure that it will use an index if one appropriate exists -- Andreas Joseph Krogh <andreak@officenet.no> Senior Software Developer / Manager ------------------------+---------------------------------------------+ OfficeNet AS | The most difficult thing in the world is to | Karenslyst Allé 11 | know how to do a thing and to watch | PO. Box 529 Skøyen | somebody else doing it wrong, without | 0214 Oslo | comment. | NORWAY | | Tlf: +47 24 15 38 90 | | Fax: +47 24 15 38 91 | | Mobile: +47 909 56 963 | | ------------------------+---------------------------------------------+
"Andreas Joseph Krogh" <andreak@officenet.no> writes: > I create an index: > CREATE INDEX person_lowerfullname_idx ON > person((lower(COALESCE(firstname, '')) || lower(COALESCE(lastname, ''))) > varchar_pattern_ops); Why are you declaring it using the varchar_pattern_ops? The default operator set is the one you want for handling ordering. The pattern_ops operator set is for handling things like x LIKE 'foo%' -- Gregory Stark EnterpriseDB http://www.enterprisedb.com
On Thursday 09 August 2007 22:00:54 Gregory Stark wrote: > "Andreas Joseph Krogh" <andreak@officenet.no> writes: > > I create an index: > > CREATE INDEX person_lowerfullname_idx ON > > person((lower(COALESCE(firstname, '')) || lower(COALESCE(lastname, ''))) > > varchar_pattern_ops); > > Why are you declaring it using the varchar_pattern_ops? > > The default operator set is the one you want for handling ordering. The > pattern_ops operator set is for handling things like x LIKE 'foo%' Ooops, just fugured that out. But - it still doesn't use the index if I remove the "varchar_pattern_ops". I solved it by adding a function: CREATE OR REPLACE FUNCTION concat_lower(varchar, varchar) RETURNS varchar AS $$ SELECT lower(coalesce($1, '')) || lower(coalesce($2, '')) $$ LANGUAGE SQL IMMUTABLE; And than creating an index: CREATE INDEX person_lowerfullname_idx ON person(concat_lower(firstname, lastname)); Another question then: Why doesn't "varchar_pattern_ops" handle ordering? This means I need 2 indexes on the columns I want to match with LIKE and ORDER BY. Just doesn't seem right to need 2 "similar" indexes... -- Andreas Joseph Krogh <andreak@officenet.no> Senior Software Developer / Manager ------------------------+---------------------------------------------+ OfficeNet AS | The most difficult thing in the world is to | Karenslyst Allé 11 | know how to do a thing and to watch | PO. Box 529 Skøyen | somebody else doing it wrong, without | 0214 Oslo | comment. | NORWAY | | Tlf: +47 24 15 38 90 | | Fax: +47 24 15 38 91 | | Mobile: +47 909 56 963 | | ------------------------+---------------------------------------------+
On 8/9/07, Andreas Joseph Krogh <andreak@officenet.no> wrote: > Ooops, just fugured that out. But - it still doesn't use the index if I remove > the "varchar_pattern_ops". Huh? CREATE INDEX person_lowerfullname_idx ON person ((lower(COALESCE(firstname, '')) || lower(COALESCE(lastname, '')))); EXPLAIN ANALYZE select id from person order by (lower(COALESCE(firstname, '')) || lower(COALESCE(lastname, ''))) ASC limit 1; Limit (cost=0.00..0.08 rows=1 width=68) (actual time=0.030..0.030 rows=0 loops=1) -> Index Scan using person_lowerfullname_idx on person (cost=0.00..62.25 rows=800 width=68) (actual time=0.018..0.018 rows=0 loops=1) Total runtime: 0.318 ms EXPLAIN ANALYZE select id from person where (lower(COALESCE(firstname, '')) || lower(COALESCE(lastname, ''))) like 'A%' order by (lower(COALESCE(firstname, '')) || lower(COALESCE(lastname, ''))) ASC limit 1; Limit (cost=0.01..5.10 rows=1 width=68) (actual time=0.038..0.038 rows=0 loops=1) -> Index Scan using person_lowerfullname_idx on person (cost=0.01..20.34 rows=4 width=68) (actual time=0.032..0.032 rows=0 loops=1) Index Cond: (((lower((COALESCE(firstname, ''::character varying))::text) || lower((COALESCE(lastname, ''::character varying))::text)) >= 'A'::text) AND ((lower((COALESCE(firstname, ''::character varying))::text) || lower((COALESCE(lastname, ''::character varying))::text)) < 'B'::text)) Filter: ((lower((COALESCE(firstname, ''::character varying))::text) || lower((COALESCE(lastname, ''::character varying))::text)) ~~ 'A%'::text) Total runtime: 0.138 ms Works for me.
On Thursday 09 August 2007 22:57:35 Rodrigo De León wrote: > On 8/9/07, Andreas Joseph Krogh <andreak@officenet.no> wrote: > > Ooops, just fugured that out. But - it still doesn't use the index if I > > remove the "varchar_pattern_ops". > > Huh? > > CREATE INDEX person_lowerfullname_idx > ON person > ((lower(COALESCE(firstname, '')) || lower(COALESCE(lastname, '')))); > > > EXPLAIN ANALYZE select id from person order by > (lower(COALESCE(firstname, '')) || > lower(COALESCE(lastname, ''))) ASC limit 1; > > Limit (cost=0.00..0.08 rows=1 width=68) (actual time=0.030..0.030 > rows=0 loops=1) > -> Index Scan using person_lowerfullname_idx on person > (cost=0.00..62.25 rows=800 width=68) (actual time=0.018..0.018 rows=0 > loops=1) > Total runtime: 0.318 ms > > > EXPLAIN ANALYZE select id from person > where (lower(COALESCE(firstname, '')) || > lower(COALESCE(lastname, ''))) like 'A%' > order by (lower(COALESCE(firstname, '')) || lower(COALESCE(lastname, ''))) > ASC limit 1; > > Limit (cost=0.01..5.10 rows=1 width=68) (actual time=0.038..0.038 > rows=0 loops=1) > -> Index Scan using person_lowerfullname_idx on person > (cost=0.01..20.34 rows=4 width=68) (actual time=0.032..0.032 rows=0 > loops=1) > Index Cond: (((lower((COALESCE(firstname, ''::character > varying))::text) || lower((COALESCE(lastname, ''::character > varying))::text)) >= 'A'::text) AND ((lower((COALESCE(firstname, > ''::character varying))::text) || lower((COALESCE(lastname, > ''::character varying))::text)) < 'B'::text)) > Filter: ((lower((COALESCE(firstname, ''::character > varying))::text) || lower((COALESCE(lastname, ''::character > varying))::text)) ~~ 'A%'::text) > Total runtime: 0.138 ms > > > Works for me. I forgot to mention: I use UTF-8. IIRC, it works fine with C-locale without the "varchar_pattern_ops", which is why it works for you I think. -- Andreas Joseph Krogh <andreak@officenet.no> Senior Software Developer / Manager ------------------------+---------------------------------------------+ OfficeNet AS | The most difficult thing in the world is to | Karenslyst Allé 11 | know how to do a thing and to watch | PO. Box 529 Skøyen | somebody else doing it wrong, without | 0214 Oslo | comment. | NORWAY | | Tlf: +47 24 15 38 90 | | Fax: +47 24 15 38 91 | | Mobile: +47 909 56 963 | | ------------------------+---------------------------------------------+
On Thursday 09 August 2007 22:38:46 Andreas Joseph Krogh wrote: > On Thursday 09 August 2007 22:00:54 Gregory Stark wrote: > > "Andreas Joseph Krogh" <andreak@officenet.no> writes: > > > I create an index: > > > CREATE INDEX person_lowerfullname_idx ON > > > person((lower(COALESCE(firstname, '')) || lower(COALESCE(lastname, > > > ''))) varchar_pattern_ops); > > > > Why are you declaring it using the varchar_pattern_ops? > > > > The default operator set is the one you want for handling ordering. The > > pattern_ops operator set is for handling things like x LIKE 'foo%' > > Ooops, just fugured that out. But - it still doesn't use the index if I > remove the "varchar_pattern_ops". I solved it by adding a function: > > CREATE OR REPLACE FUNCTION concat_lower(varchar, varchar) RETURNS varchar > AS $$ > SELECT lower(coalesce($1, '')) || lower(coalesce($2, '')) > $$ LANGUAGE SQL IMMUTABLE; > > And than creating an index: > CREATE INDEX person_lowerfullname_idx ON person(concat_lower(firstname, > lastname)); > > Another question then: Why doesn't "varchar_pattern_ops" handle ordering? > This means I need 2 indexes on the columns I want to match with LIKE and > ORDER BY. Just doesn't seem right to need 2 "similar" indexes... Hmm, one more question: If I want to ORDER BY "created" too, the index is not used anymore: -- This uses index: EXPLAIN ANALYZE select firstname, lastname from person order by concat_lower(firstname, lastname) ASC limit 10; -- This doesn't EXPLAIN ANALYZE select firstname, lastname from person order by concat_lower(firstname, lastname) ASC, created DESC limit 10; I figured out that it's because they have different sort-order (ASC/DESC). If both are ASC, the index is used. Is there a way around this? -- Andreas Joseph Krogh <andreak@officenet.no> Senior Software Developer / Manager ------------------------+---------------------------------------------+ OfficeNet AS | The most difficult thing in the world is to | Karenslyst Allé 11 | know how to do a thing and to watch | PO. Box 529 Skøyen | somebody else doing it wrong, without | 0214 Oslo | comment. | NORWAY | | Tlf: +47 24 15 38 90 | | Fax: +47 24 15 38 91 | | Mobile: +47 909 56 963 | | ------------------------+---------------------------------------------+
Andreas Joseph Krogh <andreak@officenet.no> writes: >> Another question then: Why doesn't "varchar_pattern_ops" handle ordering? It does handle ordering, just not the ordering you're asking for here. If you substituted USING ~<~ for ASC you'd find that the pattern_ops index could be used for that. >> This means I need 2 indexes on the columns I want to match with LIKE and >> ORDER BY. Just doesn't seem right to need 2 "similar" indexes... If you want to use the same index for both, you have to run the database in C locale. Non-C locales generally define a sort ordering that is not compatible with LIKE searches. (The point of the pattern_ops opclass is really to force C-locale ordering of the index when the ordinary text comparison operators yield a different ordering.) > -- This doesn't > EXPLAIN ANALYZE select firstname, lastname from person order by > concat_lower(firstname, lastname) ASC, created DESC limit 10; This ORDER BY is asking for an ordering that is almost completely unrelated to the index's ordering. regards, tom lane
Andreas Joseph Krogh <andreak@officenet.no> writes: > I forgot to mention: I use UTF-8. IIRC, it works fine with C-locale without > the "varchar_pattern_ops", which is why it works for you I think. That shouldn't make any difference, and doesn't for me in testing here: regression=# select version(); version -----------------------------------------------------------------------------------------------------------PostgreSQL 8.2.4on x86_64-unknown-linux-gnu, compiled by GCC gcc (GCC) 4.1.2 20070626 (Red Hat 4.1.2-13) (1 row) regression=# show lc_collate;lc_collate ------------en_US.utf8 (1 row) regression=# show server_encoding ;server_encoding -----------------UTF8 (1 row) regression=# \d person Table "public.person" Column | Type | Modifiers -----------+-------------------+-----------------------------------------------------id | integer | notnull default nextval('person_id_seq'::regclass)firstname | character varying | lastname | character varying | Indexes: "person_pkey" PRIMARY KEY, btree (id) "person_lowerfullname_idx" btree ((lower(COALESCE(firstname, ''::charactervarying)::text) || lower(COALESCE(lastname, ''::character varying)::text))) regression=# explain select id from person order by (lower(COALESCE(firstname, '')) || lower(COALESCE(lastname, ''))) ASClimit 1; QUERY PLAN -------------------------------------------------------------------------------------------------Limit (cost=0.00..0.08rows=1 width=68) -> Index Scan using person_lowerfullname_idx on person (cost=0.00..62.25 rows=800 width=68) (2 rows) So there's something going on that you haven't told us about your installation. regards, tom lane
"Andreas Joseph Krogh" <andreak@officenet.no> writes: > Ooops, just fugured that out. But - it still doesn't use the index if I remove > the "varchar_pattern_ops". I solved it by adding a function: Hm, well it does for me, you would have to post your explain analyze to see what's going on. > Another question then: Why doesn't "varchar_pattern_ops" handle ordering? This > means I need 2 indexes on the columns I want to match with LIKE and ORDER BY. > Just doesn't seem right to need 2 "similar" indexes... If you initd in the C locale you only need one index. In other locales the collation order and the pattern order are different. -- Gregory Stark EnterpriseDB http://www.enterprisedb.com