I have a mongo document that looks like the below JSON object. What I'm trying to do, using the mongoDb Query builder, is return all books from user 1 that are read: false.
For example:
var query = new Query();
query.addCriteria(Criteria.where("id").is(1));
query.fields().elemMatch("books", Criteria.where("read").is(false));
return users 1 and the first unread book but i'd like the full list of unread box.
Users:[
{
id: 1,
name: 'John Doe',
books: [
{
id: 1,
title: 'The Hobbit',
read: false
},
{
id: 2,
title: 'The Lord of the Rings',
read: false
},
{
id: 3,
title: 'The Silmarillion',
read: false
}
]
},
{
id: 2,
name: 'Jane Doe',
books: []
}
}
CodePudding user response:
You can use filter with project stage in an aggregation pipeline.
db.collection.aggregate([
{
$match: {
"id": 1
}
},
{
$project: {
"books": {
$filter: {
input: "$books",
as: "b",
cond: {
$eq: ["$$b.read",false]
}
}
}
}
}
])
The query translates to this project stage of the pipeline.
AggregationOperation matchStage = Aggregation
.match(Criteria.where("id").is(1));
AggregationOperation projectStage = Aggregation.project()
.and(ArrayOperators.Filter
.filter("books")
.as("b")
.by(Eq.valueOf("read").equalToValue(false)))
.as("books");
List<Users> users = mongoTemplate.aggregate(
Aggregation.newAggregation(matchStage, projectStage),
Users.class, //collection class
Users.class //return type class
).getMappedResults();
CodePudding user response:
Here is a solution using a MongoRepository<Users,Long> Interface and @Aggregation pipeline. Cleaner then the query builder IMO and supports searching and paging.
@Aggregation(pipeline = {
"{'$match':{'userId': ?0 }}", // filter by userid
"{'$project':{'books':1}}", // only include books in the pipeline
"{'$unwind': '$books'}", // 'unwind' the books array
"{'$match':{'books.read': ?1, 'books.title': { '$regex' : ?2, '$options' : 'i'}}}", // filter by read and title
"{'$group': {'_id': $_id, 'count': {$sum: 1}, 'books': {'$push': '$books'}}}", // group by full count (for pagination) and books
"{'$project':{'_id': 0, 'count': 1, 'books': {$slice: ['$books', ?3, ?4]}}}", // define what to return (count, subset of books)
})
List<BookList> findByUserIdAndRead(Long userId, boolean read, String filter, long skip, int size);
BookList class
public class BookList {
private Integer count;
private List<Book> books;
}