继this answer之后我想知道使用PostgreSQL的内置全文搜索的最佳方法是,如果我想按等级排序,和限制只匹配查询。
让我们假设一个非常简单的表格。
CREATE TABLE pictures (
id SERIAL PRIMARY KEY,
title varchar(300),
...
)
或其他什么。现在我想搜索title
字段。首先,我创建一个索引:
CREATE INDEX pictures_title ON pictures
USING gin(to_tsvector('english', title));
现在我要搜索'small dog'
。这有效:
SELECT pictures.id,
ts_rank_cd(
to_tsvector('english', pictures.title), 'small dog'
) AS score
FROM pictures
ORDER BY score DESC
但我真正想要的是:
SELECT pictures.id,
ts_rank_cd(
to_tsvector('english', pictures.title), to_tsquery('small dog')
) AS score
FROM pictures
WHERE to_tsvector('english', pictures.title) @@ to_tsquery('small dog')
ORDER BY score DESC
或者这个(不起作用 - 不能在score
子句中使用WHERE
:
SELECT pictures.id,
ts_rank_cd(
to_tsvector('english', pictures.title), to_tsquery('small dog')
) AS score
FROM pictures WHERE score > 0
ORDER BY score DESC
最好的方法是什么?我的问题很多:
to_tsvector(...)
的版本,它会调用两次,还是足够智能以某种方式缓存结果?to_ts...
函数调用?score
条款中使用WHERE
?score > 0
过滤或使用@@
更好吗?答案 0 :(得分:9)
已接受的答案完全是错误的,因此我不得不鸣叫:
使用@@
运算符将使用全文本GIN索引,而对score > 0
的测试则不会。
我在问题中创建了一个表,但添加了名为title_tsv
的列:
CREATE TABLE test_pictures (
id BIGSERIAL,
title text,
title_tsv tsvector
);
CREATE INDEX ix_pictures_title_tsv ON test_pictures
USING gin(title_tsv);
我在表中填充了一些测试数据:
INSERT INTO test_pictures(title, title_tsv)
SELECT T.data, to_tsvector(T.data)
FROM some_table T;
然后我用explain analyze
运行了“接受的”解决方案:
EXPLAIN ANALYZE
SELECT score, id, title
FROM (
SELECT ts_rank_cd(P.title_tsv, to_tsquery('address & shipping')) AS score
,P.id
,P.title
FROM test_pictures as P
) S
WHERE score > 0
ORDER BY score DESC;
得到以下内容。请注意执行时间为5,015毫秒
QUERY PLAN |
----------------------------------------------------------------------------------------------------------------------------------------------|
Gather Merge (cost=274895.48..323298.03 rows=414850 width=60) (actual time=5010.844..5011.330 rows=1477 loops=1) |
Workers Planned: 2 |
Workers Launched: 2 |
-> Sort (cost=273895.46..274414.02 rows=207425 width=60) (actual time=4994.539..4994.555 rows=492 loops=3) |
Sort Key: (ts_rank_cd(p.title_tsv, to_tsquery('address & shipping'::text))) DESC |
Sort Method: quicksort Memory: 131kB |
-> Parallel Seq Scan on test_pictures p (cost=0.00..247776.02 rows=207425 width=60) (actual time=17.672..4993.997 rows=492 loops=3) |
Filter: (ts_rank_cd(title_tsv, to_tsquery('address & shipping'::text)) > '0'::double precision) |
Rows Removed by Filter: 497296 |
Planning time: 0.159 ms |
Execution time: 5015.664 ms |
现在将其与@@
运算符进行比较:
EXPLAIN ANALYZE
SELECT ts_rank_cd(to_tsvector(P.title), to_tsquery('address & shipping')) AS score
,P.id
,P.title
FROM test_pictures as P
WHERE P.title_tsv @@ to_tsquery('address & shipping')
ORDER BY score DESC;
执行结果大约需要 29毫秒:
QUERY PLAN |
-------------------------------------------------------------------------------------------------------------------------------------------------|
Gather Merge (cost=13884.42..14288.35 rows=3462 width=60) (actual time=26.472..26.942 rows=1477 loops=1) |
Workers Planned: 2 |
Workers Launched: 2 |
-> Sort (cost=12884.40..12888.73 rows=1731 width=60) (actual time=17.507..17.524 rows=492 loops=3) |
Sort Key: (ts_rank_cd(to_tsvector(title), to_tsquery('address & shipping'::text))) DESC |
Sort Method: quicksort Memory: 171kB |
-> Parallel Bitmap Heap Scan on test_pictures p (cost=72.45..12791.29 rows=1731 width=60) (actual time=1.781..17.268 rows=492 loops=3) |
Recheck Cond: (title_tsv @@ to_tsquery('address & shipping'::text)) |
Heap Blocks: exact=625 |
-> Bitmap Index Scan on ix_pictures_title_tsv (cost=0.00..71.41 rows=4155 width=0) (actual time=3.765..3.765 rows=1477 loops=1) |
Index Cond: (title_tsv @@ to_tsquery('address & shipping'::text)) |
Planning time: 0.214 ms |
Execution time: 28.995 ms |
在执行计划中可以看到,在第二个查询中使用了索引ix_pictures_title_tsv
,但在第一个查询中没有使用索引,这使使用@@
运算符的查询的速度提高了172倍!
答案 1 :(得分:8)
select *
from (
SELECT
pictures.id,
ts_rank_cd(to_tsvector('english', pictures.title),
to_tsquery('small dog')) AS score
FROM pictures
) s
WHERE score > 0
ORDER BY score DESC
答案 2 :(得分:5)
如果我使用带有重复to_tsvector(...)的版本,它会调用两次,还是足够智能以某种方式缓存结果?
注意这些事情的最好方法是做一个简单的解释,尽管那些很难阅读。
长话短说,是的,PostgreSQL非常聪明,可以重复使用计算结果。
有没有办法不重复to_ts ...函数调用?
我通常会添加一个tsv
列,它是文本搜索向量。如果使用触发器进行此自动更新,它会立即为您提供易于访问的向量,但它还允许您通过选择性触发来有选择地更新搜索索引。
有没有办法在WHERE子句中使用得分?
是的,但不是那个名字。 或者你可以创建一个子查询,但我个人只是重复它。
如果有的话,按分数过滤会更好吗? 0或使用@@ thing?
我能想到的最简单的版本是:
SELECT *
FROM pictures
WHERE 'small dog' @@ text_search_vector
text_search_vector
显然可以替换为to_tsvector('english', pictures.title)