使用 Postgres 进行加权 + 有序标签搜索
Weighted + ordered tag search using Postgres
在对数万个音频文件进行 AI 文件分析后,我最终在 Postgres 数据库中得到了这种数据结构:
id | name | tag_1 | tag_2 | tag_3 | tag_4 | tag_5
1 | first song | rock | pop | 80s | female singer | classic rock
2 | second song | pop | rock | jazz | electronic | new wave
3 | third song | rock | funk | rnb | 80s | rnb
标签位置真的很重要:越“靠左”,在歌曲中就越突出。标签的数量也是有限的(50 个标签),并且 AI 总是 return 每首歌有 5 个标签,预期没有空值。
另一方面,这是我要查询的内容:
{"rock" => 15, "pop" => 10, "soul" => 3}
键是标签名称,值是任意权重。条目数可以是 1 到 50 之间的随机数。
根据示例数据集,在这种情况下它应该 return [1, 3, 2]
如果使用原始连接字符串更容易实现,我也愿意接受数据重组,但是...使用 Postgres(tsvectors?)是否可行,或者我真的必须为此使用 Elasticsearch 之类的东西?
经过大量的试验和错误,这就是我最终得到的,只使用 Postgres:
- 将所有数据集转为整数,所以它会变成这样(我还添加了列以更接近真实数据集):
id | bpm | tag_1 | tag_2 | tag_3 | tag_4 | tag_5
1 | 114 | 1 | 2 | 3 | 4 | 5
2 | 102 | 2 | 1 | 6 | 7 | 8
3 | 110 | 1 | 9 | 10 | 3 | 12
- 将请求作为字符串存储在数组中(请注意,我之前使用某种“请求生成器”对这些请求进行了清理):
requests = [
"bpm BETWEEN 110 AND 124
AND tag_1 = 1
AND tag_2 = 2
AND tag_3 = 3
AND tag_4 = 4
AND tag_5 = 5",
"bpm BETWEEN 110 AND 124
AND tag_1 = 1
AND tag_2 = 2
AND tag_3 = 3
AND tag_4 = 4
AND tag_5 IN (1, 3, 5)",
"bpm BETWEEN 110 AND 124
AND tag_1 = 1
AND tag_2 = 2
AND tag_3 = 3
AND tag_4 IN (1, 3, 5),
AND tag_5 IN (1, 3, 5)",
....
]
- 简单地循环请求数组,从最精确到最近似:
# Ruby / ActiveRecord example
track_ids = []
requests.each do |request|
track_ids += Track.where([
"(#{request})
AND tracks.id NOT IN ?", track_ids
]).pluck(:id)
break if track_ids.length > 200
end
...完成!我所有的歌曲都是按相似度排序的,最接近的在最上面,越往下,它们越接近。因为一切都是关于整数的,所以它非常快(在 100K 行的数据集上足够快),并且输出看起来像纯粹的魔法。奖励点:它仍然很容易被整个团队调整和维护。
我知道这很粗糙,所以我愿意接受任何更有效的方法来做同样的事情,即使堆栈中需要其他东西(ES?) ,但到目前为止:这是一个简单有效的解决方案。
在对数万个音频文件进行 AI 文件分析后,我最终在 Postgres 数据库中得到了这种数据结构:
id | name | tag_1 | tag_2 | tag_3 | tag_4 | tag_5
1 | first song | rock | pop | 80s | female singer | classic rock
2 | second song | pop | rock | jazz | electronic | new wave
3 | third song | rock | funk | rnb | 80s | rnb
标签位置真的很重要:越“靠左”,在歌曲中就越突出。标签的数量也是有限的(50 个标签),并且 AI 总是 return 每首歌有 5 个标签,预期没有空值。
另一方面,这是我要查询的内容:
{"rock" => 15, "pop" => 10, "soul" => 3}
键是标签名称,值是任意权重。条目数可以是 1 到 50 之间的随机数。 根据示例数据集,在这种情况下它应该 return [1, 3, 2]
如果使用原始连接字符串更容易实现,我也愿意接受数据重组,但是...使用 Postgres(tsvectors?)是否可行,或者我真的必须为此使用 Elasticsearch 之类的东西?
经过大量的试验和错误,这就是我最终得到的,只使用 Postgres:
- 将所有数据集转为整数,所以它会变成这样(我还添加了列以更接近真实数据集):
id | bpm | tag_1 | tag_2 | tag_3 | tag_4 | tag_5
1 | 114 | 1 | 2 | 3 | 4 | 5
2 | 102 | 2 | 1 | 6 | 7 | 8
3 | 110 | 1 | 9 | 10 | 3 | 12
- 将请求作为字符串存储在数组中(请注意,我之前使用某种“请求生成器”对这些请求进行了清理):
requests = [
"bpm BETWEEN 110 AND 124
AND tag_1 = 1
AND tag_2 = 2
AND tag_3 = 3
AND tag_4 = 4
AND tag_5 = 5",
"bpm BETWEEN 110 AND 124
AND tag_1 = 1
AND tag_2 = 2
AND tag_3 = 3
AND tag_4 = 4
AND tag_5 IN (1, 3, 5)",
"bpm BETWEEN 110 AND 124
AND tag_1 = 1
AND tag_2 = 2
AND tag_3 = 3
AND tag_4 IN (1, 3, 5),
AND tag_5 IN (1, 3, 5)",
....
]
- 简单地循环请求数组,从最精确到最近似:
# Ruby / ActiveRecord example
track_ids = []
requests.each do |request|
track_ids += Track.where([
"(#{request})
AND tracks.id NOT IN ?", track_ids
]).pluck(:id)
break if track_ids.length > 200
end
...完成!我所有的歌曲都是按相似度排序的,最接近的在最上面,越往下,它们越接近。因为一切都是关于整数的,所以它非常快(在 100K 行的数据集上足够快),并且输出看起来像纯粹的魔法。奖励点:它仍然很容易被整个团队调整和维护。
我知道这很粗糙,所以我愿意接受任何更有效的方法来做同样的事情,即使堆栈中需要其他东西(ES?) ,但到目前为止:这是一个简单有效的解决方案。