Collapse

在 Elasticsearch 中,collapse 参数用于根据特定字段对搜索结果进行去重折叠,仅返回每个字段值匹配的第一个文档(或按规则排序后的最优文档)。这一功能常用于排除重复数据或按分组展示结果,类似于 SQL 中的 DISTINCT。

一、Collapse 的核心作用

1.去重折叠根据指定字段(如 cat_id)折叠相同值的文档,确保结果中每个分组仅返回一个代表文档。

2.保留上下文支持通过 inner_hits 查看被折叠的其他文档。

二、基本语法

GET /index/_search
{
  "query": {
    "match_all": {}
  },
  "from":0,                        //折叠之后的文档的分页参数
  "size":10,                       //折叠之后的文档的分页参数
  "collapse": {
    "field": "field_name",         // 折叠依据的字段
    "inner_hits": {                // 可选:返回分组内其他文档
      "name": "most_recent",        // 自定义inner_hits名称
      "size": 5,                    // 返回每组最多文档数
      "sort": [{"field_name": "desc"}] // 分组内排序规则
      "_source": ["field_name"]        //子文档返回的字段
    },
    "max_concurrent_group_searches": 10 // 控制并发请求数(默认值:取决于分片数);
  }
}

三、参数详解

1. field

•必填参数,指定折叠依据的字段。

•字段类型必须为 keyword 或数值类型(如 long),不支持 text 类型。

原因分析:

collapse功能依赖字段的 精确值匹配,而text类型字段在索引时会经过 分词器(Analyzer) 处理,将原始文本拆分为多个词项(Term)。分词后的词项是独立存储的,原始完整值已丢失。所以collapse无法对text类型字段进行操作。

2. inner_hits

•可选参数,返回分组内被折叠的文档详情。

•常用子参数:

◦name: 自定义结果中 inner_hits 的名称(默认生成随机名称)。

◦size: 每组返回的文档数量(默认返回前 3 个)。

◦sort: 定义分组内文档的排序规则(默认与主查询排序一致)。

3. max_concurrent_group_searches

•当使用 inner_hits 返回折叠组内的其他文档时,Elasticsearch 会为每个分组执行独立的子查询。此参数通过限制并发查询数,避免因分组过多导致资源耗尽(如 CPU、内存、文件句柄等)。

•控制并发请求数,避免资源耗尽。

•默认值由分片数决定,分组较多时可适当调高。

四、排序策略

•主排序:决定折叠后返回的代表文档(默认按相关性得分排序)。

•分组内排序:通过 inner_hits.sort 自定义(如按时间倒排)。

示例:优先返回高评分用户的最新文章


{
  "query": {"match_all": {}},
  "sort": [{"score": "desc"}],       // 主排序:按评分降序,也可指定其他字段排序
  "collapse": {
    "field": "catId",
    "inner_hits": {
      "sort": [{"docUpdateTime": "desc"}]  // 分组内按时间排序
    }
  }
}

五、作用域

1. 对查询结果的作用域

•核心逻辑:

◦collapse 的作用域是 整个查询结果,即它会在主查询(query)匹配的所有文档中,根据指定字段进行分组去重。

◦示例:若查询匹配 1000 个文档,collapse 按 user_id 折叠后,最终返回的文档数是不同 user_id 的数量(如 100 个用户)。

•执行阶段:

◦查询阶段:collapse 在查询阶段生效,会优化分片级别的处理(如仅返回每个分片内的分组代表文档)。

◦协调节点阶段:协调节点汇总各分片结果后,再次进行全局折叠,确保最终结果的唯一性。

2. 对分页参数的作用域

•分页逻辑:

◦from 和 size 参数的作用域是 折叠后的结果集。

◦示例:若 size:10,表示从折叠后的结果中取前 10 个文档,而非原始查询结果的前 10 个。

3. 对排序的作用域

•主排序(sort):

◦sort 参数的作用域是 所有匹配文档,决定了每个分组的代表文档。

◦示例:若按 price:asc 排序,则每个分组中价格最低的文档会被选中。

◦默认排序:未指定 sort 时,按 _score(相关性得分)排序。

•分组内排序(inner_hits.sort):

