自己动手写分布式搜索引擎
上QQ阅读APP看书,第一时间看更新

3.3.15 关联内容(BlockJoinQuery)

一个简单的例子:假如有个网店卖衬衫,每件衬衫有一些常见的字段,如名称、描述、织物、价格等。对于每件衬衫,有许多单独的库存单元,它具有自己的列,如大小、颜色、库存数量等。库存单元叫作SKU。这些型号是你实际出售的,或在你的库存中,因为当有人买衬衫时,他们买的是特定的SKU产品(大小和颜色)。

也许你卖的是袋鼠牌短袖T恤,有下列SKU(大小,颜色):

● 小号,蓝色

● 小号,黑色

● 中号,黑色

● 大号,灰色

可能用户首先搜索“袋鼠 衬衫”,得到一个结果分支。然后下钻到一个特定的型号和颜色。产生这个查询:

        name:袋鼠AND size=小号AND color=蓝色

这应该匹配名称是“衬衫”列,而大小和颜色是SKU列的衬衫。

但是,如果用户下钻到一个小号的灰色衬衫:

        name:袋鼠AND size=小号AND color=灰色

这件衬衫查询不会返回任何结果,因为小号衬衫只有蓝色和黑色。

如何使用BlockJoinQuery运行这个查询?从把每件衬衫(父亲)和它所有的SKU(孩子)当作独立的文档索引开始。

使用新的IndexWriter.addDocuments API增加一件T恤和它所有的SKU作为一个独立的文档块。这个方法自动增加一个文档块成为一个有相邻的文档编号的独立的段。这是BlockJoinQuery起作用的基础。

也要加一个标志列到每个衬衫文档,例如,type = shirt。因为BlockJoinQuery需要一个Filter标识出父文档。

要在搜索阶段运行BlockJoinQuery,首先要创建一个父过滤器匹配T恤。注意,过滤器在底层必须使用FixedBitSet,就像CachingWrapperFilter:

        Filter shirts = new CachingWrapperFilter(
                          new QueryWrapperFilter(
                            new TermQuery(
                            new Term("type", "shirt"))));

一旦创建好这个过滤器,任何时间需要执行Join的时候,都可以重用这个过滤器。

然后每个查询都需要join,因为涉及SKU和衬衫列。从孩子查询开始,只匹配SKU字段:

        BooleanQuery skuQuery = new BooleanQuery();
        skuQuery.add(new TermQuery(new Term("size", "small")), Occur.MUST);
        skuQuery.add(new TermQuery(new Term("color", "blue")), Occur.MUST);

接下来,使用BlockJoinQuery转换命中结果,把它们从SKU文档空间上升到衬衫文档空间:

        BlockJoinQuery skuJoinQuery = new BlockJoinQuery(
            skuQuery,
            shirts,
            ScoreMode.None);

这里的ScoreMode enum类型决定应该如何为多个SKU命中结果打分。在这个查询中,不需要SKU匹配出来的分数,但是如果需要的话,可以用平均数、最大值或者求和的方法打分。

最后,可以使用skuJoinQuery作为子句,构建任意的衬衫查询:

        BooleanQuery query = new BooleanQuery();
        query.add(new TermQuery(new Term("name", "wolf")), Occur.MUST);
        query.add(skuJoinQuery, Occur.MUST);

这个连接是一对多(父亲对孩子)的内连接。