提问者:小点点

如何使用include和regex在elasticsearch中正确查询术语聚合值内部?


您如何有效地过滤/搜索汇总结果?

想象一下,你在弹性搜索中有100万文档。在这些文档中,你有一个multi_field(关键字,文本)标签

{
  ...
  tags: ['Race', 'Racing', 'Mountain Bike', 'Horizontal'],
  ...
},
{
  ...
  tags: ['Tracey Chapman', 'Silverfish', 'Blue'],
  ...
},
{
  ...
  tags: ['Surfing', 'Race', 'Disgrace'],
  ...
},

您可以将这些值用作针对查询的过滤器(facets),以仅拉取包含此标签的文档:

...
"filter": [
  {
    "terms": {
      "tags": [
        "Race"
      ]
    }
  },
  ...
]

但是您希望用户能够查询可能的标签过滤器。因此,如果用户键入比赛,则返回应显示(来自前面的示例),['比赛','Tracey Chapman','耻辱']。这样,用户可以查询要使用的过滤器。为了实现这一点,我必须使用聚合:

{
  "aggs": {
    "topics": {
      "terms": {
        "field": "tags",
        "include": ".*[Rr][Aa][Cc][Ee].*", // I have to dynamically form this
        "size": 6
      }
    }
  },
  "size": 0
}

这正是我需要的!但是它很慢,非常慢。我试过添加execution_hint,它对我没有帮助。

你可能会想,“在聚合之前使用一个查询!”但问题是它会拉取该查询中所有文档的所有值。这意味着,您可以显示完全不相关的标签。如果我在聚合之前查询比赛,并且没有使用包含正则表达式,我最终会得到所有其他值,例如'水平'等…

我如何重写这个聚合以更快地工作?有没有更好的方法来编写这个?我真的必须为值创建一个单独的索引吗?(悲伤的脸)似乎这将是一个常见的问题,但通过留档和谷歌搜索没有找到答案。


共1个答案

匿名用户

您当然不需要仅针对值的单独索引…

这是我的看法:

  1. 你对正则表达式所做的事情本质上是标记器应该做的——即构造子字符串(或N-gram),以便以后可以针对它们。这意味着关键字竞赛将需要被标记为n-gram["rac","ran","ace"]。(低于3个字符没有真正的意义——大多数自动补全库选择忽略少于3个字符,因为可能的匹配膨胀得太快了。)

Elasticsearch提供了N-gram标记器,但我们需要将名为max_ngram_diff的默认索引级别设置从1增加到(任意)10,因为我们希望捕获尽可能多的ngram:

PUT tagindex
{
  "settings": {
    "index": {
      "max_ngram_diff": 10
    },
    "analysis": {
      "analyzer": {
        "my_ngrams_analyzer": {
          "tokenizer": "my_ngrams",
          "filter": [ "lowercase" ]
        }
      },
      "tokenizer": {
        "my_ngrams": {
          "type": "ngram",
          "min_gram": 3,
          "max_gram": 10,
          "token_chars": [ "letter", "digit" ]
        }
      }
    }
  },
  { "mappings": ... }                                 --> see below
}

现在,嵌套列表应该包含对象,因此

{
  "tags": ["Race", "Racing", "Mountain Bike", "Horizontal"]
}

将需要转换为

{
  "tags": [
    { "tag": "Race" },
    { "tag": "Racing" },
    { "tag": "Mountain Bike" },
    { "tag": "Horizontal" }
  ]
}

之后,我们将继续进行多字段映射,保持原始标签不变,但也添加一个. token enize字段进行搜索,并添加一个.keyword字段进行聚合:

  "index": { ... },
  "analysis": { ... },
  "mappings": {
    "properties": {
      "tags": {
        "type": "nested",
        "properties": {
          "tag": {
            "type": "text",
            "fields": {
              "tokenized": {
                "type": "text",
                "analyzer": "my_ngrams_analyzer"
              },
              "keyword": {
                "type": "keyword"
              }
            }
          }
        }
      }
    }
  }

然后,我们将添加调整后的标签文档:

POST tagindex/_doc
{"tags":[{"tag":"Race"},{"tag":"Racing"},{"tag":"Mountain Bike"},{"tag":"Horizontal"}]}

POST tagindex/_doc
{"tags":[{"tag":"Tracey Chapman"},{"tag":"Silverfish"},{"tag":"Blue"}]}

POST tagindex/_doc
{"tags":[{"tag":"Surfing"},{"tag":"Race"},{"tag":"Disgrace"}]}

并应用嵌套的过滤器术语聚合:

GET tagindex/_search
{
  "aggs": {
    "topics_parent": {
      "nested": {
        "path": "tags"
      },
      "aggs": {
        "topics": {
          "filter": {
            "term": {
              "tags.tag.tokenized": "race"
            }
          },
          "aggs": {
            "topics": {
              "terms": {
                "field": "tags.tag.keyword",
                "size": 100
              }
            }
          }
        }
      }
    }
  },
  "size": 0
}

产生

{
  ...
  "topics_parent" : {
    ...
    "topics" : {
      ...
      "topics" : {
        ...
        "buckets" : [
          {
            "key" : "Race",
            "doc_count" : 2
          },
          {
            "key" : "Disgrace",
            "doc_count" : 1
          },
          {
            "key" : "Tracey Chapman",
            "doc_count" : 1
          }
        ]
      }
    }
  }
}

注意事项

  • 为了使其工作,您必须重新索引
  • ngram会增加存储空间--取决于每个文档有多少标签,这可能会成为一个问题
  • 嵌套字段在内部被视为“单独的文档”,因此这也会影响磁盘空间

附言:这是一个有趣的用例。让我知道实现进展如何!