如何在Searchkick(ElasticSearch)中合并两类搜索结果
可行的实现方案
嘿,我来给你分享几个合并这两个Searchkick搜索结果的实用方法~
1. 简单直接:数组拼接法
这是最容易上手的方式,把两个搜索结果转成数组后直接相加,要是需要保持按相关性评分排序,再重新排一遍就好:
# 先分别执行两个搜索 foods = Food.search(name, fields: [:name], where: { or: [ [{nutritionist_id: nil}, {nutritionist_id: current_nutritionist_id}] ] }, order: {_score: :desc}) recipes = Recipe.search(name, fields: [:recipe_name], where: { or: [ [{nutritionist_id: nil}, {nutritionist_id: current_nutritionist_id}] ] }, order: {_score: :desc}) # 拼接结果数组 combined_results = foods.to_a + recipes.to_a # 按_score降序重新排序(如果需要统一排序的话) combined_results.sort_by! { |item| -item._score }
优缺点:
- 优点:零额外配置,代码改动极小,快速实现需求。
- 缺点:如果搜索结果量很大,会占用较多内存;分页逻辑需要自己在内存中处理,不如数据库层面分页高效。
2. 更优方案:Searchkick多模型搜索
这是推荐的做法,直接让Elasticsearch在搜索阶段就完成多模型的合并,性能和扩展性都更好:
combined_results = Searchkick.search( name, # 指定要搜索的模型 models: [Food, Recipe], # 给每个模型指定对应的搜索字段 fields: { Food: [:name], Recipe: [:recipe_name] }, # 共用的where条件 where: { or: [ [{nutritionist_id: nil}, {nutritionist_id: current_nutritionist_id}] ] }, # 按相关性评分降序排序 order: {_score: :desc}, # 支持分页参数,比如 page: 1, per_page: 20 page: params[:page], per_page: 20 )
为什么说这个更优?
- 搜索和合并在Elasticsearch端完成,内存占用低,性能更高;
- 原生支持分页、排序、高亮等Searchkick的特性,不用自己手动处理;
- 返回的结果集合和单个模型搜索的结构一致,后续处理逻辑更统一。
小提示:区分结果类型
如果需要在后续逻辑里区分是Food还是Recipe,直接通过对象的类判断就行:
combined_results.each do |item| if item.is_a?(Food) # 处理食物相关逻辑,比如渲染食物卡片 elsif item.is_a?(Recipe) # 处理食谱相关逻辑,比如渲染食谱卡片 end end
总结
- 数据量小、快速验证需求:用数组拼接法;
- 数据量大、需要分页/高性能:用多模型搜索法,这是长期维护的最优解。
内容的提问来源于stack exchange,提问作者Danilo Cândido
相关产品推荐
相关产品推荐

