使用 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:

  1. 将所有数据集转为整数,所以它会变成这样(我还添加了列以更接近真实数据集):
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
  1. 将请求作为字符串存储在数组中(请注意,我之前使用某种“请求生成器”对这些请求进行了清理):
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)",
   ....
]
  1. 简单地循环请求数组,从最精确到最近似:
# 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?) ,但到目前为止:这是一个简单有效的解决方案。