solr的Nested Doc的详解及应用
程序员文章站
2024-02-02 08:35:34
...
nested doc,是solr提供的一种父子文档嵌套的结构,但是由于在lucene中,所有文档的存储都是扁平结构的,所以嵌套只是逻辑上的说法,在物理存储中,父子嵌套是根据所有相关联的父子文档紧密排列,并且按照 子->子->父 的顺序排序,每个区块都必须父作为结尾。
如何添加nested doc结构的索引?
直接上代码
则在solr查询时,我们可以利用{!parent}的queryParser查询方法,来通过子文档的属性来反查父文档,
若想同时得到父子文档,则设置
以上就是nested doc查询的基本应用,下面介绍一下在近期工作中用到nested doc的一些稍微复杂一点的操作。
业务需求
目前有一批顾客信息和商店信息的一对多关系的数据,顾客信息包括该顾客历史吃过的所有的菜,商店信息为商店的坐标点以及该顾客在该商店中的吃过的菜
基于以上数据,业务方想要通过两个查询条件,得到满足要求的顾客
基于业务需求,很明显需要用到上面介绍的nested doc的结构,
但是如何查询得到我们想要的结果呢,很明显要借鉴{!parent}的查询方式
所以先看一下这个queryParser的源代码
发现真正起作用的是返回的BlockJoinParentQParser,再看一下这个的源代码
也就是将which后的语句解析为parentFilter(下方红色部分),将{}外的语句解析为childQuery(下方蓝色部分)
{!parent which="PARENT:true"}CHILD_PRICE:10
再拼装成ToParentBlockJoinQuery的一个Query类型,具体查询时先用parentFilter定位到满足条件的父文档,再根据父子文档物理位置相邻的特性,直接向上移动指针,遍历其子文档,再根据childQuery筛选我们需要的子文档
所以根据我们上述的两个需求,将第一点封装成parentFilter,将第二点封装成childQuery即可,这里的细节不细说了,直接说结果
1:{!terms f=sfield}A,B,C 用于获取吃过ABC三个菜其中之一的客户
2:{!geofilt sfield=coordinate pt=X,Y d=10} 用于获取满足经纬度X,Y范围10km内的店铺
但是如果直接拼装成如下的语句,在解析时会出现问题,因为中间的空格都会被当做分隔符分开,造成语句的错乱
为了解决这个问题,自己定义一个queryParser,继承BlockJoinParentQParserPlugin,改写其中的createParser方法,
即自己在解析时传入拼好的值,则能有效的避免这个问题,在查询时也就只用传入我们需要后续拼接的参数即可
这样就解决了我们的需求
Problem Solved!
如何添加nested doc结构的索引?
直接上代码
SolrInputDocument doc = new SolrInputDocument(); doc.setField("id",1); doc.setField("author","D'angelo"); doc.setField("type","p"); SolrInputDocument c = new SolrInputDocument(); c.setField("id",2); c.setField("title","solr in action"); c.setField("comments","bravo"); c.setField("type","c"); //**将c添加为doc的子文档*/ doc.addChildDocument(c); SolrInputDocument doc2 = new SolrInputDocument(); doc2.setField("id",3); doc2.setField("author","Russell"); doc2.setField("type","p"); SolrInputDocument c2 = new SolrInputDocument(); c2.setField("id",4); c2.setField("title","java in action"); c2.setField("comments","good"); c2.setField("type","c"); doc2.addChildDocument(c2);
则在solr查询时,我们可以利用{!parent}的queryParser查询方法,来通过子文档的属性来反查父文档,
/**Usage: {!parent which="PARENT:true"}CHILD_PRICE:10*/ query: {!parent which=type:p}title:"java in action" "response": { "numFound": 1, "start": 0, "docs": [ { "id": "3", "author": "Russell", "author_s": "Russell", "type": "p", "_version_": 1601161242996637700, "_root_": "3", "_childDocuments_": [ { "id": "4", "title": "java in action", "comments": "good", "type": "c", "_root_": "3" } ] } ] }
若想同时得到父子文档,则设置
fl:*,[child parentFilter=type:p]
以上就是nested doc查询的基本应用,下面介绍一下在近期工作中用到nested doc的一些稍微复杂一点的操作。
业务需求
目前有一批顾客信息和商店信息的一对多关系的数据,顾客信息包括该顾客历史吃过的所有的菜,商店信息为商店的坐标点以及该顾客在该商店中的吃过的菜
基于以上数据,业务方想要通过两个查询条件,得到满足要求的顾客
- 1:顾客以前吃过某几个特定的菜
- 2:顾客曾光临过给定坐标点的附近某个距离范围内的店铺
基于业务需求,很明显需要用到上面介绍的nested doc的结构,
但是如何查询得到我们想要的结果呢,很明显要借鉴{!parent}的查询方式
所以先看一下这个queryParser的源代码
public class BlockJoinParentQParserPlugin extends QParserPlugin { public static final String NAME = "parent"; @Override public QParser createParser(String qstr, SolrParams localParams, SolrParams params, SolrQueryRequest req) { QParser parser = createBJQParser(qstr, localParams, params, req); return parser; } protected QParser createBJQParser(String qstr, SolrParams localParams, SolrParams params, SolrQueryRequest req) { return new BlockJoinParentQParser(qstr, localParams, params, req); } }
发现真正起作用的是返回的BlockJoinParentQParser,再看一下这个的源代码
protected String getParentFilterLocalParamName() { return "which"; } BlockJoinParentQParser(String qstr, SolrParams localParams, SolrParams params, SolrQueryRequest req) { super(qstr, localParams, params, req); } public Query parse() throws SyntaxError { String filter = this.localParams.get(this.getParentFilterLocalParamName()); String scoreMode = this.localParams.get("score", ScoreMode.None.name()); QParser parentParser = this.subQuery(filter, (String)null); Query parentQ = parentParser.getQuery(); String queryText = this.localParams.get("v"); if(queryText != null && queryText.length() != 0) { QParser childrenParser = this.subQuery(queryText, (String)null); Query childrenQuery = childrenParser.getQuery(); return this.createQuery(parentQ, childrenQuery, scoreMode); } else { SolrConstantScoreQuery wrapped = new SolrConstantScoreQuery(this.getFilter(parentQ)); wrapped.setCache(false); return wrapped; } } protected Query createQuery(Query parentList, Query query, String scoreMode) throws SyntaxError { //AllParentsAware extends ToParentBlockJoinQuery return new BlockJoinParentQParser.AllParentsAware(query, this.getFilter(parentList).filter, ScoreModeParser.parse(scoreMode), parentList); }
也就是将which后的语句解析为parentFilter(下方红色部分),将{}外的语句解析为childQuery(下方蓝色部分)
{!parent which="PARENT:true"}CHILD_PRICE:10
再拼装成ToParentBlockJoinQuery的一个Query类型,具体查询时先用parentFilter定位到满足条件的父文档,再根据父子文档物理位置相邻的特性,直接向上移动指针,遍历其子文档,再根据childQuery筛选我们需要的子文档
所以根据我们上述的两个需求,将第一点封装成parentFilter,将第二点封装成childQuery即可,这里的细节不细说了,直接说结果
1:{!terms f=sfield}A,B,C 用于获取吃过ABC三个菜其中之一的客户
2:{!geofilt sfield=coordinate pt=X,Y d=10} 用于获取满足经纬度X,Y范围10km内的店铺
但是如果直接拼装成如下的语句,在解析时会出现问题,因为中间的空格都会被当做分隔符分开,造成语句的错乱
{!parent which={!terms f=sfield}A,B,C}{!geofilt sfield=coordinate pt=X,Y d=10}
为了解决这个问题,自己定义一个queryParser,继承BlockJoinParentQParserPlugin,改写其中的createParser方法,
//UserRecommendParentQPlugin extends BlockJoinParentQParserPlugin @Override public QParser createParser(String qstr, SolrParams localParams, SolrParams params, SolrQueryRequest req) { String sfield = localParams.get("sfield","coordinate"); String pt = localParams.get("pt", "0.0,0.0"); String d = localParams.get("d", "100"); String terms = localParams.get("termList",""); String parentQ = "{!terms f=termcount}"+term String q = "{!geofilt sfield="+sfield+" pt="+ pt+" d="+d+"}"; logger.info("child query: "+ q); if(localParams instanceof ModifiableSolrParams){ ((ModifiableSolrParams) localParams).set("which","type:p"); ((ModifiableSolrParams) localParams).set("v",q); } QParser parser = createBJQParser(q, localParams, params, req); return parser; }
即自己在解析时传入拼好的值,则能有效的避免这个问题,在查询时也就只用传入我们需要后续拼接的参数即可
{!userRecommend sfield=xxx pt=xxx d=xxx termList=xxx }userId:123
这样就解决了我们的需求
Problem Solved!
上一篇: 下载就上51cto