PostgreSQL中的全文搜索,根据每个关键字的不同分数进行自定义排名
有没有办法在PostgreSQL中“扩展”ts_rank函数或创建自定义的设置权重 我有两个表,PostgreSQL中的全文搜索,根据每个关键字的不同分数进行自定义排名,sql,postgresql,search,full-text-search,Sql,Postgresql,Search,Full Text Search,有没有办法在PostgreSQL中“扩展”ts_rank函数或创建自定义的设置权重 我有两个表,记录和标记,记录可以有多个标记。它是使用表记录\u标记的多对多关联records\u tags有一列score,这意味着同一个标签的得分对于每个记录都不同,并且PostgreSQL的setweight中的权重级别不止4个 简化数据示例: 记录表格 id | title | description | privacy
记录
和标记
,记录
可以有多个标记。它是使用表记录\u标记的多对多关联records\u tags
有一列score
,这意味着同一个标签的得分对于每个记录都不同,并且PostgreSQL的setweight中的权重级别不止4个
简化数据示例:
记录
表格
id | title | description | privacy
----+------------------------+--------------------------------------+---------
1 | 'The best record ever' | 'Long and meaningful description...' | 1
2 | 'Another record' | 'Description of the other record...' | 2
id | name
----+---------------------------
1 | 'artificial intelligence'
2 | 'machine learning'
3 | 'life science'
标签
表格
id | title | description | privacy
----+------------------------+--------------------------------------+---------
1 | 'The best record ever' | 'Long and meaningful description...' | 1
2 | 'Another record' | 'Description of the other record...' | 2
id | name
----+---------------------------
1 | 'artificial intelligence'
2 | 'machine learning'
3 | 'life science'
记录\u标签
表格
record_id | tag_id | score
-----------+--------+-------
1 | 1 | 87
1 | 2 | 23
2 | 1 | 54
2 | 2 | 67
2 | 3 | 90
这些表中的数据合并到另一个表search\u documents
,该表的列body
为jsonb类型,并包含每个记录的聚合标记名
search\u documents.body
如下所示:
{
title: 'The best record ever',
description: 'Long and meaningful description...',
tags: ['artificial intelligence', 'machine learning']
}
setweight(to_tsvector('simple', (body ->> 'tags')), 'A') || ' ' ||
setweight(to_tsvector('english', (body ->> 'title')), 'B') || ' ' ||
setweight(to_tsvector('english', (body ->> 'description')), 'C')
SELECT
ts_rank(sd.tsv, to_tsquery('english', ''' ' || :query || ' ''' || ':*'), 1) +
ts_rank(sd.tsv, to_tsquery('simple', ''' ' || :query || ' ''' || ':*'), 1) AS rank
sd.id AS id
FROM
search_documents sd
WHERE
sd.tsv @@ to_tsquery('english', ''' ' || :query || ' ''' || ':*') OR
sd.tsv @@ to_tsquery('simple', ''' ' || :query || ' ''' || ':*')
IF NEW.searchable_type = 'record' THEN
NEW.tsv := (
setweight(to_tsvector('simple', (NEW.body ->> 'tags')), 'A') || ' ' ||
setweight(to_tsvector('english', (NEW.body ->> 'title')), 'B') || ' ' ||
setweight(to_tsvector('english', (NEW.body ->> 'description')), 'C')
)::tsvector;
ELSE
NEW.tsv := (
setweight(to_tsvector('simple', (NEW.body ->> 'name')), 'A') || ' ' ||
setweight(to_tsvector('english', (NEW.body ->> 'description')), 'C')
)::tsvector;
END IF;
return NEW;
现在,我已经使用tsvector和setweight实现了全文搜索,如下所示:
{
title: 'The best record ever',
description: 'Long and meaningful description...',
tags: ['artificial intelligence', 'machine learning']
}
setweight(to_tsvector('simple', (body ->> 'tags')), 'A') || ' ' ||
setweight(to_tsvector('english', (body ->> 'title')), 'B') || ' ' ||
setweight(to_tsvector('english', (body ->> 'description')), 'C')
SELECT
ts_rank(sd.tsv, to_tsquery('english', ''' ' || :query || ' ''' || ':*'), 1) +
ts_rank(sd.tsv, to_tsquery('simple', ''' ' || :query || ' ''' || ':*'), 1) AS rank
sd.id AS id
FROM
search_documents sd
WHERE
sd.tsv @@ to_tsquery('english', ''' ' || :query || ' ''' || ':*') OR
sd.tsv @@ to_tsquery('simple', ''' ' || :query || ' ''' || ':*')
IF NEW.searchable_type = 'record' THEN
NEW.tsv := (
setweight(to_tsvector('simple', (NEW.body ->> 'tags')), 'A') || ' ' ||
setweight(to_tsvector('english', (NEW.body ->> 'title')), 'B') || ' ' ||
setweight(to_tsvector('english', (NEW.body ->> 'description')), 'C')
)::tsvector;
ELSE
NEW.tsv := (
setweight(to_tsvector('simple', (NEW.body ->> 'name')), 'A') || ' ' ||
setweight(to_tsvector('english', (NEW.body ->> 'description')), 'C')
)::tsvector;
END IF;
return NEW;
并按如下方式搜索查询:
{
title: 'The best record ever',
description: 'Long and meaningful description...',
tags: ['artificial intelligence', 'machine learning']
}
setweight(to_tsvector('simple', (body ->> 'tags')), 'A') || ' ' ||
setweight(to_tsvector('english', (body ->> 'title')), 'B') || ' ' ||
setweight(to_tsvector('english', (body ->> 'description')), 'C')
SELECT
ts_rank(sd.tsv, to_tsquery('english', ''' ' || :query || ' ''' || ':*'), 1) +
ts_rank(sd.tsv, to_tsquery('simple', ''' ' || :query || ' ''' || ':*'), 1) AS rank
sd.id AS id
FROM
search_documents sd
WHERE
sd.tsv @@ to_tsquery('english', ''' ' || :query || ' ''' || ':*') OR
sd.tsv @@ to_tsquery('simple', ''' ' || :query || ' ''' || ':*')
IF NEW.searchable_type = 'record' THEN
NEW.tsv := (
setweight(to_tsvector('simple', (NEW.body ->> 'tags')), 'A') || ' ' ||
setweight(to_tsvector('english', (NEW.body ->> 'title')), 'B') || ' ' ||
setweight(to_tsvector('english', (NEW.body ->> 'description')), 'C')
)::tsvector;
ELSE
NEW.tsv := (
setweight(to_tsvector('simple', (NEW.body ->> 'name')), 'A') || ' ' ||
setweight(to_tsvector('english', (NEW.body ->> 'description')), 'C')
)::tsvector;
END IF;
return NEW;
但它根本不允许我使用标签的分数
我的想法是,我有一个用于标记分数的标准化函数,它返回0到1范围内的分数,并使用该函数乘以a权重。它看起来像这样-(x_i− 最小(x))/(最大(x)− 最小值(x))
在计算等级
时,除了当前实现之外,还有什么方法可以使用标记的分数
编辑:
search\u documents
是一个表,不是物化视图,在搜索过程开始时,它拥有(或将拥有)所有数据。它包含我要搜索的所有项目,不仅包括记录
,还包括其他项目-帐户
和扬声器
。更新源表时,搜索文档也会更新。还有隐私栏,因为每个用户都有不同的权限,我不希望他们在无法访问搜索结果时看到搜索结果中的项目
搜索\u文档
表格示例:
tsv | searchable_id | searchable_type | privacy | body
-----+---------------+-----------------+---------+-----------------------------------------------------
... | 1 | 'record' | 1 | { title: '...', description: '...', tags: ['...'] }
... | 1 | 'account' | 1 | { name: '...', description: '...' }
... | 1 | 'speaker' | 1 | { name: '...', description: '...' }
tsv
是在插入/更新表时使用触发器创建的ts_向量。它是这样创建的:
{
title: 'The best record ever',
description: 'Long and meaningful description...',
tags: ['artificial intelligence', 'machine learning']
}
setweight(to_tsvector('simple', (body ->> 'tags')), 'A') || ' ' ||
setweight(to_tsvector('english', (body ->> 'title')), 'B') || ' ' ||
setweight(to_tsvector('english', (body ->> 'description')), 'C')
SELECT
ts_rank(sd.tsv, to_tsquery('english', ''' ' || :query || ' ''' || ':*'), 1) +
ts_rank(sd.tsv, to_tsquery('simple', ''' ' || :query || ' ''' || ':*'), 1) AS rank
sd.id AS id
FROM
search_documents sd
WHERE
sd.tsv @@ to_tsquery('english', ''' ' || :query || ' ''' || ':*') OR
sd.tsv @@ to_tsquery('simple', ''' ' || :query || ' ''' || ':*')
IF NEW.searchable_type = 'record' THEN
NEW.tsv := (
setweight(to_tsvector('simple', (NEW.body ->> 'tags')), 'A') || ' ' ||
setweight(to_tsvector('english', (NEW.body ->> 'title')), 'B') || ' ' ||
setweight(to_tsvector('english', (NEW.body ->> 'description')), 'C')
)::tsvector;
ELSE
NEW.tsv := (
setweight(to_tsvector('simple', (NEW.body ->> 'name')), 'A') || ' ' ||
setweight(to_tsvector('english', (NEW.body ->> 'description')), 'C')
)::tsvector;
END IF;
return NEW;
这就是在搜索文档中创建记录
数据的方式:
SELECT GREATEST(MAX(r.privacy), MAX(f.privacy), MAX(a.privacy)) AS privacy,
'record' AS searchable_type,
r.id AS searchable_id,
json_build_object(
'tags', array_remove(array_agg(t.name), NULL),
'title', r.title,
'description', r.description
) AS body
FROM records r
LEFT JOIN folders f ON r.folder_id = f.id
LEFT JOIN accounts a ON r.account_id = a.id
LEFT JOIN records_tags rt ON r.id = rt.record_id
LEFT JOIN tags t ON rt.tag_id = t.id
WHERE r.id = :id
GROUP BY searchable_id
ON CONFLICT(searchable_type, searchable_id)
DO UPDATE
SET privacy = EXCLUDED.privacy,
body = EXCLUDED.body
您可以使用的三参数形式设置特定词素的权重,而不是为整个tsvector设置相同的所有权重。您可能会将其构建到表“search_documents”的创建过程中,但我不能建议具体的实现,因为您没有向我们展示该创建过程。一旦获得了正确加权的tsvector,将其存储为单独的列,而不是存储在JSONB中可能是有意义的。您可以使用的3参数形式设置特定词素的权重,而不是为整个tsvector设置相同的所有权重。您可能会将其构建到表“search_documents”的创建过程中,但我不能建议具体的实现,因为您没有向我们展示该创建过程。一旦获得了正确加权的tsvector,将其存储为单独的列,而不是存储在JSONB中可能是有意义的。实际上,我已经展示了ts_向量的创建过程,并且我已经在setweight函数中使用了权重,但只有4个级别。无论如何,我编辑了这个问题,并添加了数据样本。除非我错放了什么,否则您使用的是2参数形式。您需要使用3参数形式。您没有显示search_documents表(物化视图?)本身的创建,这可能需要更改以适应此情况。正如我在文章中所写,search_documents是一个表。我在问题上增加了结构。我试图在没有其他数据的情况下使问题尽可能简单,因为我认为这些数据并不重要。一切都有点复杂,因为我不仅要搜索记录,还要搜索其他表-帐户和扬声器。事实上,我已经展示了ts_向量创建过程,我已经在setweight函数中使用了权重,但只有4个级别。无论如何,我编辑了这个问题,并添加了数据样本。除非我错放了什么,否则您使用的是2参数形式。您需要使用3参数形式。您没有显示search_documents表(物化视图?)本身的创建,这可能需要更改以适应此情况。正如我在文章中所写,search_documents是一个表。我在问题上增加了结构。我试图在没有其他数据的情况下使问题尽可能简单,因为我认为这些数据并不重要。一切都有点复杂,因为我不仅要搜索记录,还要搜索其他表-帐户和扬声器。“在计算排名时,除了当前的实现之外,还有什么方法可以使用标记的分数吗?”
我能想到的一个解决方案是修改“排名”作为后处理步骤。在根据文本的ts_rank获得排名后,您可以使用自定义公式使用分数值修改排名。这正是我最终得到的结果,它就像一个符咒一样工作。“在计算排名时,除了当前实现之外,还有什么方法可以使用标记的得分?”
我能想到的一个解决方案是将“排名”修改为后处理步骤。在你根据文本的ts_等级获得等级后,你可以使用你的自定义公式使用分数值修改等级。这正是我最终得到的结果,它就像一个符咒。