Lucene 高阶查询的六脉神剑 —— QueryParser

本文深入探讨了如何使用 Lucene 的 QueryParser 将多种查询模式结合,通过文本字符串创建复杂的查询。内容涵盖关键词查询、组合查询、范围查询、模糊查询、短语查询以及字段加权,讲解了 QueryParser 的语法和使用技巧,例如如何利用通配符、布尔运算符以及字段限定符来构造高效查询表达式。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

上篇我们介绍了 Lucene 多样的查询模式,每一种都是相互独立的用来解决特定查询目标的 Query 对象。本节我们要将这些查询模式使用 QueryParser 组合起来进行合并查询 —— 用一个文本字符串表达式来表示所有的查询模式。

QueryParser 的基本作用是将一个满足特定语法的字符串转换成相应的查询对象。我们可以不再需要使用组合对象的方式来手动构造复合逻辑查询,而是通过一个单行字符串就可以完成原先需要数行代码才能完成的查询功能。

关键词查询 TermQuery 与组合查询 BooleanQuery

关键词查询是最简单的查询,在 QueryParser 的语法里它就是一个「无空格」普通的字符串。

var analyser = new HanLPAnalyzer();
var parser = new QueryParser("content", analyser);
var query = parser.parse("北京大学");
System.out.println(query);
var hits = searcher.search(query, 10).scoreDocs;

--------------
content:北京大学

var query = parser.parse("北京 大学");
System.out.println(query);

------------
content:北京 content:大学

System.out.println(query.getClass());

------------
class org.apache.lucene.search.BooleanQuery

var query = parser.parse("+北京 +大学");

var query = parser.parse("+北京 -大学")

注意 QueryParser 会使用传递进去的 analyzer 对象对字符串进行分词,最开始例子的「北京大学」解析后之所以是单个字符串,那是因为「北京大学」本身就是一个完整的原子词汇。如果我们试试「北京林业大学」就会完全是不一样的结果,它是三个词汇的或运算。

var query = parser.parse("北京林业大学");

-----------------
content:北京 content:林业 content:大学

Lucence 对于整数范围和字符串范围是不一样的处理方式,它有 NumericRangeQuery 和 TermRangeQuery,但是默认的 QueryParser 表达式仅支持 TermRangeQuery,使用下面的范围表达式解析出来的结果将会是 TermRangeQuery。如果对整形的 article_id 字段进行字符串的范围查询,那么结果将会是空集。

var parser = new QueryParser("article_id", analyser);
var query = parser.parse("[400000 TO 450000]");
System.out.println(query);
System.out.println(query.getClass());

---------------
article_id:[400000 TO 450000]
class org.apache.lucene.search.TermRangeQuery

static class MyQueryParser extends QueryParser {

    public MyQueryParser(String f, Analyzer a) {
        super(f, a);
    }

    @Override
    protected Query getRangeQuery(String field, String low, String high, boolean startInclusive, boolean endInclusive) throws ParseException {
        if(this.field.equals(field)) {
            return IntPoint.newRangeQuery(field, Integer.parseInt(low), Integer.parseInt(high));
        }
        return super.getRangeQuery(field, low, high, startInclusive, endInclusive);
    }
}

var analyser = new HanLPAnalyzer();
var parser = new MyQueryParser("article_id", analyser);
var query = parser.parse("[400000 TO 450000]");
System.out.println(query);
System.out.println(query.getClass());
var hits = searcher.search(query, 10).scoreDocs;
System.out.println(hits.length);

--------------
article_id:[400000 TO 450000]
class org.apache.lucene.document.IntPoint$1
10

这两个查询都是有效利用了关键词树 FST 的前缀属性来扫描出匹配的关键词集合。PrefixQuery 可以理解为 WildcardQuery 的子集。通配符查询的 QueryParser 语法比较简单,还是使用 * 号和 ? 号。

var parser = new QueryParser("content", analyser);
var query = parser.parse("北京*");
System.out.println(query);
System.out.println(query.getClass());

--------
content:北京*
class org.apache.lucene.search.PrefixQuery

640?wx_fmt=png
图片

关于首字符带 * 号,QueryParser 还有一个例外情况,它内置了一个非常特殊的通配符 : ,它表示匹配所有的内容,也就是全文遍历 MatchAllDocsQuery。

var parser = new QueryParser("content", analyser);
var query = parser.parse("*:*");
System.out.println(query);
System.out.println(query.getClass());

------------
*:*
class org.apache.lucene.search.MatchAllDocsQuery

QueryParser 使用双引号来表示短语查询,默认的 slop 是零。

var parser = new QueryParser("content", analyser);
var query = parser.parse("\"动物世界\"");
System.out.println(query);
System.out.println(query.getClass());

-------------
content:"动物 世界"
class org.apache.lucene.search.PhraseQuery

var parser = new QueryParser("content", analyser);
var query = parser.parse("\"动物世界\"");
System.out.println(query);
System.out.println(query.getClass());

------------
content:北京大学
class org.apache.lucene.search.TermQuery

var query = parser.parse("\"动物世界\"~5");

----------
content:"动物 世界"~5

模糊查询也使用波浪号,但是不需要双引号了,如果希望设定模糊相似度(模糊因子),可以在波浪号后面增加一个浮点数,默认的模糊因子我也不知道是多少,但是我从代码中了解到默认的编辑距离是 2。如果设置了模糊因子 factor,编辑距离将会是 min((1-factor)*Length, 2),无论如何编辑距离也不会超过 2。

var parser = new QueryParser("title", analyser);
var query = parser.parse("动物世界~0.5");
System.out.println(query);
System.out.println(query.getClass());

-----------
title:动物世界~2
class org.apache.lucene.search.FuzzyQuery

为了支持更加复杂的组合逻辑,ParseQuery 提供了括号、AND、OR、NOT 这样的组合操作符,适当使用它们可以使得查询表达式逻辑更加清晰。

var parser = new QueryParser("content", analyser);
var query = parser.parse("(北京* OR 清华) AND 大学");
System.out.println(query);

-------------
+(title:北京* title:清华) +title:大学

var analyser = new HanLPAnalyzer();
var parser = new QueryParser("content", analyser);
var query = parser.parse("(北京* OR 清华) AND NOT 小学");
System.out.println(query);

-----------
+(content:北京 content:清华) -content:小学

我们在构造 QueryParser 时会传入一个目标字段名称,其实这个字段名称只是默认字段名称,如果表达式中没有指定字段名称,那么就会使用这个默认字段名称。如果不想使用默认字段名称,可以在表达式中使用字段限定符。

var parser = new QueryParser("content", analyser);
var query = parser.parse("(title:北京* OR title:清华) AND 大学");
System.out.println(query);

----------
+(title:北京* title:清华) +content:大学

字段加权

如果我希望在标题出现「北京大学」或者内容出现「北京大学」都可以,但是希望标题中出现的评分排序要靠前,那么可以对个别字段进行加权。下面我们来对比一下加权前后的效果

var parser = new QueryParser("content", analyser);
var query = parser.parse("title:科幻 OR 科幻");

var parser = new QueryParser("content", analyser);
var query = parser.parse("title:科幻^5.0 OR 科幻");

640?wx_fmt=png
图片

再看加权后

640?wx_fmt=png
图片

很明显评分显著发生了放大,还有一个很重要的改变就是标题中没有「科幻」的文章从前十中消失了。在文章搜索中,加权是一个必不可少的功能,但是究竟加权多大的值这又是另外一个我们暂时不好回答的问题,随着我们对搜索技术了解的逐步深入,在不久的未来也许我们会有答案。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值