javascript - Elasticsearch - 在对结果进行评分时找到最接近的数字

标签 javascript node.js elasticsearch

我需要一种方法来匹配最接近的 elasticsearch 文档。

我想使用 Elasticsearch 来过滤可量化的属性,并且已经能够使用 range 查询实现硬限制,接受跳过该结果集之外的结果。我希望获得与多个过滤器匹配最接近的结果。

const query = {
  query: {
    bool: {
      should: [
        {
          range: {
            gte: 5,
            lte: 15
          }
        },
        {
          range: {
            gte: 1979,
            lte: 1989
          }
        }
      ]
    }
  }
}
const results = await client.search({
  index: 'test',
  body: query
})

假设我有一些包含年份和销售额的文档。在代码片段中是一个如何在 javascript 中完成的小例子。它遍历整个列表并计算一个分数,然后根据该分数对它们进行排序,在任何时候都不会过滤掉结果,它们只是按相关性组织。

const data = [
  { "item": "one", "year": 1980, "sales": 20 },
  { "item": "two", "year": 1982, "sales": 12 },
  { "item": "three", "year": 1986, "sales": 6 },
  { "item": "four", "year": 1989, "sales": 4 },
  { "item": "five", "year": 1991, "sales": 6 }
]

const add = (a, b) => a + b


const findClosestMatch = (filters, data) => {
 const scored = data.map(item => ({
    ...item,
    // add the score to a copy of the data
    _score: calculateDifferenceScore(filters, item)
  }))
  // mutate the scored array by sorting it
  scored.sort((a, b) => a._score.total - b._score.total)
  return scored
}

const calculateDifferenceScore = (filters, item) => {
  const result = Object.keys(filters).reduce((acc, x) => ({
    ...acc,
    // calculate the absolute difference between the filter and data point
    [x]: Math.abs(filters[x] - item[x])
  }), {})
  // sum the total diffences
  result.total = Object.values(result).reduce(add)
  return result
}

console.log(
  findClosestMatch({ sales: 10, year: 1984 }, data)
)
<script src="https://codepen.io/synthet1c/pen/KyQQmL.js"></script>

我试图在 elasticsearch 中实现相同的目标,但在使用 function_score 查询时运气不佳。例如

const query = {
  query: {
    function_score: {
      functions: [
        {
          linear: {
            "year": {
              origin: 1984,
            },
            "sales": {
              origin: 10,
            }
          }
        }
      ]
    }
  }
}
const results = await client.search({
  index: 'test',
  body: query
})

没有要搜索的文本,我只是用它来按数字过滤,我做错了什么,或者这不是 Elasticsearch 的目的,还有更好的选择吗?

使用上面的每个文档仍然有一个默认分数,我无法获得任何过滤器来对分数应用任何修饰符。

感谢您的帮助,我是 elasticsearch 的新手,非常感谢文章或文档区域的链接!

最佳答案

您的想法是正确的,您只是在查询中遗漏了几个字段以使其发挥作用。

它应该是这样的:

{
      "query": {
        function_score: {
            functions: [
                {
                    linear: {
                        "year": {
                            origin: 1984,
                            scale: 1,
                            decay: 0.999
                        },
                        "sales": {
                            origin: 10,
                            scale: 1,
                            decay: 0.999
                        }
                    }
                },
            ]
        }
    }
}

scale 字段是必需的,因为它告诉 elastic 如何衰减分数,没有它查询就会失败。

decay 字段不是强制性的,但是如果没有它,elastic 真的不知道如何计算文档的新分数,所以它最终只会给原始范围内的文档一个默认分数+ 对我们没有用的规模。

source docs .

  • 如果您想要得分最高的文档,我还建议您将结果大小限制为 1,否则您将不得不添加一个排序阶段(在弹性或代码中)。

编辑:(避免空值)

您可以像这样在函数上方添加过滤器:

{
    "query": {
        "function_score": {
            "query": {
                "bool": {
                    "must": [
                        {
                            "bool": {
                                "filter": [
                                    {
                                        "bool": {
                                            "must": [
                                                {
                                                    "exists": {
                                                        "field": "year"
                                                    }
                                                },
                                                {
                                                    "exists": {
                                                        "field": "sales"
                                                    }
                                                },
                                            ]
                                        }
                                    }
                                ]
                            }
                        },
                        {
                            "match_all": {}
                        }
                    ]
                }
            },
            "functions": [
                {
                    "linear": {
                        "year": {
                            "origin": 1999,
                            "scale": 1,
                            "decay": 0.999
                        },
                        "sales": {
                            "origin": 50,
                            "scale": 1,
                            "decay": 0.999
                        }
                    }
                }
            ]
        }
    }
}

请注意,我在使用match_all 查询时遇到了一些小问题,这是由于过滤器查询将分数设置为 0,因此通过使用 match_all 查询我将其重置对于所有匹配的文档,返回 1。

这也可以通过改变功能以更“适当”的方式实现,我选择不走这条路。

关于javascript - Elasticsearch - 在对结果进行评分时找到最接近的数字,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58046769/

相关文章:

javascript - 放置由javascript生成的同一图像的多个实例

elasticsearch - Elasticsearch嵌套查询和排序

javascript - jQuery 弹出窗口在第一次单击时不起作用

javascript - jQuery 图像加载 - 多个完成函数执行

node.js - 从 nodejs 调用 https - 将数字 cer 文件放在哪里?

node.js - puppeteer 不在 vpn 上工作,而是在本地运行

api - 在 Heroku 上,使用 Node.js 是否可以避免对第三方 API 调用的队列和 worker dynos 的需求?

Elasticsearch 地理距离排序

ruby-on-rails - Elasticsearch:已达到当前计划的最大索引数-状态:500

javascript - 这个 jQuery Mobile 通用导航系统是如何工作的?