英文:
Count operation returns 1 instead of 0 after grouping array in Spring Data MongoDB Aggregation
问题
我需要使用 Spring Data MongoDB 创建高级聚合,其模型如下:
@Getter
@Setter
@Document
public class Library {
@Id
@JsonSerialize(using = ToStringSerializer.class)
private ObjectId id;
private Address address;
private String workingHours;
// ...
}
@Getter
@Setter
@Document
public class Book {
@Id
@JsonSerialize(using = ToStringSerializer.class)
private ObjectId id;
private Boolean published;
private Boolean hidden;
private String title;
@JsonSerialize(using = ToStringSerializer.class)
private ObjectId libraryId;
// ...
}
pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb</artifactId>
<version>2.2.0</version>
</dependency>
图书馆集合:
{
"_id" : ObjectId("5f45440ee89590218e83a697"),
"workingHours" : "8:00 PM - 8:00 AM",
"address" : DBRef("addresses", ObjectId("5f4544198da452a5523e3d11"))
}
图书集合:
{
"_id" : ObjectId("5f454423be823729015661ed"),
"published": true,
"hidden": false,
"title": "The Hobbit, or There and Back Again",
"libraryId": ObjectId("5f45440ee89590218e83a697")
},
{
"_id" : ObjectId("5f45445b876d08649b88ed5a"),
"published": true,
"hidden": false,
"title": "Harry Potter and the Philosopher's Stone",
"libraryId": ObjectId("5f45440ee89590218e83a697")
},
{
"_id" : ObjectId("5f45446c7e33ca70363f629a"),
"published": true,
"hidden": false,
"title": "Harry Potter and the Cursed Child",
"libraryId": ObjectId("5f45440ee89590218e83a697")
},
{
"_id" : ObjectId("5f45447285f9b3e4cb8739ad"),
"published": true,
"hidden": false,
"title": "Fantastic Beasts and Where to Find Them",
"libraryId": ObjectId("5f45440ee89590218e83a697")
},
{
"_id" : ObjectId("5f45449fc121a20afa4fbb96"),
"published": false,
"hidden": false,
"title": "Universal Parks & Resorts",
"libraryId": ObjectId("5f45440ee89590218e83a697")
},
{
"_id" : ObjectId("5f4544a5f13839bbe89edb23"),
"published": false,
"hidden": true,
"title": "Ministry of Dawn",
"libraryId": ObjectId("5f45440ee89590218e83a697")
}
根据用户的上下文,我需要返回基于 startsWith()
或 like()
原则进行过滤的不同数量的图书。
假设我有 4 本已发布的图书,普通用户添加了一本,另外还有一本是隐藏的。
- 管理员应该知道所有这些,所以他将看到
booksCount
为6
。 - 普通用户只能看到已发布的或自己添加的图书,所以他将看到
booksCount
为5
。 - 将来可能会有其他条件。
我提出了如下的聚合方法:
Criteria criteria = Criteria.where("_id").ne(null).and("address.city").is("Chicago");
MatchOperation matchOperation = Aggregation.match(criteria);
LookupOperation lookupOperation = LookupOperation.newLookup().from("books").localField("_id").foreignField("topicId").as("books");
UnwindOperation unwindOperation = Aggregation.unwind("books", true);
MatchOperation secondMatchOperation = Aggregation.match(Criteria.where("books.published").is(Boolean.TRUE).orOperator(Criteria.where("creator.userId").is(context.getUserId())));
AggregationOperation group = Aggregation.group("_id")
.first("_id").as("id")
.first("published").as("published")
.first("title").as("title")
.push("books").as("books").count().as("booksCount");
Aggregation aggregation = !isAdministrator() ?
Aggregation.newAggregation(matchOperation, lookupOperation, unwindOperation, secondMatchOperation, group) :
Aggregation.newAggregation(matchOperation, lookupOperation, unwindOperation, group);
mongoTemplate.aggregate(aggregation, "libraries", Document.class).getRawResults().get("results");
除了 count()
操作之外,一切正常。
- 如果图书数组大小为 0,它总是返回 1。
- 如果图书数组大小大于 0,它会返回正确的数量。
- 我尝试使用
newBuilder(GroupOps.SUM, null, 0)
替代count()
,但现在它总是返回 0。 - 如果我使用
newBuilder(GroupOps.SUM, null, 2)
,它会返回size + 2
。我不知道发生了什么。
我的问题:
- 有人能告诉我我做错了什么,如何纠正吗?
- 另外,我需要将 "booksCount" 从
Integer
解析为String
。在分组阶段是否可能?
提前感谢您的帮助。
英文:
I need to create advanced aggregation using Spring Data MongoDB having model like that:
@Getter
@Setter
@Document
public class Library {
@Id
@JsonSerialize(using = ToStringSerializer.class)
private ObjectId id;
private Address address;
private String workingHours;
...
}
@Getter
@Setter
@Document
public class Book {
@Id
@JsonSerialize(using = ToStringSerializer.class)
private ObjectId id;
private Boolean published;
private Boolean hidden;
private String title;
@JsonSerialize(using = ToStringSerializer.class)
private ObjectId libraryId;
...
}
pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb</artifactId>
<version>2.2.0</version>
</dependency>
Libraries collection:
{
"_id" : ObjectId("5f45440ee89590218e83a697"),
"workingHours" : "8:00 PM - 8:00 AM",
"address" : DBRef("addresses", ObjectId("5f4544198da452a5523e3d11"))
}
Books collection:
{
"_id" : ObjectId("5f454423be823729015661ed"),
"published": true,
"hidden": false,
"title": "The Hobbit, or There and Back Again"
"libraryId": ObjectId("5f45440ee89590218e83a697")
},
{
"_id" : ObjectId("5f45445b876d08649b88ed5a"),
"published": true,
"hidden": false,
"title": "Harry Potter and the Philosopher's Stone"
"libraryId": ObjectId("5f45440ee89590218e83a697")
},
{
"_id" : ObjectId("5f45446c7e33ca70363f629a"),
"published": true,
"hidden": false,
"title": "Harry Potter and the Cursed Child"
"libraryId": ObjectId("5f45440ee89590218e83a697")
},
{
"_id" : ObjectId("5f45447285f9b3e4cb8739ad"),
"published": true,
"hidden": false,
"title": "Fantastic Beasts and Where to Find Them"
"libraryId": ObjectId("5f45440ee89590218e83a697")
},
{
"_id" : ObjectId("5f45449fc121a20afa4fbb96"),
"published": false,
"hidden": false,
"title": "Universal Parks & Resorts"
"libraryId": ObjectId("5f45440ee89590218e83a697")
},
{
"_id" : ObjectId("5f4544a5f13839bbe89edb23"),
"published": false,
"hidden": true,
"title": "Ministry of Dawn"
"libraryId": ObjectId("5f45440ee89590218e83a697")
}
Depending on the context of the user, I have to return a different count of books that can be filtered based on startsWith()
or like()
principle.
Assuming that I have 4 published books, one more added by normal user and one more hidden.
- Administrator should know about all of them, so he will see
booksCount
as6
. - The regular user sees only published or added by himself, so he will see
booksCount
as5
. - There can be some other conditions in the future.
I came up with aggregation like this:
Criteria criteria = Criteria.where("_id").ne(null).and("address.city").is("Chicago");
MatchOperation matchOperation = Aggregation.match(criteria);
LookupOperation lookupOperation = LookupOperation.newLookup().from("books").localField("_id").foreignField("topicId").as("books");
UnwindOperation unwindOperation = Aggregation.unwind("books", true);
MatchOperation secondMatchOperation = Aggregation.match(Criteria.where("books.published").is(Boolean.TRUE).orOperator(Criteria.where("creator.userId").is(context.getUserId()));
AggregationOperation group = Aggregation.group("_id")
.first("_id").as("id")
.first("published").as("published")
.first("title").as("title")
.push("books").as("books").count().as("booksCount");
Aggregation aggregation = !isAdministrator() ?
Aggregation.newAggregation(matchOperation, lookupOperation, unwindOperation, secondMatchOperation, group) :
Aggregation.newAggregation(matchOperation, lookupOperation, unwindOperation, group);
mongoTemplate.aggregate(aggregation, "libraries", Document.class).getRawResults().get("results");
Everything works fine instead of count()
operation.
- If books array size is 0 it always returns 1.
- If books array size is greater than 0 it returns the proper amount.
- I've tried using
newBuilder(GroupOps.SUM, null, 0)
instead ofcount()
, but now it always return 0. - If I use
newBuilder(GroupOps.SUM, null, 2)
it returnssize + 2
. I don't know what is going on.
My quesions:
- Can anyone tell me what I am doing wrong and how to correct it?
- In addition I need to parse "booksCount" from
Integer
toString
. Is it possible in group stage?
Thank you in advance.
答案1
得分: 2
因为 Aggregation.unwind("books", true);
导致了这种情况。当没有连接时,该语句会将文档保留下来,除非你将其设置为 Aggregation.unwind("books")
。默认行为是 false
。因此,在计数时,该文档被计算为一个文档。这就是为什么它以 1
作为输出的原因。错误输出示例
所以你可以在下一个阶段中计算大小。
project("_id", "published", "title", "books")
.and(ArrayOperators.Size.lengthOfArray(ConditionalOperators.ifNull("books").then(Collections.emptyList()))).as("booksCount")
在 Mongo playground 中的正确答案 正常工作。
英文:
It happens because of Aggregation.unwind("books", true);
. When there is no join, this statement keeps as the document unless you make it as Aggregation.unwind("books")
. Default behavior is false
. Then when you counting, the document is counted as a document. That's why its giving you the 1
as output. Example with wrong output
So what you can do is, you can count the size in the next stage.
project("_id", "published", "title", "books")
.and(ArrayOperators.Size.lengthOfArray(ConditionalOperators.ifNull("books").then(Collections.emptyList()))).as("booksCount")
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论