更快地搜索字段的第1个字符与不匹配的记录?

我目前有以下内容:

User (id, fname, lname, deleted_at, guest) 

我可以通过fname初始查询用户列表,如下所示:

 User Load (9.6ms) SELECT "users".* FROM "users" WHERE (users.deleted_at IS NULL) AND (lower(left(fname, 1)) = 's') ORDER BY fname ASC LIMIT 25 OFFSET 0 

由于以下索引,这很快:

  CREATE INDEX users_multi_idx ON users (lower(left(fname, 1)), fname) WHERE deleted_at IS NULL; 

我现在要做的是能够查询所有不以字母AZ开头的用户。 我这样做是这样的:

 SELECT "users".* FROM "users" WHERE (users.deleted_at IS NULL) AND (lower(left(fname, 1)) ~ E'^[^a-zA-Z].*') ORDER BY fname ASC LIMIT 25 OFFSET 0 

但问题是这个查询非常慢并且似乎没有使用索引来加速第一个查询。 关于如何优雅地使第二个查询(非az)更快的任何建议?

我正在使用带有rails 3.2的Postgres 9.1

谢谢

更新的答案
前面的问题在这里。

我的第一个想法(带有text_pattern_ops索引)与我的测试中的正则表达式不兼容。 更好地将您的查询重写为:

 SELECT * FROM users WHERE deleted_at IS NULL WHERE lower(left(fname, 1)) < 'a' COLLATE "C" OR lower(left(fname, 1)) > 'z' COLLATE "C" ORDER BY fname LIMIT 25 OFFSET 0; 

除了这些表达式通常更快之外,你的正则表达式中也有大写字母,它与index lower()的索引不匹配。 与单个字符相比,尾随字符毫无意义。

并使用此索引:

 CREATE INDEX users_multi_idx ON users (lower(left(fname, 1)) COLLATE "C" , fname) WHERE deleted_at IS NULL; 

COLLATE "C"部分是可选的,只会带来非常小的性能提升。 它的目的是将排序规则重置为默认的posix排序规则,它只使用字节顺序,通常更快。 有用,无论如何整理规则都不相关。

如果使用它创建索引,则只有与排序规则匹配的查询才能使用它。 因此,如果性能不是您的首要要求,您可以跳过它来简化操作。

作为@ ErwinBrandstetter的通用解决方案的替代方案,PostgreSQL支持部分索引 。 你可以说:

 CREATE INDEX users_nonalphanumeric_not_deleted_key ON users (id) WHERE (users.deleted_at IS NULL) AND (lower(left(fname, 1)) ~ E'^[^a-zA-Z].*'); 

此索引对任何其他查找都没有帮助,但它会预先计算此特定查询的答案。 对于从更大的表返回一个小的预定义子集的查询,此技术通常很有用,因为生成的索引将忽略表的绝大部分并且仅包含感兴趣的行。