Lucene Solr使用复杂的过滤器

时间:2013-07-17 20:08:07

标签: java solr lucene boolean-logic boolean-operations

我目前在为Lucene / Solr指定过滤器时遇到问题。我提出的每个解决方案都打破了其他解决方案让我先举一个例子。假设我们有以下5个文件:

  • doc1 = [type:Car,sold:false,owner:John]
  • doc2 = [type:Bike,productID:1,owner:Brian]
  • doc3 = [type:Car,sold:true,owner:Mike]
  • doc4 = [type:Bike,productID:2,owner:Josh]
  • doc5 = [type:Car,sold:false,owner:John]

所以我需要构建以下过滤器查询:

  1. 给我所有类型的文件:已售出的汽车:仅假,如果是与汽车不同的类型,则包含在结果中。所以基本上我想要文档1,2,4,5,我不想要的唯一文件是doc3,因为它已售出:true。更准确地说:

    for each document d in solr/lucene
    if d.type == Car {
        if d.sold == false, then add to result
        else ignore
    }
    else {
        add to result
    }
    return result
    
  2. 过滤所有(类型:Car和sold:false)或(类型:Bike和productID:1)的文档。因此,我将获得1,2,5。

  3. 获取所有文件,如果类型:Car然后只获得售卖:false,否则从我所有者John,Brian,Josh那里得到文件。所以对于这个查询,我应该得到1,2,4,5。
  4. 注意:您不知道文档中的所有类型。这显然是因为文件数量很少。

    所以我的解决方案是:

    1. ( - type:Car)OR((type:Car)AND(sold:false)。这样可以正常工作。
    2. (( - type:Car)OR((type:Car)AND(sold:false))AND((-type:Bike)OR((type:Bike)AND(productID:1)))。这个解决方案不起作用。
    3. ((所有者:约翰)或(所有者:Brian)或(所有者:Josh))和(( - 类型:汽车)或((类型:汽车)和(卖出:假))。这不起作用,我能做到这一点,如果我这样做:((所有者:约翰)或(所有者:布莱恩)或(所有者:乔希))和((版本:*或( - 类型:汽车) ))OR((类型:汽车)和(卖出:假))。我不明白这是如何工作的,因为逻辑上它应该有效,但Solr / Lucene不知怎的做了什么。

2 个答案:

答案 0 :(得分:1)

好的,除了售出的汽车外,你可以使用-(type:Car sold:true)

这可以合并到其他查询中,但是你需要小心这样的孤独的否定查询。一般来说,Lucene不能很好地处理它们,Solr也有一些奇怪的陷阱。特别是,A -B更像是“得到所有A但禁止B”,而不是“获得所有A和除B以外的任何东西”。与A or -B类似的问题,请参阅this question了解更多信息。

为了解决这个问题,您需要使用一组额外的括号来包围否定,以确保Solr将其理解为独立的否定查询,例如:(-(type:Car AND sold:true))

所以:

  1. -(type:Car AND sold:true)(这不会得到您所说的结果,但根据我的评论,我并不理解您的陈述结果)

  2. (type:Bike AND productID:1) (-(type:Car AND sold:true))(你实际上是在问题描述中写的!)

  3. (-(type:Car AND sold:false)) owner:(John Brian Josh)

答案 1 :(得分:0)

我的建议是使用程序化Lucene(即使用Java Lucene API直接在Java中)而不是发出将被解释的文本查询。这将为您提供更细粒度的控制。

您要做的是使用QueryWrapperFilter API构建Lucene过滤器对象。 QueryWrapperFilter是一个过滤器,它接受Lucene查询,并筛选出与该查询不匹配的任何文档。

为了使用QueryWrapperFilter,您需要构建一个与您感兴趣的术语相匹配的查询。最好的方法是使用TermQuery

TermQuery tq = new TermQuery(new Term("fieldname", "value"));

正如您可能已经猜到的那样,您需要将“fieldname”替换为字段名称,将“value”替换为所需值。例如,根据OP中的示例,您可能希望执行new Term("type", "Car")

之类的操作

这只匹配一个词。您将需要多个TermQueries,以及一种将它们组合在一起以创建单个更大查询的方法。最好的方法是使用BooleanQuery

BooleanQuery bq = new BooleanQuery();
bq.add(tq, BooleanQuery.Occur.MUST);

您可以根据需要多次调用bq.add - 对于您拥有的每个TermQuery一次。第二个参数指定查询的严格程度。它可以指定显示子查询MUSTSHOULD出现,或NOT出现(这些是BooleanQuery.Occur枚举的三个值)。

添加完每个子查询后,此BooleanQuery表示完整查询,该查询仅匹配您要求的文档。但是,它仍然不是过滤器。我们现在需要将它提供给QueryWrapperFilter,它将返回一个过滤器对象:

QueryWrapperFilter qwf = new QueryWrapperFilter(bq);

应该这样做。然后,如果您只想对该过滤器允许的文档运行查询,只需使用新查询(称之为q)和过滤器,然后创建FilteredQuery:

FilteredQuery fq = new FilteredQuery(q, qwf);