I have a collection with objects like this:
{
"_id": ObjectId("52ed12c144aecc4bf004d0b6"),
"title": "myBook",
"summary": "This is a book summary",
"chapters": [
{
"number": 1
"created_at": ISODate("2013-12-17T23:00:00Z")
},
{
"number": 2
"created_at": ISODate("2014-12-17T23:00:00Z")
}
]
"covers": [
"http://url.com/cover1.jpg",
"http://url.com/cover2.jpg",
],
"urls": [
"http://url.com/myBook",
"http://url.com/myBook/option2"
],
"genres": [
"comedy",
"romantic"
],
"created_at": ISODate("2012-12-17T23:00:00Z"),
"modify_at": ISODate("2014-02-01T15:41:48.149Z")
}
I only want to get books which 'crated_at' >= '2012-12-17T23:00:00Z' with only the chapters which 'created_at' > "2013-12-17T23:00:00Z". (As a filter)
My output json should be something like this:
{
"_id": ObjectId("52ed12c144aecc4bf004d0b6"),
"title": "myBook",
"summary": "This is a book summary",
"chapters": [
{
"number": 2
"created_at": ISODate("2014-12-17T23:00:00Z")
}
]
"covers": [
"http://url.com/cover1.jpg",
"http://url.com/cover2.jpg",
],
"urls": [
"http://url.com/myBook",
"http://url.com/myBook/option2"
],
"genres": [
"comedy",
"romantic"
],
"created_at": ISODate("2012-12-17T23:00:00Z"),
"modify_at": ISODate("2014-02-01T15:41:48.149Z")
}
In the query output could be books without any chapters, others with a subset of all their chapters or books with all their chapters.
For to search books by 'created_at' I query by:
db.books.find({ created_at: {$gte: ISODate("2012-12-17T23:00:00Z")} })
but I don't know what I have to add for to filter the chapters in the output of this result.
You could try to do it with aggregation framework.
db.chapters.aggregate(
{ $match: { created_at: {$gte: ISODate("2012-12-17T23:00:00Z")} } },
{ $unwind: "$chapters" },
{ $match: { "chapters.created_at": {$gte: ISODate("2013-12-17T23:00:00Z")} } },
{ $group:
{
_id:
{
id: "$_id",
title: "$title",
summary: "$summary",
covers: "$covers",
urls: "$urls",
genres: "$genres",
created_at: "$created_at",
modify_at: "$modify_at"
},
chapters: { $push: "$chapters" }
}
},
{ $project:
{
_id: "$_id.id",
title: "$_id.title",
summary: "$_id.summary",
chapters: "$chapters",
covers: "$_id.covers",
urls: "$_id.urls",
genres: "$_id.genres",
created_at: "$_id.created_at",
modify_at: "$_id.modify_at"
}
}
)
Related
Is there a way to keep results from a match under a new field, and under another new field some computed value?
I'm trying to extract a set of genres from a collection of movies, and also keep original results...
Document example:
{
"_id": "62e97ba6ec445b864fc3bc39",
"id": 19913,
"genres": [
"Comedy",
"Drama",
"Romance"
],
"imdb_id": "tt1022603",
"overview": "Tom, greeting-card writer and hopeless romantic...",
"title": "(500) Days of Summer",
"release_date": "2009-07-17",
}
Desired output:
{
result: [
... movies
]
categories: [
"Comedy",
"Drama",
"Romance"
]
}
What I have so far:
use('the_base');
function matchGenre(genre) {
return {
"$match": {
"genres": genre,
}
};
}
function limit(num) {
return {
"$limit": num
};
}
db.movie.aggregate([
matchGenre("Drama"),
limit(5),
{"$unwind": "$genres"},
{"$group": {
"_id": 0,
"gens": { "$addToSet": "$genres" }
}}
]);
My current result:
{
"_id": 0,
"gens": [
"Romance",
"Comedy",
"Thriller",
"Science Fiction",
"Fantasy",
"Drama",
"Crime",
"Action",
"Mystery",
"Adventure",
"Horror"
]
}
I would generally use facets.
Here's an example: https://mongoplayground.net/p/PbORyp4JaF5
db.collection.aggregate([
{
$facet: {
results: [
{
$match: {}
}
],
categories: [
{
$unwind: "$genres"
},
{
$sortByCount: "$genres"
}
],
release_date: [
{
$unwind: "$release_date"
},
{
$sortByCount: "$release_date"
}
]
}
}
])
I have taken the liberty to add an additional facet of release_date, and also ensure that there is a count present in each of the facets, as this is often helpful and required.
Have a look at my collections below,
db={
"replies": [
{
"_id": {
"$oid": "60c4814d09488145b72beda9"
},
"post": [
{
"$oid": "5fc67eb5111f570dc3eb7087"
}
],
"likes": [],
"text": "Reply not reported",
"comment": {
"$oid": "60c4813f09488145b72beda8"
},
"__v": 0
},
{
"_id": {
"$oid": "60c4815609488145b72bedaa"
},
"post": [
{
"$oid": "5fc67eb5111f570dc3eb7087"
}
],
"likes": [],
"text": "Reply reported",
"comment": {
"$oid": "60c4813f09488145b72beda8"
},
"__v": 0,
"reportCount": 1,
"reportedUsers": [
{
"$oid": "6252fe50a5cbd65064d4aab8"
}
]
}
],
"comments": [
{
"_id": {
"$oid": "5fca1877111f570dc3eb7088"
},
"replies": [],
"text": "Reported comment",
"post": {
"$oid": "5fc67eb5111f570dc3eb7087"
},
"reportCount": 1,
"reportedUsers": [
{
"$oid": "6252fe50a5cbd65064d4aab8"
}
],
"__v": 0
},
{
"_id": {
"$oid": "60c4813f09488145b72beda8"
},
"replies": [
{
"$oid": "60c4814d09488145b72beda9"
},
{
"$oid": "60c4815609488145b72bedaa"
}
],
"text": "Comment not reported",
"post": {
"$oid": "5fc67eb5111f570dc3eb7087"
},
"__v": 0
}
]
}
I am trying to get comments and replies from post (Object id - 5fc67eb5111f570dc3eb7087). Assume I blocked user with id "6252fe50a5cbd65064d4aab8". In this case the results should not contain the first comment with id "5fca1877111f570dc3eb7088" reportedUsers contains the above mentioned id and the reply with id also need to filter out since it is also reported.
Both comments are comes under post "5fc67eb5111f570dc3eb7087". Can somebody help with the aggregate query for filter comments and replies under post "5fc67eb5111f570dc3eb7087" if the reported users includes user id "6252fe50a5cbd65064d4aab8".
Mongodb playground url
Please let me know any more details needed.
Expected output will be list of comments with expanded list of replies like below,
[
{
"_id": "60c4813f09488145b72beda8",
"replies": [
{
"_id": "60c4814d09488145b72beda9",
"post": "5fc67eb5111f570dc3eb7087",
"likes": [],
"text": "Reply not reported",
"comment": "60c4813f09488145b72beda8"
}
],
"text": "Comment not reported",
"post": "5fc67eb5111f570dc3eb7087"
}
]
Edit:
according to your comment that $oid represents ObjectId, and assuming a post can have several reported user, you can do something like this:
db.comments.aggregate([
{
$match: { "post": ObjectId("5fc67eb5111f570dc3eb7087")}
},
{
$facet: {
reportedUsers: [
{
$match: {reportedUsers: { $ne: []} }
},
{
$project: {
_id: 0,
reportedUsers: 1
}
},
{ $unwind: "$reportedUsers" },
{
$group: {
_id: 0,
rep: { $addToSet: "$reportedUsers" }
}
}
],
ok: [ {$match: {reportedUsers: null}} ]
}
},
{$unwind: "$ok"},
{
$lookup: {
from: "replies",
localField: "ok.replies",
foreignField: "_id",
let: {
reportedUsers: "$reportedUsers"
},
pipeline: [
{
$addFields: {
reportedUsers: {
"$ifNull": [
"$reportedUsers",
[]
]
}
}
}
],
as: "ok.replies"
}
},
{
$project: {
ok: 1,
reportedUsers: {
"$arrayElemAt": [
"$reportedUsers",
0
]
}
}
},
{
$addFields: {
"replies": {
$filter: {
input: "$ok.replies",
as: "item",
cond: {
$eq: [
{
$size: {
"$setIntersection": [
"$$item.reportedUsers",
"$reportedUsers.rep"
]
}
},
0
]
}
}
},
reportedUsers: 0
}
},
{
$project: {
_id: "$ok._id",
post: "$ok.post",
replies: 1,
text: "$ok.text"
}
}
])
As you can see here
This query is $matching all the comments for the required post, then split it to comments without reportedUsers (ok) and group all the reportedUsers, via a $facet step. Then it collects all replies from the replies collection, via $lookup and adding an empty reportedUsers array if it does not exists. Next step is filter the replies and remove all replies that has a reported user that was in our list of reportedUsers grouped before.
I have more than thousand of documents in my mongoDB collection where each document represents the data for a song like this:
{
"_id": ObjectID("612e03a790c3d3189845589b"),
"artist": "3 Doors Down",
"title": "Landning in London",
"album": "Another 700 Miles",
"genre": "Seventeen Days",
"trackNumber": 5,
"year": 2005,
"rating": 3,
"duration": "4:32",
"country": "United States",
}
Now with the help of the aggregation pipeline I want to group my collections the following way. So Far I could do grouping for 1 stage but I struggle to push through the complete root document until end and do this nested grouping.
List all genres --> list all artists within this genre --> list all albums of this artist within this genre --> list the COMPLETE root document for all songs of this album:
{
[
{
"genreName": "Alternative Rock",
"artists": [
{
"artistName": "3 Doors Down",
"albums": [
{
"albumName": "Seventeen Days",
"songs": [
{
"_id": ObjectID(612e03a790c3d3189845589b),
"artist": "3 Doors Down",
"title": "Landning in London",
"album": "Another 700 Miles",
"genre": "Seventeen Days",
"trackNumber": 5,
"year": 2005,
"rating": 3,
"duration": "4:32",
"country": "United States",
}
]
}
]
}
]
}
]
}
This one should work:
db.collection.aggregate([
{
$group: {
_id: { genreName: "$genre", artistName: "$artist", albumName: "$album" },
songs: { $push: "$$ROOT" }
}
},
{
$group: {
_id: { genreName: "$_id.genreName", artistName: "$_id.artistName" },
albums: { $push: { albumName: "$_id.albumName", songs: "$songs" } }
}
},
{
$group: {
_id: "$_id.genreName",
artists: { $push: { artistsName: "$_id.artistsName", albums: "$albums" } }
}
},
{ $project: { _id: 0, genreName: "$_id", artists: 1 } }
])
I'm building a new LMS in mongodb and I have the following collections:
Courses
{
"_id": ObjectId("5f6a6b159de1304fb885b194"),
"title": "Course test",
"sections": [
{
"_id": ObjectId("5f6a6b159de1304fb885b195"),
"title": "Section 1 - introduction",
"order": 1,
"modules": [
{
"_id": ObjectId("5f6a6b159de1304fb885b196"),
"module_FK_id": ObjectId("5f6a6b149de1304fb885b135"),
"title": "Module 1",
"order": 1
},
{
"_id": ObjectId("5f6a6b159de1304fb885b198"),
"module_FK_id": ObjectId("5f6a6b149de1304fb885b14a"),
"title": "Module 2",
"order": 2
},
]
},
{
"_id": ObjectId("5f6a6b149de1304fb885b175"),
"title": "Section 2 - How to do something",
"order": 2,
"modules": [
{
"_id": ObjectId("5f6a6b149de1304fb885b141"),
"module_FK_id": ObjectId("5f6a6b149de1304fb885b150"),
"title": "Module 1",
"order": 1
},
{
"_id": ObjectId("5f6a6b149de1304fb885b15f"),
"module_FK_id": ObjectId("5f6a6b149de1304fb885b18e"),
"title": "Module 2",
"order": 2
},
]
},
]
}
Modules (only one as example)
{
"_id": ObjectId("5f6a6b149de1304fb885b135"),
"text": "Lorem ipsum...",
"mediaUrl": "urllinkhere"
}
As shown, I choose to have embedded documents for sections and modules titles but I need also a second collection, modules, because each module contains a large amount of text and my course document may get too big quickly.
Now I need to rebuild the entire document as if it was completely embedded.
Here's an example:
{
"_id": ObjectId("5f6a6b159de1304fb885b194"),
"title": "Course test",
"sections": [
{
"_id": ObjectId("5f6a6b159de1304fb885b195"),
"title": "Section 1 - introduction",
"order": 1,
"modules": [
{
"_id": ObjectId("5f6a6b159de1304fb885b196"),
"module_FK_id": ObjectId("5f6a6b149de1304fb885b135"),
"title": "Module 1",
"order": 1
"text": "Lorem ipsum...",
"mediaUrl": "urllinkhere"
},
// last two fields from collection "modules"
I'm trying different combination of aggregation and lookups but I can't obtain the desired result.
Can anybody help me?
You can construct the aggregation pipeline like this below, just remember to $group in the reverse order of $unwind operations
db.courses.aggregate([
{
$unwind: "$sections"
},
{
$unwind: "$sections.modules"
},
{
"$lookup": {
"from": "modules",
"localField": "sections.modules.module_FK_id",
"foreignField": "_id",
"as": "module_details"
}
},
{
$unwind: {
path: "$module_details",
preserveNullAndEmptyArrays: true
}
},
{
"$project": {
title: 1,
sections: {
_id: "$sections._id",
modules: {
_id: "$sections.modules._id",
module_FK_id: "$sections.modules.module_FK_id",
order: "$sections.modules.order",
title: "$sections.modules.title",
mediaUrl: "$module_details.mediaUrl",
text: "$module_details.text"
},
order: "$sections.order",
title: "$sections.title"
}
}
},
{
"$group": {
"_id": "$sections._id",
main_id: {
$first: "$_id"
},
main_title: {
$first: "$title"
},
order: {
$first: "$sections.order"
},
title: {
$first: "$sections.title"
},
modules: {
$push: "$sections.modules"
}
}
},
{
"$project": {
"_id": "$main_id",
"title": "$main_title",
section: {
_id: "$_id",
modules: "$modules",
title: "$title",
order: "$order",
}
}
},
{
$group: {
_id: "$_id",
title: {
$first: "$title"
},
sections: {
$push: "$section"
}
}
}
])
Working Example
I'm trying to count the number of votes per question for the following schema.
[
{
"_id": "564b9e13583087872176dbd2",
"question": "fav NFL team",
"choices": [
{
"text": "St. Louis Rams",
"_id": "564b9e13583087872176dbd7",
"votes": [
{
"ip": "::ffff:192.168.15.130",
"_id": "564b9e30583087872176dbd8"
},
{
"ip": "::ffff:192.168.1.1",
"_id": "564bb355e4e1b7200da92668"
}
]
},
{
"text": "Oakland Raiders",
"_id": "564b9e13583087872176dbd6",
"votes": [
{
"ip": "::ffff:192.168.1.135",
"_id": "564bb273e4e1b7200da92667"
}
]
},
{
"text": "Denver Broncos",
"_id": "564b9e13583087872176dbd5",
"votes": []
},
{
"text": "Kansas City Chiefs",
"_id": "564b9e13583087872176dbd4",
"votes": [
{
"ip": "::ffff:192.168.1.100",
"_id": "564bab48e4e1b7200da92666"
}
]
},
{
"text": "Detroit Lions",
"_id": "564b9e13583087872176dbd3",
"votes": [
{
"ip": "::ffff:192.168.15.1",
"_id": "564b9f41583087872176dbd9"
}
]
}
]
}
]
I'm assuming I am going to have to use aggregate and sum.
I was able to get the count for the choices array, but I'm not sure how to go deeper.
db.polls.aggregate([{$unwind: '$choices'}, {$group:{_id:'$_id', 'sum':{$sum:1}}}])
The vote count for "fav NFL team" would be 5.
Also, for reference here is my mongoose code that generated the schema
var mongoose = require('mongoose');
var voteSchema = new mongoose.Schema({
ip: 'String'
});
var choiceSchema = new mongoose.Schema({
text: String,
votes: [voteSchema]
});
exports.PollSchema = new mongoose.Schema({
question: {
type: String,
required: true
},
choices: [choiceSchema]
});
I figured out how to do it in mango, I needed another unwind.
db.polls.aggregate([
{$unwind: '$choices'},
{$unwind:'$choices.votes'},
{$group:{
_id:'$_id',
'sum':{
$sum:1
}
}}
])
And here it is in mongoose
Poll.aggregate([
{$unwind: '$choices'},
{$unwind: '$choices.votes'},
{$group:{
_id: '$_id',
'sum': {
$sum:1
}
}}
], function(err, result) {
if (err) {
console.log(err);
}
res.json(result);
});