◦仅影响 inner_hits 返回的被折叠文档顺序,不影响代表文档的选择。

4. 对聚合(Aggregations)的作用域

•默认行为:

◦聚合操作的作用域是 原始查询结果(未折叠的文档)。

◦示例:若按 category 折叠后统计每个分类的文档数,聚合结果会包含所有文档(包括被折叠的文档)。

组合查询

Elasticsearch组合查询有以下几种:

1.bool Query,布尔查询,可以组合多个过滤语句来过滤文档。

2.boosting Query,在 positive 块中指定匹配文档的语句,同时降低在 negative 块中也匹配的文档的得分,提供调整相关性算分的能力。

3.constant_score Query,包装了一个过滤器查询,不进行算分。

4.dis_max Query,返回匹配了一个或者多个查询语句的文档,但只将最佳匹配的评分作为相关性算分返回。

5.function_score Query,支持使用函数来修改查询返回的分数。

一、Bool Query

bool Query 使用一个或者多个布尔查询子句进行构建,每个子句都有一个类型,这些类型如下:

1.must,查询的内容必须在匹配的文档中出现,并且会进行相关性算分。

2.filter,查询的内容必须在匹配的文档中出现,但不像 must,filter 的相关性算分是会被忽略的。因为其子句会在 filter context 中执行,所以其相关性算分会被忽略,并且子句将被考虑用于缓存。

3.should,查询的内容应该在匹配的文档中出现,可以指定最小匹配的数量。

4.must_not,查询的内容不能在匹配的文档中出现。与 filter 一样其相关性算分也会被忽略。

查询其示例如下:

POST standard/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "term": {
            "businessNo": {
              "value": "JDV013049074367"
            }
          }
        },
        {
          "term": {
            "isDelete": {
              "value": "1"
            }
          }
        }
      ]
    }
  }
}

如上示例,使用了 must 子句来查询。must 子句中包含了两个 term query,分别对业务单号和是否删除进行查询。

除了使用 must 子句外,还可以使用 filter 子句和 should 子句来做实现。should 子句有一个 minimum_should_match 参数,可以指定最少匹配的查询数量或者百分比。其示例如下

POST standard/_search
{
  "query": {
    "bool": {
      "should": [
        {
          "term": {
            "businessNo": {
              "value": "JDV013049074367"
            }
          }
        },
        {
          "term": {
            "isDelete": {
              "value": "1"
            }
          }
        }
      ],
     "minimum_should_match": 2
    }
  }
}

如上示例,把子句改为 should,并且指定了 minimum_should_match 为 2,使得 should 子句中的查询必须命中两个或以上,这个文档才会被匹配。需要注意的是,当 Bool Query 包含至少一个 should 查询并且没有 must 、filter 的情况下,其值默认为 1,否则默认为 0

也可以使用 must 子句来查询业务单号,而使用 filter 子句来过滤是否删除,其示例如下:

POST standard/_search
{
"query":{
  
    "bool" : {
    "must" : [
      {
        "term" : {
          "businessNo" : {
            "value" : "JDV013049074367"
          }
        }
      }
    ],
    "filter" : [
      {
        "term" : {
          "isDelete" : {
            "value" : "1"
          }
        }
      }
    ]
    }
}
}

二、Boosting Query

boosting Query 可以指定两个块:positive 块和 negative 块。可以在 positive 块来指定匹配文档的语句,而在 negative 块中匹配的文档其相关性算分将会降低。相关性算分降低的程度将由 negative_boost 参数决定,其取值范围为:[0.0, 1.0]。

POST standard/_search
{
  "query": {
    "boosting": {
      "positive": {
        "term": {
          "sellerNo": {
            "value": "C190930001185"
          }
        }
      },
      "negative": {
        "term": {
          "buNo": {
            "value": "010K1298874"
          }
        }
      },
      "negative_boost": 0.5
    }
  }
}

如上示例,查询商家编码中含有 “C190930001185” 的数据,并且想让事业部编码为 “010K1298874” 的数据相关性降低一半。在 negative 块中匹配的文档,其相关性算分为:在 positive 中匹配时的算分 * negative_boost。

