假設模型:
{
"group" : "fans",
"name": "Anne",
"user" : [
{
"first" : "John",
"last" : "Smith"
},
{
"first" : "Alice",
"last" : "White"
}
]
}
由于用戶是“嵌套”型別,我想使用多重匹配查詢來選擇匹配的檔案(和內部匹配)在父級和內部匹配上都匹配。
用例 1 - 父級上的所有匹配項
搜索fans Anne應該給我上面的檔案以及所有內部點擊,因為它在父級別上完全匹配。
用例 2 - 內部命中的所有匹配項
搜索John Smith應該給我上面的檔案,但只有第一個內部命中,因為它在父級不匹配,也不匹配第二個內部命中。
用例 3 - 父級和內部命中的部分匹配
搜索fans Smith應該給我上面的檔案,但只有第一個內部命中,因為組合結果與父和第一個內部命中欄位相匹配。它不應該回傳第二個內部命中,因為Smith它自己和父欄位都缺少。
用例 1 和 2 很容易通過 boolquery 解決,該 boolquery 將父級別的多匹配查詢和嵌套查詢中的另一個多匹配查詢連接在一起(以下是 Java 代碼):
boolQuery()
.should(multiMatchQuery(searchTerm).operator(AND).type(CROSS_FIELDS))
.should(nestedQuery("user", multiMatchQuery(searchTerm).operator(AND).type(CROSS_FIELDS), NONE))
這是我堅持的第三個用例。上述查詢僅適用于父級別或嵌套級別,但不能組合使用。我試圖將“include_in_parent”添加到嵌套型別以使其與父級一起索引,但隨后它匹配John Alice我不想要的搜索。
uj5u.com熱心網友回復:
您無法在多重匹配查詢中處理嵌套欄位和非嵌套欄位。由于嵌套檔案的性質。
所以我認為唯一的解決方案是更改您的模型并復制每個嵌套檔案中的groupand欄位。name因此,您的請求邏輯將是加入對父項的多重匹配查詢和對欄位fans搜索的嵌套查詢。group/name/first/last
我知道您當然不想更改模型,但是在使用 ElasticSearch 時,您必須調整模型以匹配您想要提供的搜索功能。不是相反;)
uj5u.com熱心網友回復:
我做了最后的努力來嘗試手動解決這個問題,并且出乎意料地設法找到了一種方法。它非常復雜,但這就是我目前解決它的方式。(我將在代碼片段下方解釋它是如何作業的。)
private static final String FULL_MATCH_ON_PARENT = "full-match-on-parent";
private static final String PARTIAL_MATCH_ON_PARENT = "partial-match-on-parent";
private static final String PARTIAL_MATCH_ON_NESTED = "partial-match-on-nested";
private final RestHighLevelClient client;
private final ObjectMapper objectMapper;
public List<ParentObject> search(String searchTerm) throws IOException {
SearchSourceBuilder searchSourceBuilder = searchSource().size(20).query(queryForMatchingParents(searchTerm));
SearchRequest request = new SearchRequest().indices("my-index").source(searchSourceBuilder);
SearchResponse search = client.search(request, DEFAULT);
return Stream.of(search.getHits().getHits())
.map(hit -> {
ParentObject parent = readJson(hit.getSourceAsString(), ParentObject.class);
if (!Arrays.asList(hit.getMatchedQueries()).contains(FULL_MATCH_ON_PARENT)) {
List<String> nestedToKeep = Arrays.stream(hit.getMatchedQueries())
.filter(queryName -> queryName.startsWith(PARTIAL_MATCH_ON_PARENT))
.map(partialMatchOnParentQueryName -> partialMatchOnParentQueryName.replace("parent", "nested"))
.flatMap(partialMatchOnNestedQueryName -> Arrays.stream(hit.getInnerHits().get(partialMatchOnNestedQueryName).getHits()))
.map(innerHit -> readJson(innerHit.getSourceAsString(), NestedObject.class).getId())
.distinct()
.collect(toList());
parent.getNestedObjects().removeAll(parent.getNestedObjects().stream()
.filter(nested -> !nestedToKeep.contains(nested.getId()))
.collect(toList()));
}
return parent;
})
.filter(Objects::nonNull)
.collect(toList());
}
private QueryBuilder queryForMatchingParents(String searchTerm) {
BoolQueryBuilder superAggregateQuery = boolQuery();
MultiMatchQueryBuilder matchParentQuery = multiMatchQuery(searchTerm)
.operator(Operator.AND)
.type(MultiMatchQueryBuilder.Type.CROSS_FIELDS).queryName(FULL_MATCH_ON_PARENT);
superAggregateQuery.should(matchParentQuery);
BoolQueryBuilder aggregateQuery = boolQuery();
aggregateQuery.mustNot(matchParentQuery);
BoolQueryBuilder matchNestedQuery = boolQuery();
int counter = 1;
for (Pair<String, String> searchTermPair : getParentNestedPairsOfSearchTerm(searchTerm)) {
matchNestedQuery.should(queryPartialInnerHit(searchTermPair.getLeft(), searchTermPair.getRight(), counter));
counter ;
}
aggregateQuery.must(matchNestedQuery);
superAggregateQuery.should(aggregateQuery);
return superAggregateQuery;
}
private QueryBuilder queryPartialInnerHit(String parentSearchTerm, String nestedSearchTerm, int counter) {
BoolQueryBuilder splitBoolQuery = boolQuery();
if (StringUtils.isNotEmpty(parentSearchTerm)) {
splitBoolQuery.must(multiMatchQuery(parentSearchTerm)
.operator(Operator.AND)
.type(MultiMatchQueryBuilder.Type.CROSS_FIELDS)).queryName(PARTIAL_MATCH_ON_PARENT "-" counter);
} else {
// this is necessary because we still need the queryname to trigger for empty string in parentSearchTerm
splitBoolQuery.must(matchAllQuery()).queryName(PARTIAL_MATCH_ON_PARENT "-" counter);
}
splitBoolQuery.must(nestedQuery("nested",
multiMatchQuery(nestedSearchTerm)
.operator(Operator.AND)
.fuzzyTranspositions(false)
.type(MultiMatchQueryBuilder.Type.CROSS_FIELDS),
ScoreMode.Min).innerHit(new InnerHitBuilder(PARTIAL_MATCH_ON_NESTED "-" counter).setExplain(true)));
return splitBoolQuery;
}
private List<Pair<String, String>> getParentNestedPairsOfSearchTerm(String searchTerm) {
Set<String> words = new HashSet<>(Arrays.asList(searchTerm.split(" ")));
Set<Set<String>> powerSet = powerSet(words);
powerSet = powerSet.stream().filter(set -> set.size() < words.size()).collect(Collectors.toSet());
return powerSet.stream()
.map(set -> {
ArrayList<String> truncatedWords = new ArrayList<>(words);
truncatedWords.removeAll(set);
String words1 = String.join(" ", set);
String words2 = String.join(" ", truncatedWords);
return new ImmutablePair<>(words1, words2);
})
.collect(Collectors.toList());
}
private <T> Set<Set<T>> powerSet(Set<T> originalSet) {
Set<Set<T>> sets = new HashSet<>();
if (originalSet.isEmpty()) {
sets.add(new HashSet<>());
return sets;
}
List<T> list = new ArrayList<>(originalSet);
T head = list.get(0);
Set<T> rest = new HashSet<>(list.subList(1, list.size()));
for (Set<T> set : powerSet(rest)) {
Set<T> newSet = new HashSet<>();
newSet.add(head);
newSet.addAll(set);
sets.add(newSet);
sets.add(set);
}
return sets;
}
private <T> T readJson(String json, Class<T> objectClass) throws IOException {
return objectMapper.readValue(json, objectClass);
}
由于我的問題中解釋的原因,Elasticsearch 無法同時在父物件和嵌套物件中搜索給定查詢。因此,我創建了一個冪集來查找要在父物件和嵌套物件中搜索的所有組合。例如,如果我輸入 3 個搜索詞 ( one two three),我會查找 2^3=8 個組合。one在父欄位和two three嵌套欄位等中搜索。
我為每個部分匹配(在父級和嵌套級別)分配一個命名查詢,并使用相同的數字后綴“計數器”來識別哪些屬于一起。當我們回傳搜索回應時,這變得很重要。
在我談論我們如何解釋結果之前,我應該提到部分匹配只有 2^3-1 個組合。我認為 parent 上的完全匹配是一種特殊情況,因為在這種情況下我不需要過濾任何回傳的嵌套物件。因此它有一個不同的命名查詢FULL_MATCH_ON_PARENT。
在回應中,我們為每個命中提取父級別上的完全或部分匹配的命名查詢。如果不存在完全匹配(基于缺少FULL_MATCH_ON_PARENT匹配的查詢),則評估嵌套物件以進行丟棄。僅應保留具有匹配的部分父命中的嵌套物件,因此回圈當前partial-match-on-parent-{number}命名查詢并檢索具有嵌套物件的相應內部命中。從那里開始,它應該從代碼中自我解釋。
該解決方案已通過廣泛的集成測驗。
轉載請註明出處,本文鏈接:https://www.uj5u.com/caozuo/428935.html
標籤:弹性搜索
上一篇:Opensearchclient.update(python)上的“document_missing_exception”
