在默认情况下,ES对搜索结果是按照相关性降序排序的。有时需要按照某些字段的值进行升序或者降序排序。

        ES提供了sort子句可以对数据进行排序。使用sort子句一般是按照字段信息进行排序,不受相关性影响,而且打分步骤需要耗费一定的硬件资源和时间,因此默认情况下,不对文档进行打分。使用sort排序分为两种类别,一种是按照字段值的大小进行排序,另一种是按照给定地理坐标的距离远近进行排序。

按普通字段值排序

        使用sort子句对字段值进行排序时需要指定排序的字段。ES默认是按照字段值进行升序排序,可以设置order参数为asc或desc,指定按照字段值进行升序或者降序排序。以下示例为搜索名称包含“北京”的旅馆,并对旅馆按照价格进行降序排列:

GET /hotel/_search
{
  "query": {
    "match": {
      "title": "北京"
    }
  },
  "sort": [
    {
      "price": {
        "order": "desc"
      }
    }
  ]
}

        使用sort对搜索结果排序后,在每个文档的_source信息下面多出了一个sort信息,该信息中显示了当前文档排序字段的值。另外,文档的_score值和max_score都为null,这说明在默认情况下ES查询时使用sort对结果排序是不计算分数的。也可以使用sort对搜索结果按照多个字段进行排序。例如,用户可以按照价格进行降序排列,然后再按照口碑值进行降序排列,对应的DSL如下:

GET /hotel/_search
{
  "query": {
    "match": {
      "title": "北京"
    }
  },
  "sort": [
    {
      "price": {
        "order": "desc"
      },
      "praise": {
        "order": "desc"
      }
    }
  ]
}

在Java客户端中对搜索结果进行排序时,可以一次或者多次调用searchSourceBuilder.sort()方法添加一个或多个排序条件。对应上面的排序DSL,Java代码如下:

@Test
    public void testNormalFieldSort() throws IOException {
        RestHighLevelClient restHighLevelClient = new RestHighLevelClient(RestClient.builder(Arrays.stream("127.0.0.1:9200".split(","))
                .map(host->{
                    String[] split = host.split(":");
                    String hostName = split[0];
                    int port = Integer.parseInt(split[1]);
                    return new HttpHost(hostName,port,HttpHost.DEFAULT_SCHEME_NAME);
                }).filter(Objects::nonNull).toArray(HttpHost[]::new)));
        SearchRequest request = new SearchRequest("hotel");
        request.source(new SearchSourceBuilder().query(QueryBuilders.matchQuery("title","北京")).sort("price", SortOrder.DESC)
                .sort("praise",SortOrder.DESC));
        SearchResponse searchResponse = restHighLevelClient.search(request, RequestOptions.DEFAULT);
        for (SearchHit hit:searchResponse.getHits()) {
            System.out.println(hit.getSourceAsString()+"  "+hit.getScore());
        }
    }

按地理距离排序

        使用geo_distance查询,配合sort可以指定另一种排序规则,即按照文档坐标与指定坐标的距离对结果进行排序。使用时,需要在sort内部指定排序名称为geo_distanc,并指定目的地坐标。除了可以指定升序或者降序排列外,还可以指定排序结果中sort子句中的距离的计量单位,默认值为km即千米。在进行距离计算时,系统默认使用的算法为arc,该算法的特点是计算精准但是耗费时间较长,用户可以使用distance_type参数选择另一种计算速度快但经度略差的算法,名称为plane。如下示例使用geo_distance查询天安门5km范围内的旅馆,并按照距离由近及远进行排序:        

GET /hotel/_search
{
  "query": {
    "geo_distance":{
      "distance":"5km",
      "location":{
        "lat":39.915143,
        "lon":116.4039
      }
    }
  },
  "sort": [
    {
      "_geo_distance": {
        "location": {
          "lat":39.915143,
          "lon":116.4039
        },
        "order": "asc",
        "unit": "km",
        "distance_type": "plane"
      }
    }
  ]
}

        在Java客户端中对geo_distance的搜索结果进行排序时,可以调用SortBuilders.geo DistanceSort()方法新建geo_distance查询对象的实例,然后将该实例传给searchSource Builder.sort()方法即可完成按照距离排序的要求。对应上面的排序DSL,Java代码如下:

@Test
    public void testGeoSort() throws IOException {
        RestHighLevelClient restHighLevelClient = new RestHighLevelClient(RestClient.builder(Arrays.stream("127.0.0.1:9200".split(","))
                .map(host->{
                    String[] split = host.split(":");
                    String hostName = split[0];
                    int port = Integer.parseInt(split[1]);
                    return new HttpHost(hostName,port,HttpHost.DEFAULT_SCHEME_NAME);
                }).filter(Objects::nonNull).toArray(HttpHost[]::new)));
        SearchRequest request = new SearchRequest("hotel");
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder().query(QueryBuilders.geoDistanceQuery("location").distance(5, DistanceUnit.KILOMETERS)
                .point(39.915143, 116.4039));
        GeoDistanceSortBuilder geoDistanceSortBuilder = SortBuilders.geoDistanceSort("location", 39.915143, 116.4039).point(39.915143, 116.4039).unit(DistanceUnit.KILOMETERS);
        searchSourceBuilder.sort(geoDistanceSortBuilder);
        SearchResponse searchResponse = restHighLevelClient.search(request, RequestOptions.DEFAULT);
        for (SearchHit hit:searchResponse.getHits()) {
            System.out.println(hit.getSourceAsString()+"  "+hit.getScore());
        }
    }

在实际开发过程中,往往需要先写出符合需求的查询的DSL,然后对照DSL进行Java编码,最后再通过对比两方的结果是否一致来判定程序正确与否。