三、constant_score Query

constant_score Query包装 了一个过滤器查询,不进行算分。使用 Constant Score 可以将 query 转化为 filter,可以忽略相关性算分的环节,并且 filter 可以有效利用缓存,从而提高查询的性能。使用示例:

POST standard/_search
{
  "query": {
    "constant_score": {
      "filter": {
        "range": {
          "expDate": {
            "gte": 1730390400000,
            "lte": 1731168000000
          }
        }
      }
    }
  }
}

如上示例,过滤出了业务日期大于等于2024-11-01 并且小于 2024-11-10 的数据。



四、dis_max Query

disjunction max query 简称 dis_max,是分离最大化查询的意思。

•disjunction(分离)的含义是:表示把同一个文档中的每个字段上的查询都分开,分别进行算分操作。

•max(最大化): 是将多个字段查询的得分的最大值作为最终评分返回。

所以 disjunction max query 的效果是:将所有与任一查询匹配的文档作为结果返回,但是只将最佳匹配的得分作为查询的算分结果进行返回。

dis_max Query 的使用示例如下:

POST standard/_search
{
  "query": {
    "dis_max": {
      "queries": [
        {
          "term": {
            "sellerNo": {
              "value": "C190930001185"
            }
          }
        },
        {
          "term": {
            "buNo": {
              "value": "010K1298874"
            }
          }
        }
      ],
      "tie_breaker": 0.9
    }
  }
}

如上示例,查询商家编码为“C190930001185”或者事业部编码为”010K1298874“”,而最终返回的相关性评分将以匹配 “C190930001185” 或者匹配 “010K1298874” 中最大的那个评分为准。

在介绍 mutil-match 的时候也有一个 tie_breaker 参数。 当指定 “tie_breaker” 的时候,算分结果将会由以下算法来决定:

1.令算分最高的字段的得分为 s1

2.令其他匹配的字段的算分 * tie_breaker 的和为 s2

3.最终算分为:s1 + s2

“tie_breaker” 的取值范围为:[0.0, 1.0]。当其为 0.0 的时候,按照上述公式来计算,表示使用最佳匹配字段的得分作为相关性算分。当其为 1.0 的时候,表示所有字段的得分同等重要。当其在 0.0 到 1.0 之间的时候,代表其他字段的得分也需要参与到总得分的计算当中去。

五、function_score Query

function_score Query 允许你在查询结束以后去修改每一个匹配文档的相关性算分,所以使用算分函数可以改变或者替换原来的相关性算分结果。

function_score Query 提供了以下几种算分函数:

1.script_score:利用自定义脚本完全控制算分逻辑。

2.weight:为每一个文档设置一个简单且不会被规范化的权重。

3.random_score:为每个用户提供一个不同的随机算分,对结果进行排序。

4.field_value_factor:使用文档字段的值来影响算分,例如将好评数量这个字段作为考虑因数。

5.decay functions:衰减函数,以某个字段的值为标准,距离指定值越近,算分就越高。

六、总结

Bool Query 是使用的比较多的,Bool Query 提供了 must、filter、should、must_not 这几种类型来构建查询语句,其中 filter 与 must_not 的算分是会被忽略的。

Boosting Query 可以将部分匹配文档的得分进行降低,只需要在 negative 块中指定如何匹配文档即可,并且使用 negative_boost 参数来决定减小得分程度,其取值范围为:[0.0, 1.0]。

constant_score Query 可以将 query 转化为 filter,可以忽略相关性算分的环节,并且 filter 可以有效利用缓存,从而提高查询的性能。非常适合一些不需要算分的查询,例如精确值的查询、枚举量的查询等。

dis_max Query 是分离最大化查询的意思。其将所有与任一查询匹配的文档作为结果返回,但是只将最佳匹配的得分作为查询的算分结果进行返回。为了避免“一家独大”的情况,其他匹配的字段可以使用 “tie_breaker” 参数来进行“维权”。

function_score 是修改相关性算分的终极武器,它允许用户在查询结束以后去修改每一个匹配文档的相关性算分。

官方文档:https://www.elastic.co/guide/en/elasticsearch/reference/7.13/compound-queries.html