抛出问题
在 RDBMS 中,我们可以使用 GROUP BY 来对检索的数据进行分组,同样地,想要在 Lucene 中实现分组要如何做呢?首先思考如下几个问题
- Lucene 是如何实现分组的?
- 用来分组的字段(域)或者说 Field 如何添加?
- 组的大小如何设置?
- 组内大小如何设置?
- 如何实现组的分页?
- 如果结果集超过了组内大小,可以通过分页解决,那么如果结果集超过了组大小的上限,如何解决?
- 如何实现单类别分组,即类似SQL中的 GROUP BY A
- 如何实现多类别分组,即类似SQL中的 GROUP BY A, B
从 SQL 的 GROUP BY 说起
如果分组后面只有一个字段,如 GROUP BY A 意思是将所有具有相同A字段值的记录放到一个分组里。那么如果是GROUP BY A, B呢?其意思是将所有具有相同A字段值和B字段值的记录放到一个分组里,在这里A和B之间是逻辑与的关系。
通常的,如果在SQL中,我们仅用 GROUP BY 语句而不加 WHERE 条件的话,那么相当于在全部数据中进行分组,对应于 Lucene 中相当于使用 GROUP 加 new MatchAllDocsQuery() 的功能。
而如果在SQL中,我们不仅用 GROUP BY 还有 WHERE 条件语句,那么相当于在满足 WHERE 条件的记录中进行分组,这种 WHERE 条件在 Lucene 中可以通过构造各种不同的 Query 进行过滤,然后在符合条件的结果中分组。
Lucene 分组
有关Lucene分组问题,需要有一系列输入参数,官方Doc在此,核心点如下
- groupField:用来分组的域,在 Lucene 中,这个域只能设置一个,不像 SQL 中可以根据多个列分组。没有该域的文档将被分到一个单独的组里面
- groupSort:组间排序方式,用来指定如何对不同的分组进行排序,而不是组内的文档排序,默认值是
Sort.RELEVANCE
- topNGroups:保留多少组,例如10只取前十个分组
- groupOffset:指定组偏移量,比如当topNGroups的值是10的时候,groupOffset为3,则意思是返回7个分组,跳过前面3个,在分页时候很有用
- withinGroupSort:组内排序方式,默认值是
Sort.RELEVANCE
,注意和groupSort的区别,不要求和groupSort使用一样的排序方式 - maxDocsPerGroup:表示一个组内最多保留多少个文档
- withinGroupOffset:每组显示的文档的偏移量
分组通常有两个阶段,第一阶段用FirstPassGroupingCollector
收集不同的分组,第二阶段用SecondPassGroupingCollector
收集这些分组内的文档,如果分组很耗时,建议用CachingCollector
类,可以缓存 hits 并在第二阶段快速返回。这种方式让你相当于只运行了一次 query,但是付出的代价是用 RAM 持有所有的 hits。返回的结果集是TopGroups的实例。
Groups是由GroupSelector(抽象类)的实现来定义的,目前支持两种实现方式
- TermGroupSelector 基于 SortedDocValues 域进行分组
- ValueSourceGroupSelector 基于 ValueSource 值进行分组
通常不建议直接使用 FirstPassGroupingCollector 和 SecondPassGroupingCollector 来进行分组操作,因为Lucene提供了一个非常简便的封装类 GroupingSearch,目前分组操作还不支持 Sharding。
网上有许多讲解 Lucene 分组的文章,但是讲的都非常浅显,一般都是取 Top N 个分组,这个 N 是一个确定的值,试问如果我要对全部的结果集进行分组统计,而分组数量超过 Top N 的话,那么这种方式统计的结果显然是不准确的,因为它并没有统计全部的数据。还有的是直接把 maxDoc()
函数的值作为 groupLimit
的值,然后对某个分组内的全部文档进行迭代,无法实现组内分页的问题。
所以本文就针对这个问题,不仅解决了组内分页的问题,还解决了组间分页的问题,可以迭代完全的结果集。
另外一个需要注意的问题就是 maxDoc()
可能返回的是 Integer
型的上限,而将其直接作为 groupLimit 传入的话,是会报错的,错误如下
组内大小和组间大小如果设置为Integer.MAX_VALUE报
Exception in thread “main” java.lang.NegativeArraySizeException
组内大小和组间大小如果设置为Integer.MAX_VALUE-1报
Exception in thread “main” java.lang.IllegalArgumentException: maxSize must be <= 2147483630; got: 2147483646
完整示例如下
|
|
|
|
例如组的分页大小是2,组内分页大小是2,结果如下:
|
|
例如组的分页大小是1,组内分页大小是3,结果如下
|
|