如何在 ElasticSearch 中按非标记化字段长度进行搜索

How to search by non-tokenized field length in ElasticSearch

假设我创建了一个索引 people,它将采用具有两个属性的条目:namefriends

PUT /people
{
  "mappings": {
    "properties": {
      "friends": { 
        "type": "text",
        "fields": {
          "keyword": { 
            "type": "keyword"
          }
        }
      }
    }
  }
}

我放了两个条目,每个条目都有两个朋友。

POST /people/_doc
{
  "name": "Jack",
  "friends": [
    "Jill", "John"
  ]
}


POST /people/_doc
{
  "name": "Max",
  "friends": [
    "John", "John"  # Max will have two friends, but both named John
  ]
}

现在我想搜索有多个朋友的人

GET /people/_search
{
  "query": {
    "bool": {
      "filter": [
        {
          "script": {
            "script": {
              "source": "doc['friends.keyword'].length > 1"
            }
          }
        }
      ]
    }
  }
}

这只会 return Jack 而忽略 Max。我认为这是因为我们实际上是在遍历倒排索引,而 John 和 John 只创建了一个标记 - 即 'john' 所以标记的长度实际上是 1。

由于我的索引比较小,性能不是重点,所以我想实际遍历source而不是倒排索引

GET /people/_search
{
  "query": {
    "bool": {
      "filter": [
        {
          "script": {
            "script": {
              "source": "ctx._source.friends.length > 1"
            }
          }
        }
      ]
    }
  }
}

但是根据 https://github.com/elastic/elasticsearch/issues/20068 源仅在更新时支持,在搜索时不支持,所以我不能。

一个明显的解决方案似乎是获取字段的长度并将其存储到索引中。像 friends_count: 2 这样的东西,然后根据它进行过滤。但这需要重新编制索引,而且这似乎应该以我所缺少的一些明显方式解决。

非常感谢。

ES 7.11 中有一项新功能作为运行时字段运行时字段是在查询时评估的字段。运行时字段使您能够:

  1. 在不重新索引数据的情况下向现有文档添加字段
  2. 在不了解数据结构的情况下开始使用您的数据
  3. 覆盖查询时从索引字段返回的值
  4. 在不修改基础架构的情况下为特定用途定义字段

您可以找到有关运行时字段的更多信息here,但是如何使用运行时字段您可以这样做:

索引时间:

PUT my-index/
{
  "mappings": {
    "runtime": {
      "friends_count": {
        "type": "keyword",
        "script": {
          "source": "doc['@friends'].size()"
        }
      }
    },
    "properties": {
      "@timestamp": {"type": "date"}
    }
  }
}

您还可以在搜索时间使用运行时字段以获取更多信息检查here

搜索时间

GET my-index/_search
{
  "runtime_mappings": {
    "friends_count": {
      "type": "keyword",
      "script": {
        "source": "ctx._source.friends.size()"
      }
    }
  }
}

更新:

POST mytest/_update_by_query
{
    "query": {
        "match_all": {}
    }, 
    "script": {
       "source": "ctx._source.arrayLength = ctx._source.friends.size()"
    }
}

您可以使用上面的查询更新您的所有文档并调整您的查询。

对于所有想知道同一问题的人,我认为@Kaveh 的回答是最有可能的方法,但我没能在我的案例中做到这一点。在我看来,源是在执行查询后创建的,因此您无法出于过滤查询的目的访问源。

你有两个选择:

  • 在应用程序级别过滤结果(丑陋且缓慢的解决方案)
  • 实际上将字段长度保存在一个单独的字段中。比如friends_count

可能还有一个我不知道的选项(?)。