solrのNested Docの詳細と応用
詳細
nested docは、solrが提供する親子ドキュメントのネスト構造ですが、luceneではすべてのドキュメントの格納がフラット構造であるため、ネストは論理的な言い方にすぎません.物理的な格納では、親子ネストはすべての関連する親子ドキュメントに基づいて緊密に配列され、子->子->親の順にソートされ、各ブロックは親が末尾になる必要があります.
nested doc構造のインデックスを追加するにはどうすればいいですか?
ダイレクトコード
solrクエリーでは、{!parent}のqueryParserクエリーメソッドを使用して、サブドキュメントのプロパティを使用して親ドキュメントを逆検索できます.
親子ドキュメントを同時に取得するには、
以上がnested docクエリーの基本的な応用ですが、最近の作業でnested docを使用した少し複雑な操作について説明します.
ビジネスニーズ
現在、顧客情報と店舗情報の一対の多関係のデータがあり、顧客情報には当該顧客の歴史が食べたすべての料理が含まれており、店舗情報は店舗の座標点であり、当該顧客が当該店舗で食べた料理は以上のデータに基づいており、業務側は2つの照会条件を通じて、要求を満たす顧客を獲得したいと考えている
:お客様は以前、特定の料理をいくつか食べたことがあります. :顧客が所定の座標点の近くにある距離範囲の店舗 に来たことがある.
ビジネスニーズに基づいて、上記のnested docの構造を使う必要があるのは明らかですが、どのようにして私たちが望んでいる結果を検索するかは、明らかに{!parent}のクエリー方式を参考にしなければならないので、まずこのqueryParserのソースコードを見てみましょう.
本当に役に立つのは戻ってきたBlockJoinParentQparserで、これのソースコードをもう一度見てみましょう.
つまりwhich後の文をparentFilter(下の赤い部分)、{}外の文をchildQuery(下の青い部分)と解析します
{!parent which="PARENT:true"}CHILD_PRICE:10
さらにToParentBlockJoinQueryの1つのQueryタイプにスペルし、具体的なクエリーではまずparentFilterで条件を満たす親ドキュメントに位置決めし、親子ドキュメントの物理的な位置に隣接する特性に基づいて、直接ポインタを上に移動し、そのサブドキュメントを遍歴し、childQueryに基づいて私たちが必要とするサブドキュメントをフィルタリングするので、私たちの上記の2つのニーズに基づいて、第1点をparentFilterにカプセル化し、第2点をchildQueryにカプセル化すればよい.ここでの詳細は詳しく言わず、結果1:{!terms f=sfield}A,B,CはABCの3つの料理のうちの1つを食べた顧客2:{!geofilt sfield=coordinate pt=X,Y d=10}は経緯度Xを満たすために使用される.Y範囲10 km以内の店舗ですが、そのまま次のような文につづると、解析時に問題が発生します.真ん中のスペースが区切り文字として区切られ、文の乱れが発生します
この問題を解決するために、自分でqueryParserを定義し、BlockJoinParentQparserPluginを継承し、その中のcreateParserメソッドを書き換えます.
すなわち,自分が解析時にパッチ値を入力すると,この問題を効果的に回避でき,クエリ時に後続のパッチが必要なパラメータだけを入力するだけでよい.
これで私たちのニーズが解決しました
Problem Solved!
nested docは、solrが提供する親子ドキュメントのネスト構造ですが、luceneではすべてのドキュメントの格納がフラット構造であるため、ネストは論理的な言い方にすぎません.物理的な格納では、親子ネストはすべての関連する親子ドキュメントに基づいて緊密に配列され、子->子->親の順にソートされ、各ブロックは親が末尾になる必要があります.
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を使用した少し複雑な操作について説明します.
ビジネスニーズ
現在、顧客情報と店舗情報の一対の多関係のデータがあり、顧客情報には当該顧客の歴史が食べたすべての料理が含まれており、店舗情報は店舗の座標点であり、当該顧客が当該店舗で食べた料理は以上のデータに基づいており、業務側は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の1つのQueryタイプにスペルし、具体的なクエリーではまずparentFilterで条件を満たす親ドキュメントに位置決めし、親子ドキュメントの物理的な位置に隣接する特性に基づいて、直接ポインタを上に移動し、そのサブドキュメントを遍歴し、childQueryに基づいて私たちが必要とするサブドキュメントをフィルタリングするので、私たちの上記の2つのニーズに基づいて、第1点をparentFilterにカプセル化し、第2点をchildQueryにカプセル化すればよい.ここでの詳細は詳しく言わず、結果1:{!terms f=sfield}A,B,CはABCの3つの料理のうちの1つを食べた顧客2:{!geofilt sfield=coordinate pt=X,Y d=10}は経緯度Xを満たすために使用される.Y範囲10 km以内の店舗ですが、そのまま次のような文につづると、解析時に問題が発生します.真ん中のスペースが区切り文字として区切られ、文の乱れが発生します
{!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!