【问题标题】:Elasticsearch - find closest number when scoring resultsElasticsearch - 在评分结果时找到最接近的数字
【发布时间】:2019-09-22 06:47:05
【问题描述】:

我需要一种方法来匹配最接近的弹性搜索文档编号。

我想使用弹性搜索来过滤可量化的属性,并且已经能够使用 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
})

假设我有一些包含年份和销售额的文件。在 sn-p 中是如何在 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>

我试图在弹性搜索中实现相同的目标,但在使用 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 的新用户链接到文档的文章或区域!

【问题讨论】:

    标签: javascript node.js elasticsearch


    【解决方案1】:

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

    应该是这样的:

    {
          "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。

    这也可以通过更改功能以更“适当”的方式实现,这是我选择不采用的路径。

    【讨论】:

    • 感谢您的回答,很抱歉回复晚了。我认为我的示例并没有说明我的数据集的全部故事,我给出了一个我认为涵盖了我的问题的简单示例,但实际上,当我运行您的查询时,文档可能没有年份或销售集我得到没有设置属性的结果,有什么方法可以在排序之前将它们过滤掉?我试图向{ filter: { exists: "sales" } } 之类的函数添加过滤器,但我添加过滤器的每个地方都是无效的语法。
    • 那是你的答案,这正是我正在寻找的,你已经告诉我,制作复杂的查询可能比我最初想象的要复杂一些,我要去学习了。再次感谢!
    猜你喜欢
    • 2021-07-27
    • 1970-01-01
    • 2012-07-30
    • 1970-01-01
    • 1970-01-01
    • 2012-06-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多