ElasticSearch仅返回具有唯一值的文档

17

假设我有这样的给定数据

{
            "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客户端。


为什么 GEORGE 没有被返回?你的查询是什么?这个问题需要更多细节才能有用(并且回答)。 - Burkhard
@Burkhard 我更新了我的问题。将George的喜好更改为Hyundae。 - user962206
4个回答

21
您可以使用聚合来消除重复项。使用项聚合,结果将按一个字段分组,例如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
      } ]
    }
  }
}

请注意,使用聚合将是昂贵的,因为需要消除重复项并对结果进行排序。

如何通过桶获取那个只包含键的唯一元素? - user962206
2
PS:fuzzy_like_this_field在ES 1.6中已被弃用 https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-flt-field-query.html - HIRA THAKUR
1
有没有一种方法可以针对数组值本身进行定位,而不仅仅是数组中的“项”? 对于包含如下值的数组['mercedes slk', 'bmw 320'],我得到的聚合键返回值为['mercedes','slk','bmw','320']。 - mimimimichael
1
请参考此答案,了解如何使用 top_hits 拉取第一个结果 - http://stackoverflow.com/questions/34878356/select-distinct-values-of-bool-query-elastic-search#answer-34902873 - Nigel Sheridan-Smith

9

ElasticSearch没有提供任何查询方法,可以根据字段值获取不同的文档。

理想情况下,您应该使用相同的类型id索引相同的文档,因为ElasticSearch使用这两个内容为文档提供一个_uid唯一标识符。唯一标识符不仅因为其检测重复文档的方式而重要,还因为在对文档进行修改时更新相同的文档而不是插入新文档。有关索引文档的更多信息,请阅读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的位置使用它。

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

谢谢


2
这种方法使得处理分页变得困难。由于每个页面上可能会删除一些元素,因此每个页面上的结果数量可能会不准确。 - evanwong
我投赞成票,因为这个答案帮助了提问者(并且可以获得两个赞成票并解锁悬赏)。 - user2226755

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"
}

2
对于单个分片,可以使用自定义过滤器来处理并处理分页。为了处理上述用例,我们可以使用以下脚本支持:
  • 定义一个自定义脚本过滤器。在本讨论中,假设它被称为AcceptDistinctDocumentScriptFilter
  • 此自定义过滤器以主键列表作为输入。
  • 这些主键是将用于确定记录唯一性的字段的值。
  • 现在,我们使用普通搜索请求而不是聚合,并将自定义脚本过滤器传递给请求。
  • 如果搜索已经有了过滤器/查询条件,则使用逻辑AND运算符附加自定义过滤器。
  • 以下是使用伪语法的示例 如果请求是: 从myindex选择* where file_hash ='hash_value' 然后附加自定义过滤器如下:
    从myindex选择* where file_hash ='hash_value' AND AcceptDistinctDocumentScriptFilter(params= ['file_name', 'file_folder'])
对于分布式搜索,这很棘手,需要插件来钩入QUERY阶段。更多细节请参见这里

1
虽然你提供了一个答案,但最好在这里粘贴信息,然后引用来源。(链接可能会变得无法访问) - George Netu
添加了使用自定义脚本过滤器的解决方案简要摘要。 - Ajey Dudhe

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接