假设模型:
json prettyprint-override">{
"group" : "fans",
"name": "Anne",
"user" : [
{
"first" : "John",
"last" : "Smith"
},
{
"first" : "Alice",
"last" : "White"
}
]
}
由于用户是“嵌套”类型,我想使用多匹配查询来选择在父级和内部层匹配的匹配文档(和内部命中)。
搜索粉丝安妮
应该给我上面的文档以及所有内部命中,因为它在父级别上完全匹配。
搜索John Smith
应该会给我上面的文档,但只与第一个内部命中匹配,因为它在父级上不匹配,也与第二个内部命中不匹配。
搜索<code>粉丝Smith</code>应该会给我上面的文档,但只有第一个内部命中,因为组合结果与父字段和第一个内部击中字段相匹配。它不应该返回第二个内部命中,因为它自己和父字段中都缺少Smith
。
使用boolquery很容易解决用例1和用例2,bool query将父级的多匹配查询和嵌套查询中的另一个多匹配查询连接在一起(下面是Java代码):
boolQuery()
.should(multiMatchQuery(searchTerm).operator(AND).type(CROSS_FIELDS))
.should(nestedQuery("user", multiMatchQuery(searchTerm).operator(AND).type(CROSS_FIELDS), NONE))
这是我坚持的第三个用例。上面的查询只适用于父级或嵌套级,但不能组合使用。我曾尝试将“include_in_parent”添加到嵌套类型中,以便将其与父类型一起索引,但随后它会在搜索中匹配,如JohnAlice
,这是我不希望的。
我做了最后的努力,试图手动绕过这个问题,并意外地找到了一个方法。这很复杂,但这就是我目前解决的方法。(我将在代码片段下面解释它是如何工作的。)
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个搜索词(< code >一二三),我将查找2^3=8组合。在父字段中搜索< code >一,在嵌套字段中搜索< code >二三,依此类推。
我为每个部分匹配(在父级和嵌套级)分配了一个命名查询,具有相同的数字后缀“计数器”,以识别哪些属于一起。当我们得到搜索响应时,这变得很重要。
在我谈论我们如何解释结果之前,我应该提一下,对于部分匹配,只有2^3-1组合。我认为parent上的完全匹配是一个特例,因为在这种情况下,我不需要过滤任何返回的嵌套对象。因此,它具有不同的命名查询< code > FULL _ MATCH _ ON _ PARENT 。
在响应中,我们为每个命中提取的命名查询,以在父级别上完全匹配或部分匹配。如果不存在完全匹配(基于缺少FULL_MATCH_ON_PARENT
匹配的查询),则评估嵌套对象以进行丢弃。只应保留具有匹配的部分父命中的嵌套对象,因此应循环访问当前具有父项上的部分匹配{number}
命名查询,并检索具有嵌套对象的相应内部点击。从那里开始,它应该从代码中不言自明。
广泛的集成测试都已通过此解决方案。
您无法在多标记查询中处理嵌套字段和非嵌套字段。由于嵌套文档的性质。
因此,我认为唯一的解决方案是更改模型并复制每个嵌套文档中的组
和名称
字段。因此,您的请求逻辑是加入父级上的多匹配查询,以及针对在组/名称/第一个/最后
一个字段中搜索的粉丝
的嵌套查询。
我知道你当然不想改变模型,但在使用ElasticSearch时,你必须调整模型以匹配你想要提供的搜索功能。而不是相反;)
我是Elasticsearch的新手,我提出了一个问题,Elasticsearch嵌套查询是否只能为嵌套字段返回匹配的嵌套文档。 对于示例,我有一个名为的类型,其中嵌套字段名为 和嵌套查询 我需要的是搜索有提到足球的评论的博客文章,每个博客文章的评论数与足球相匹配(在例子中它数为1,因为另一个评论刚刚提到篮球)。 然而,Elasticsearch似乎总是返回完整的文档,所以我如何才能实现它,或者我
给定一个场景,其中我有一个根文档和一个对象数组。是否可以返回包含一组对象的文档? 例如: 文件1 文件2 我需要一个查询,该查询将根据以下内容匹配并返回Doc1: 在数组中包含key1=value1的对象的文档 这可能吗?
我是elasticsearch的新手,对如何进行过滤器、查询和聚合有一些想法,但不确定如何解决下面的问题。我希望能够从下面显示的文档中只查询公司的最新交付(日期和crate_quantity)。我不确定如何去做。有没有办法使用最大聚合从每个文档中只提取最近的交付?
问题内容: 我正在为我们运行概念验证,以便对ES中更多“标准化”的数据运行嵌套查询。 例如带有嵌套 客户->-名称 -电子邮件-事件->-创建-类型 现在,我可以将给定客户的事件列表移至另一位客户。例如,客户A有50个事件客户B有5000个事件 我现在想将所有事件从客户A移动到客户B 拥有数百万客户的规模,并且在UI中针对图形进行查询,Parent / Child更适合还是应该能够嵌套处理? 在我
尝试排除其中一个子文档与查询不匹配的顶级文档。 对于下面的示例,我试图排除其中一个嵌套作业具有并且与匹配的所有文档。但是,由于其中一个嵌套作业文档与和公司匹配,因此返回此文档。我使用的是一个嵌套查询,其中公司名称必须匹配,并且过滤器的当前值为false。我如何才能使以下文件不被退回?
问题内容: Elasticsearch具有嵌套文档(出色)。我想用它来存储消息(顶级文档)及其作者(嵌套文档)。 由于一个作者可以拥有许多条消息-可以将一个版本的Author引用为多条消息的子级吗? 这样,如果您在一个地方更新Author数据,它将在所有引用它们的地方更新。 注意:这与以下内容有关:如何在Elasticsearch中或在Lucene级别进行联接-此处的答案也可以解决该问题。 问题答