• ES自定义评分查询——ES function_score


    在很多复杂的业务场景下,排序的规则会比较复杂,单一的降序,升序无法满足日常需求。不过 ES 中提供了给文档加权重的方式来排序。

    首先初始化三条测试数据,方便查看效果(type:1 为翻译,2 为转载,3 为原创):

    POST /language_index/_doc/1
    {
        "id": 1,
        "title": "Java怎么学",
        "type": 3,
        "userId": 1,
        "tags": [
            "java"
        ],
        "textContent": "我要学Java",
        "status": 1,
        "heat": 80
    }
    
    POST /language_index/_doc/2
    {
        "id": 2,
        "title": "Mysql怎么学",
        "type": 2,
        "userId": 1,
        "tags": [
            "mysql"
        ],
        "textContent": "我要学Mysql",
        "status": 1,
        "heat": 99
    }
    
    POST /language_index/_doc/3
    {
        "id": 3,
        "title": "Springboot怎么学",
        "type": 1,
        "userId": 1,
        "tags": [
            "springboot"
        ],
        "textContent": "我要学Springboot",
        "status": 1,
        "heat": 100
    }


    业务需求:
    查询 userId=1 的所有文章,按照热度(heat)降序排序,但是原创类型的文章(type=3)要显示在前面,优先级高于热度。

    如果我们简单的按照热度排序的话,那么顺序肯定是 id 为 3(热度:100),2(热度:99),1(热度:80)这样排列的。

    但是原创类型的要在前面,那么结果应该是 1(热度:80,类型:原创),3(热度:100,类型:翻译),2(热度:99,类型:转载)。

    排序条件是以热度来进行的,这个是肯定的。唯一需要处理的就是怎么将原创类型的排在前面,如果只考虑实现,方式还是有很多种的。

    比如:原创类型重新弄一个字段,只用于排序,设置原创类型字段值高于其他类型的字段值,且其他类型的字段值固定,这样排序就简单了,根据新字段值和热度进行排序就可以实现效果。

    1、weightFactorFunction
    在 ES 搜索结果中_score 这个字段相信大家并不陌生,这是 ES 给出的评分,我们可以根据评分来排序,然后将原创类型的评分提高就可以实现想要的效果。

    通过 FunctionScoreQueryBuilder 来构建查询。

        /**
         * 通过Weight的方式来实现权重<br/><br/>
         * 
         * 业务需求:<br/>
         * 查询 userId=1 的所有文章,按照热度(heat)降序排序,但是原创类型的文章(type=3)要显示在前面,优先级高于热度。
         * @throws IOException
         */
        @Test
        public void testWeightFactorFunction() throws IOException {
            BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
            // 查询 userId=1 的所有文章
            boolQuery.must(QueryBuilders.termQuery("userId", 1));
            
            // type:1 为翻译,2 为转载,3 为原创
            // 原创类型的文章(type=3)权重设置为100,其他类型的文章权重设置为1
            FunctionScoreQueryBuilder.FilterFunctionBuilder[] filterFunctionBuilders = new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{
                    new FunctionScoreQueryBuilder.FilterFunctionBuilder(QueryBuilders.termQuery("type", 3), ScoreFunctionBuilders.weightFactorFunction(100)),
                    new FunctionScoreQueryBuilder.FilterFunctionBuilder(QueryBuilders.termQuery("type", 2), ScoreFunctionBuilders.weightFactorFunction(1)),
                    new FunctionScoreQueryBuilder.FilterFunctionBuilder(QueryBuilders.matchQuery("type", 1), ScoreFunctionBuilders.weightFactorFunction(1))
            };
            
            SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
            FunctionScoreQueryBuilder functionScoreQueryBuilder = QueryBuilders.functionScoreQuery(boolQuery, filterFunctionBuilders);
            searchSourceBuilder.query(functionScoreQueryBuilder)
                    .sort("_score", SortOrder.DESC)
                    .sort("heat", SortOrder.DESC)
                    ;
            
            SearchRequest searchRequest = new SearchRequest("language_index");
            searchRequest.source(searchSourceBuilder);
            SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT);
            handleSimpleResponse(response);
        }
    
        /**
         * 返回结果解析
         * 
         * @param response
         */
        private void handleSimpleResponse(SearchResponse response) {
            SearchHits searchHits = response.getHits();
            // 4.1.总条数
            long total = searchHits.getTotalHits().value;
            System.out.println("总条数:" + total);
            //System.out.println("response:" + response);
            // 4.2.获取文档数组
            SearchHit[] hits = searchHits.getHits();
            // 4.3.遍历
            for (SearchHit hit : hits) {
                // 4.4.获取source
                String json = hit.getSourceAsString();
                // 4.5.打印
                System.out.println(json);
            }
        }

    通过 ScoreFunctionBuilders.weightFactorFunction 为文章类型设置对应的权重,原创文章权重为 100,其他的都为 1,这样原创文章的得分就高于其他类型的文章。

    在排序的时候优先得分排序,然后热度排序。就可以得到我们想要的结果了。

    2、scriptFunction
    除了使用 weightFactorFunction 来设置权重,另外介绍一种灵活度更高,适用于更复杂的排序场景的方式 scriptFunction。

    scriptFunction 允许我们通过脚本的方式来实现权重,直接看代码:

        /**
         * 通过脚本的方式来实现权重(groovy脚本)
         * 
         * @throws IOException
         */
        @Test
        public void testScriptFunction() throws IOException {
            BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
            // 查询 userId=1 的所有文章
            boolQuery.must(QueryBuilders.termQuery("userId", 1));
            
            // type:1 为翻译,2 为转载,3 为原创
            // 原创类型的文章(type=3)权重设置为100,其他类型的文章权重设置为1
            StringBuffer scoreScript = new StringBuffer();
            //scoreScript.append("if(doc['type'].value == 2) {");
            //scoreScript.append("if(doc['type'].value == 3 || doc['type'].value == 2) {");
            scoreScript.append("if(doc['id'].value == 2) {");
            scoreScript.append("   return 100;");
            scoreScript.append("} else {");
            scoreScript.append("   return 1;");
            scoreScript.append("}");
            
            FunctionScoreQueryBuilder.FilterFunctionBuilder[] filterFunctionBuilders = new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{
                    new FunctionScoreQueryBuilder.FilterFunctionBuilder(QueryBuilders.matchAllQuery(), ScoreFunctionBuilders.scriptFunction(new Script(scoreScript.toString())))
            };
            
            SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
            FunctionScoreQueryBuilder functionScoreQueryBuilder = QueryBuilders.functionScoreQuery(boolQuery, filterFunctionBuilders);
            searchSourceBuilder.query(functionScoreQueryBuilder)
                               .sort("_score", SortOrder.DESC)
                               .sort("heat", SortOrder.DESC)
            ;
            
            SearchRequest searchRequest = new SearchRequest("language_index");
            searchRequest.source(searchSourceBuilder);
            SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT);
            handleSimpleResponse(response);
        }
    
    
        /**
         * 返回结果解析
         * 
         * @param response
         */
        private void handleSimpleResponse(SearchResponse response) {
            SearchHits searchHits = response.getHits();
            // 4.1.总条数
            long total = searchHits.getTotalHits().value;
            System.out.println("总条数:" + total);
            //System.out.println("response:" + response);
            // 4.2.获取文档数组
            SearchHit[] hits = searchHits.getHits();
            // 4.3.遍历
            for (SearchHit hit : hits) {
                // 4.4.获取source
                String json = hit.getSourceAsString();
                // 4.5.打印
                System.out.println(json);
            }
        }

    scoreScript 就是控制权重的脚本,也就是一段代码(脚本默认是 groovy),是不是方便的多。

  • 相关阅读:
    麒麟短线王实战技法
    Silverlight的资源
    Windows Live SkyDrive, Windows Live Sync 和 Live Mesh
    Listview.Subitem.BackColor.ForeColor改变字体颜色和背景
    windows mobile控制面板程序
    Windows Azure百度百科
    wcf中如何Host多个WCF服务?
    强弱跟踪
    修改默认的HTTP Response Header
    DataTable 内部索引已损坏
  • 原文地址:https://www.cnblogs.com/linjiqin/p/16598066.html
Copyright © 2020-2023  润新知