【问题标题】:ElasticSearch returning only documents with distinct valueElasticSearch 仅返回具有不同值的文档
【发布时间】:2014-07-01 10:38:56
【问题描述】:

假设我有这个给定的数据

{
            "name" : "ABC",
            "favorite_cars" : [ "ferrari","toyota" ]
          }, {
            "name" : "ABC",
            "favorite_cars" : [ "ferrari","toyota" ]
          }, {
            "name" : "GEORGE",
            "favorite_cars" : [ "honda","Hyundae" ]
          }

每当我在搜索最喜欢的汽车是丰田的人时查询此数据时,它都会返回此数据

{

            "name" : "ABC",
            "favorite_cars" : [ "ferrari","toyota" ]
          }, {
            "name" : "ABC",
            "favorite_cars" : [ "ferrari","toyota" ]
          }

结果是两条名为 ABC 的记录。如何仅选择不同的文档?我想得到的结果只有这个

{
                "name" : "ABC",
                "favorite_cars" : [ "ferrari","toyota" ]
              }

这是我的查询

{
    "fuzzy_like_this_field" : {
        "favorite_cars" : {
            "like_text" : "toyota",
            "max_query_terms" : 12
        }
    }
}

我正在使用 ElasticSearch 1.0.0。使用 java api 客户端

【问题讨论】:

  • 为什么不退还乔治?您的查询是什么?这个问题需要更多细节才能有用(并且可以回答)
  • @Burkhard 我更新了我的问题。将乔治的青睐改为现代

标签: java elasticsearch aggregate spring-data-elasticsearch nosql


【解决方案1】:

您可以使用aggregations 消除重复项。使用term aggregation,结果将按一个字段分组,例如name,还提供了该字段每个值的出现次数,并将按此计数对结果进行排序(降序)。

{
  "query": {
    "fuzzy_like_this_field": {
      "favorite_cars": {
        "like_text": "toyota",
        "max_query_terms": 12
      }
    }
  },
  "aggs": {
    "grouped_by_name": {
      "terms": {
        "field": "name",
        "size": 0
      }
    }
  }
}

除了hits,结果还将包含buckets,其唯一值位于key,计数位于doc_count

{
  "took" : 4,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "failed" : 0
  },
  "hits" : {
    "total" : 2,
    "max_score" : 0.19178301,
    "hits" : [ {
      "_index" : "pru",
      "_type" : "pru",
      "_id" : "vGkoVV5cR8SN3lvbWzLaFQ",
      "_score" : 0.19178301,
      "_source":{"name":"ABC","favorite_cars":["ferrari","toyota"]}
    }, {
      "_index" : "pru",
      "_type" : "pru",
      "_id" : "IdEbAcI6TM6oCVxCI_3fug",
      "_score" : 0.19178301,
      "_source":{"name":"ABC","favorite_cars":["ferrari","toyota"]}
    } ]
  },
  "aggregations" : {
    "grouped_by_name" : {
      "buckets" : [ {
        "key" : "abc",
        "doc_count" : 2
      } ]
    }
  }
}

请注意,由于重复消除和结果排序,使用聚合的成本会很高。

【讨论】:

  • 如何通过存储桶获得该独特元素?它只包含键
  • PS:fuzzy_like_this_field 在 ES 1.6 中已被弃用 elastic.co/guide/en/elasticsearch/reference/current/…
  • 有没有办法定位数组值本身,而不仅仅是数组内的“术语”。对于包含像 ['mercedes slk', 'bmw 320'] 这样的值的数组,我将 ['mercedes', 'slk', 'bmw', '320'] 作为聚合键返回。
  • 查看这个答案,了解如何使用top_hits 获取第一个结果 - stackoverflow.com/questions/34878356/…
【解决方案2】:

ElasticSearch 不提供任何查询,您可以通过这些查询获取基于字段值的不同文档。

理想情况下,您应该使用相同的 typeid 为同一个文档编制索引,因为 ElasticSearch 使用这两个东西来提供唯一的 _uid id 到文档。唯一 id 很重要,不仅因为它检测重复文档的方式,而且在任何修改的情况下更新同一文档而不是插入新文档。有关索引文档的更多信息,您可以阅读this

但肯定有解决您的问题的方法。由于您使用的是 java api 客户端,因此您可以根据自己的字段值删除重复的文档。事实上,它让您可以更灵活地对从 ES 获得的响应执行自定义操作。

SearchResponse response = client.prepareSearch().execute().actionGet();
SearchHits hits = response.getHits();

Iterator<SearchHit> iterator = hits.iterator();
Map<String, SearchHit> distinctObjects = new HashMap<String,SearchHit>();
while (iterator.hasNext()) {
    SearchHit searchHit = (SearchHit) iterator.next();
    Map<String, Object> source = searchHit.getSource();
    if(source.get("name") != null){
        distinctObjects.put(source.get("name").toString(),source);
    }

} 

因此,您的地图中将拥有一张包含唯一 searchHit 对象的地图。

您还可以创建对象映射并使用它来代替 SearchHit。

我希望这能解决您的问题。如果代码中有任何错误,请原谅我。这只是一个伪代码,让您了解如何解决您的问题。

谢谢

【讨论】:

  • 这种方法很难处理分页。由于每页可以去掉一些元素,所以每页的结果数可以去掉。
  • 我投赞成票,因为答案有助于提问者(并获得两票并解锁赏金)。
【解决方案3】:

@JRL 几乎是正确的。您将需要在查询中进行聚合。这将为您提供按发生率排序的对象中前 10000 个“favorite_cars”的列表。

{
    "query":{ "match_all":{ } },
    "size":0,
    "Distinct" : {
        "Cars" : {
            "terms" : { "field" : "favorite_cars", "order": { "_count": "desc"}, "size":10000 }
         }
    }
}

还值得注意的是,您不希望分析您的“favorite_car”字段以获得“McLaren F1”而不是“McLaren”、“F1”。

"favorite_car": {
    "type": "string",
    "index": "not_analyzed"
}

【讨论】:

    【解决方案4】:

    对于单个分片,这可以使用自定义过滤器来处理,该过滤器也负责分页。为了处理上述用例,我们可以使用如下脚本支持:

    • 定义自定义脚本过滤器。对于本次讨论,假设它被称为 AcceptDistinctDocumentScriptFilter
    • 此自定义过滤器将主键列表作为输入。
    • 这些主键是字段,其值将用于确定记录的唯一性。
    • 现在,我们不使用聚合,而是使用普通搜索请求并将自定义脚本过滤器传递给请求。
    • 如果搜索已经定义了过滤器\查询条件,则使用逻辑 AND 运算符附加自定义过滤器。
    • 以下是使用伪语法的示例 如果请求是: select * from myindex where file_hash = 'hash_value' 然后将自定义过滤器附加为:
      select * from myindex where file_hash = 'hash_value' AND AcceptDistinctDocumentScriptFilter(params= ['file_name', 'file_folder'])

    对于分布式搜索,这很棘手,需要插件挂钩到 QUERY 阶段。更多详情here.

    【讨论】:

    • 虽然很高兴您提供了答案,但最好将信息粘贴到此处,然后引用来源。 (链接可能会及时无法访问)
    • 添加了使用自定义脚本过滤器的解决方案的简要总结。
    猜你喜欢
    • 1970-01-01
    • 2021-12-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多