我需要一种方法来匹配最接近的 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 真的不知道如何计算文档的新分数,所以它最终只会给原始范围内的文档一个默认分数+ 对我们没有用的规模。
- 如果您想要得分最高的文档,我还建议您将结果大小限制为 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/