0

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

  • doc1 = [类型:汽车,售出:假,所有者:约翰]
  • doc2 = [类型:自行车,产品 ID:1,所有者:布赖恩]
  • doc3 = [类型:汽车,已售:真实,所有者:迈克]
  • doc4 = [类型:自行车,productID:2,所有者:Josh]
  • doc5 = [类型:汽车,售出:假,所有者:约翰]

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

  1. 给我所有类型的文件:Car which has sold:false only,如果它是与 Car 不同的类型,则包括在结果中。所以基本上我想要文档 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. 过滤所有属于 (type:Car and sold:false) 或 (type:Bike and productID:1) 的文档。所以为此我会得到1,2,5。

  3. 获取类型为:Car 的所有文件,然后仅使用已售出:false,否则从所有者 John、Brian、Josh 那里获取文件。所以对于这个查询,我应该得到 1、2、4、5。

注意:您不知道文档中的所有类型。由于文档数量少,这里很明显。

所以我的解决方案是:

  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. ((owner:John) OR (owner:Brian) OR (owner:Josh)) AND ((-type:Car) OR ((type:Car) AND (sold:false))。这行不通,我可以如果我这样做,它会起作用: ((owner:John) OR (owner:Brian) OR (owner:Josh)) AND (( version :* OR (-type:Car)) OR ((type:Car) AND (sold:false)). 我不明白这是如何工作的,因为从逻辑上讲它应该工作,但 Solr/Lucene 不知何故做了一些事情。
4

2 回答 2

1

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

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

为了解决这个问题,您需要用一组额外的括号将否定括起来,以确保 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)

于 2013-07-17T20:51:07.227 回答
0

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

您要做的是使用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 调用一次。第二个参数指定查询的严格程度。它可以指定子查询MUST出现、SHOULD出现或应该NOT出现(这是BooleanQuery.Occur枚举的三个值)。

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

QueryWrapperFilter qwf = new QueryWrapperFilter(bq);

那应该这样做。然后,如果您只想对该过滤器允许通过的文档运行查询,您只需获取新查询(调用它q)和过滤器,并创建一个 FilteredQuery:

FilteredQuery fq = new FilteredQuery(q, qwf);
于 2013-07-17T20:44:01.657 回答