【问题标题】:How to get per term statistics in Elasticsearch如何在 Elasticsearch 中获取每个术语的统计信息
【发布时间】:2021-03-20 19:23:18
【问题描述】:

我需要实现以下(在后端):用户键入查询并返回命中以及命中的统计信息。下面是一个简化的例子。

假设查询为Grif,则用户返回(仅以随机词为例)

  • 格里菲斯
  • 格里芬
  • 格里夫
  • 格里夫特
  • 格里芬

并且某个词出现的频率+文档的数量,例如:

  • 格里菲斯(频率 10、3 文档)
  • Griffin(频率 17、9 文档)
  • Grif(频率 6、3 文档)
  • Grift(频率 9、5 文档)
  • Griffins(频率 11、4 个文档)

我对 Elasticsearch 比较陌生,所以我不确定从哪里开始实施这样的事情。什么类型的查询最适合这个?我可以用什么来获得这种统计数据?任何其他建议也将不胜感激。

【问题讨论】:

    标签: elasticsearch


    【解决方案1】:

    这有多个层次。你需要:

    • n-gram/partial/search-as-you-type 匹配
    • 一种按原始形式对匹配关键字进行分组的方法
    • 一种反向查找文档和词频的机制。

    我不知道有什么方法可以在 one 中实现这一目标,但这是我的看法。

    1. in my other answer 所述,您可以从一个特殊的、n-gram 驱动的分析器开始。有原始的content 字段,加上一个用于所述分析器的multi-field mapping,再加上一个keyword 字段来汇总:
    PUT my-index
    {
      "settings": {
        "index": {
          "max_ngram_diff": 20
        },
        "analysis": {
          "tokenizer": {
            "my_ngrams": {
              "type": "ngram",
              "min_gram": 3,
              "max_gram": 20,
              "token_chars": [
                "letter",
                "digit"
              ]
            }
          },
          "analyzer": {
            "my_ngrams_analyzer": {
              "tokenizer": "my_ngrams",
              "filter": [
                "lowercase"
              ]
            }
          }
        }
      },
      "mappings": {
        "properties": {
          "content": {
            "type": "text",
            "fields": {
              "analyzed": {
                "type": "text",
                "analyzer": "my_ngrams_analyzer"
              },
              "keyword": {
                "type": "keyword"
              }
            }
          }
        }
      }
    }
    
    1. 接下来,在content 字段中批量插入一些包含文本的示例文档。请注意,每个文档也有一个 _id — 您稍后会需要它们。
    POST _bulk
    {"index":{"_index":"my-index", "_id":1}}
    {"content":"Griffith"}
    {"index":{"_index":"my-index", "_id":2}}
    {"content":"Griffin"}
    {"index":{"_index":"my-index", "_id":3}}
    {"content":"Grif"}
    {"index":{"_index":"my-index", "_id":4}}
    {"content":"Grift"}
    {"index":{"_index":"my-index", "_id":5}}
    {"content":"Griffins"}
    {"index":{"_index":"my-index", "_id":6}}
    {"content":"Griffith"}
    {"index":{"_index":"my-index", "_id":7}}
    {"content":"Griffins"}
    
    1. .analyzed 字段中搜索n-gram,并通过terms aggregation 将匹配的文档按原始术语分组。同时,通过top_hits aggregation检索其中一个分桶文档的_id。顺便说一句——在给定的存储桶中返回哪个 _id 并不重要——它们都将包含相同的分桶项。
    POST my-index/_search?filter_path=aggregations.*.buckets.key,aggregations.*.buckets.doc_count,aggregations.*.buckets.*.hits.hits._id
    {
      "size": 0, 
      "query": {
        "term": {
          "content.analyzed": "grif"
        }
      },
      "aggs": {
        "full_terms": {
          "terms": {
            "field": "content.keyword",
            "size": 10
          },
          "aggs": {
            "top_doc": {
              "top_hits": {
                "size": 1,
                "_source": false
              }
            }
          }
        }
      }
    }
    
    1. 观察响应。上一个请求中的filter_path URL 参数将响应减少到我们需要的那些属性——未触及的原始full_terms 加上一个的底层ID:
    {
      "aggregations" : {
        "full_terms" : {
          "buckets" : [
            {
              "key" : "Griffins",
              "doc_count" : 2,
              "top_doc" : {
                "hits" : {
                  "hits" : [
                    {
                      "_id" : "5"
                    }
                  ]
                }
              }
            },
            {
              "key" : "Griffith",
              "doc_count" : 2,
              "top_doc" : {
                "hits" : {
                  "hits" : [
                    {
                      "_id" : "1"
                    }
                  ]
                }
              }
            },
            {
              "key" : "Grif",
              "doc_count" : 1,
              "top_doc" : {
                "hits" : {
                  "hits" : [
                    {
                      "_id" : "3"
                    }
                  ]
                }
              }
            },
            {
              "key" : "Griffin",
              "doc_count" : 1,
              "top_doc" : {
                "hits" : {
                  "hits" : [
                    {
                      "_id" : "2"
                    }
                  ]
                }
              }
            },
            {
              "key" : "Grift",
              "doc_count" : 1,
              "top_doc" : {
                "hits" : {
                  "hits" : [
                    {
                      "_id" : "4"
                    }
                  ]
                }
              }
            }
          ]
        }
      }
    }
    

    是时候进入有趣的部分了。

    有一个名为 Term Vectors 的专用 Elasticsearch API,它完全满足您的需求 - 它从整个索引中检索字段和术语统计信息。为了将这些统计信息交给您,它需要文档 ID——您将从上述聚合中获得!

    1. 最后,由于您有多个术语向量可供使用,您可以像这样使用Multi term vectors API — 再次通过filter_path 压缩响应:
    POST /my-index/_mtermvectors?filter_path=docs.term_vectors.*.*.*.doc_freq,docs.term_vectors.*.*.*.term_freq
    {
      "docs": [
        {
          "_id": "5",                 <--- guaranteeing
          "fields": [
            "content.keyword"
          ],
          "payloads": false,
          "positions": false,
          "offsets": false,
          "field_statistics": false,
          "term_statistics": true
        },
        {
          "_id": "1",                 <--- the response
          "fields": [
            "content.keyword"
          ],
          "payloads": false,
          "positions": false,
          "offsets": false,
          "field_statistics": false,
          "term_statistics": true
        },
        {
          "_id": "3",                 <--- order
          "fields": [
            "content.keyword"
          ],
          "payloads": false,
          "positions": false,
          "offsets": false,
          "field_statistics": false,
          "term_statistics": true
        },
        {
          "_id": "2",
          "fields": [
            "content.keyword"
          ],
          "payloads": false,
          "positions": false,
          "offsets": false,
          "field_statistics": false,
          "term_statistics": true
        },
        {
          "_id": "4",
          "fields": [
            "content.keyword"
          ],
          "payloads": false,
          "positions": false,
          "offsets": false,
          "field_statistics": false,
          "term_statistics": true
        }
      ]
    }
    
    1. 结果可以在您的后端进行后处理以形成您的自动完成响应。你有 A) 完整的术语,B) 匹配文档的数量 (doc_freq),和 C),术语频率:
    {
      "docs" : [
        {
          "term_vectors" : {
            "content.keyword" : {
              "terms" : {
                "Griffins" : {      |      term
                  "doc_freq" : 2,   | <--  # of docs
                  "term_freq" : 1   |      term frequency
                }
              }
            }
          }
        },
        {
          "term_vectors" : {
            "content.keyword" : {
              "terms" : {
                "Griffith" : {
                  "doc_freq" : 2,
                  "term_freq" : 1
                }
              }
            }
          }
        },
        {
          "term_vectors" : {
            "content.keyword" : {
              "terms" : {
                "Grif" : {
                  "doc_freq" : 1,
                  "term_freq" : 1
                }
              }
            }
          }
        },
        {
          "term_vectors" : {
            "content.keyword" : {
              "terms" : {
                "Griffin" : {
                  "doc_freq" : 1,
                  "term_freq" : 1
                }
              }
            }
          }
        },
        {
          "term_vectors" : {
            "content.keyword" : {
              "terms" : {
                "Grift" : {
                  "doc_freq" : 1,
                  "term_freq" : 1
                }
              }
            }
          }
        }
      ]
    }
    

    无耻插件:如果您是 Elasticsearch 的新手,并且像我一样,从实际示例中学习最好,请考虑购买 my Elasticsearch Handbook

    【讨论】:

    • 如果我的content 字段需要很大怎么办?可能吗?有什么注意事项?
    • ES 返回 Document contains at least one immense term in field="content.keyword" (whose UTF8 encoding is longer than the max length 32766), all of which were skipped. Please correct the analyzer to not produce such terms. The prefix of the first immense term is: '..', original message: bytes can be at most 32766 in length; got 103339 如果我尝试索引大小约为 100k 个字符的 content
    • .keyword 字段不适用于如此大的字符串。您可以尝试将内容拆分为content_Ncontent_N+1 等...
    • 所以,没有其他办法,只能拆分成单独的字段,如content_part_1content_part_2...content_part_N
    • 坦率地说,我的回答是针对短的、类似关键字的字符串,而不是超长的文本字段。上述方法利用了这样一个事实,即您可以快速聚合关键字,从而返回代表最多的关键字。您可以没有这个聚合(并且没有整个keyword 映射)来摆脱这个错误,但是需要一些其他的方法来从长文本字段中提取用户定义的查询匹配-- 可能通过highlighting.
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-03-28
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多