更快地搜索字段的第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].*');
此索引对任何其他查找都没有帮助,但它会预先计算此特定查询的答案。 对于从更大的表返回一个小的预定义子集的查询,此技术通常很有用,因为生成的索引将忽略表的绝大部分并且仅包含感兴趣的行。