I have a MongoDB collection [Users] each with an Array of embedded documents [Photos].
I would like to be able to have a page which lists recent photos, for example - the last 10 photos uploaded by any user.
Users
[
{
_id: ObjectID
name: "Matthew"
Photos: [
{
_id: ObjectID
url: "http://www.example.com/photo1.jpg"
created: Date("2013-02-01")
},
{
_id: ObjectID
url: "http://www.example.com/photo3.jpg"
created: Date("2013-02-03")
}
]
},
{
_id: ObjectID
name: "Bob"
Photos: [
{
_id: ObjectID
url: "http://www.example.com/photo2.jpg"
created: Date("2013-02-02")
},
{
_id: ObjectID
url: "http://www.example.com/photo4.jpg"
created: Date("2013-02-04")
}
]
}
]
My first attempt at solving this was to find all Users where Photos is not null, and sort it by the created date:
User.find({images:{$ne:[]}}).sort({"images.created":-1})
Unfortunately, this doesn't work as I need it to. It sorts Users by the most recent images, but still returns all images, and there is no way to limit the number of images returned by the function.
I started looking into aggregate, and it seems like it might be the solution I'm looking for, but I'm having trouble finding out how to get it to work.
Ideally, the type of result I would like returned would be like this:
results: [
{
_id: ObjectID (from Photo)
name: "Bob"
url: "http://www.example.com/photo4.jpg"
created: Date("2013-02-04")
}
{
_id: ObjectID (from Photo)
name: "Matthew"
url: "http://www.example.com/photo3.jpg"
created: Date("2013-02-03")
}
]
Each result should be a Photo, and I should be able to limit the results to a specific amount, and skip results for paged viewing.
Please let me know if you need any more information, and thank you for your responses.
You need aggregation framework:
db.users.aggregate(
{ $project: {"Photos" : 1, _id: 0, name:1 }},
{ $unwind: "$Photos" },
{ $sort: {"Photos.created" : -1} },
{ $skip: 1 },
{ $limit: 2 }
)
And result would be like this:
{
"result" : [
{
"name" : "Matthew",
"Photos" : {
"_id" : 2,
"url" : "http://www.example.com/photo3.jpg",
"created" : "2013-02-03"
}
},
{
"name" : "Bob",
"Photos" : {
"_id" : 3,
"url" : "http://www.example.com/photo2.jpg",
"created" : "2013-02-02"
}
}
],
"ok" : 1
}
I think you want something like this:
db.Users.aggregate( [
{$unwind:"$Photos"},
{$sort: {"Photos.created":-1},
{$limit: 10}
] );
Related
Here is the collection.
{{
"id" : "123",
"likes" : [ {
"member" : "3041"
},
{
"member" : "3141"
}]
},
{
"id" : "124",
"likes" : [ {
"member" : "3241"
},
{
"member" : "3241"
},
{
"member" : "3341"
}]
}}
How to retrieve the count of number of objects of likes key for each document?
In this format:
[{
"id" : "123",
"likesCount" : 2
},
{
"id" : "124",
"likesCount" : 3
}]
This should do it for you:
db.collection.aggregate([
{
$project: {
_id: 0,
id: 1,
likesCount: {
$size: "$likes"
}
}
}
])
You are using an aggregation and in the $project part of it use $size to get the length of the array.
You can see it working here
I think you have to map the collection to transform the objects within it into the shape you want.
In this case we get the object and extract the id and instead of all the likes we just get the length of them.
let newCollection = collection.map(obj => ({
id: obj.id,
likesCount: obj.likes.length
}));
I am using aggregate method in mongoDB to group but when I use $group it returns the only field which I used to group. I have tried $project but it is not working either. I also tried $first and it worked but the result data is now in different format.
The response format I need looks like:
{
"_id" : ObjectId("5b814b2852d47e00514d6a09"),
"tags" : [],
"name" : "name here",
"rating" : "123456789"
}
and after adding $group in my query.response is like this, the value of _id changes. (and the $group is taking only _id, if i try any other keyword it throws an error of accumulator something. please explain this also.)
{
"_id" :"name here" //the value of _id changed to the name field which i used in $group condition
}
I have to remove the duplicates in name field, without changing any structure and fields. also I am using nodeJS with mongoose, so please provide the solution that works with it.
You can use below aggregation query.
$$ROOT to keep the whole document per each name followed by $replaceRoot to promote the document to the top.
db.col.aggregate([
{"$group":{"_id":"$name","doc":{"$first":"$$ROOT"}}},
{"$replaceRoot":{"newRoot":"$doc"}}
])
user2683814's solution worked for me but in my case, I have a counter accumulator when we replace the newRoot object, the count field is missing in the final stage so I've used $mergeObjects operator to get my count field back.
db.collection.aggregate([
{
$group: {
_id: '$product',
detail: { $first: '$$ROOT' },
count: {
$sum: 1,
},
},
},
{
$replaceRoot: {
newRoot: { $mergeObjects: [{ count: '$count' }, '$detail'] },
},
}])
When you group data on any database, it means you want to perform accumulated operation on the required field and the other field which will not be include in accumulated operation will be used in group like
db.collection.aggregate([{
$group: {
_id: { field1: "", field1: "" },
acc: { $sum: 1 }
}}]
here in _id object will contains all other fields which you want to hold.
for your data you can try this
db.collection.aggregate([{
$group: {
_id: "$name",
rating: { $first: "$rating" },
tags: { $first: "$tag" },
docid: { $first: "$_id" }
}
},
{
$project: {
_id: "$docid",
name: "$_id",
rating: 1,
tags: 1
}
}])
You can use this query
db.col.aggregate([
{"$group" : {"_id" : "$name","data" : {"$first" : "$$ROOT"}}},
{"$project" : {
"tags" : "$data.tags",
"name" : "$data.name",
"rating" : "$data.rating",
"_id" : "$data._id"
}
}])
I wanted to group my collection by groupById field and store it as key value pairs having key as groupById and value as all the items of that group.
db.col.aggregate([{$group :{_id :"$groupById",newfieldname:{$push:"$"}}}]).pretty()
This is working fine for me..
This is my user collection
{
"_id" : ObjectId("58e8cb640f861e6c40627a06"),
"actorId" : "665991",
"login" : "petroav",
"gravatar_id" : "",
"url" : "https://api.github.com/users/petroav",
"avatar_url" : "https://avatars.githubusercontent.com/u/665991?"
}
This is my repo collection
{
"_id" : ObjectId("58e8cb640f861e6c40627a07"),
"repoId" : "28688495",
"name" : "petroav/6.828",
"url" : "https://api.github.com/repos/petroav/6.828"
}
This is my events collections
{
"_id" : ObjectId("58e8cb640f861e6c40627a08"),
"eventId" : "2489651045",
"type" : "CreateEvent",
"actorLogin" : "petroav",
"repoId" : "28688495",
"eventDate" : ISODate("2015-01-01T15:00:00.000+0000"),
"public" : true
}
I am trying to do following queries on above data
Return list of all repositories with their top contributor
Find the repository with the highest number of events from an actor (by login). If multiple repos have the same number of events, return the one with the latest event.
Return actor details and list of contributed repositories by login
I tried 3 one by doing this
db.events.aggregate(
[ {
$match:{"actorLogin":"petroav"}
},
{
$lookup:{
from:"repos",
localField:"repoId",
foreignField:"repoId",
as:"Repostory"
}
},
{
$group:{ _id : "$Repostory", repo: { $push: "$$ROOT" } }
}
]
).pretty()
Please help. I am new to mongodb.
These should work, you may have to update some of the variable names if they don't match your code exactly. Because you are using actorLogin and repoId as references instead of _id, you likely want to create indexes for the fields to help with performance.
Also you may want to add a $project stage at the end of these pipelines if you want to clean up the final formats, remove extra fields, rename fields, etc..
For Number 1
db.repos.aggregate(
[
{
$lookup:{
from:"events",
localField:"repoId",
foreignField:"repoId",
as:"Event"
}
},{
$unwind:"$Event"
},
{
$group:{
_id : {repo: "$_id", user: "$Event.actorLogin" },
contributionCount: { $sum:1 },//number of times logged in
}
},
{
$sort: {
contributionCount: -1
}
},{
$group:{
_id: {repo:'$_id.repo'},
contributionCount: {$first: '$contributionCount' },
actorLogin: {$first: '$_id.user' }
}
}
]
).then(console.log)
For Number 2
db.events.aggregate(
[ {
$match:{"actorLogin":"petroav"}
},
{
$lookup:{
from:"repos",
localField:"repoId",
foreignField:"repoId",
as:"Repostory"
}
},{
$unwind:"$Repostory"
},
{
$group:{
_id : "$Repostory",
loginCount: { $sum:1 },//number of times logged in
lastLoginDate: {$max:'$eventDate'} //largest ISODate for the repo
}
},
{
$sort: {
loginCount: -1,
date: -1
}
},
{limit:1}
]
).then(console.log)
For number 3
db.user.aggregate(
[
{
$match:{"actorLogin":"petroav"}
},
{
$lookup:{
from:"events",
localField:"actorLogin",
foreignField:"actorLogin",
as:"Events"
}
},{
$unwind:"$Events"
},
{
$lookup:{
from:"repos",
localField:"Events.repoId",
foreignField:"repoId",
as:"Repostory"
}
},{
$unwind:"$Repostory"
},{
$group: {
_id:'$actorLogin',
user: {$first:'$$ROOT'}
repos: {$addToSet:'$Repostory'}
}
}
]
).then(console.log)
I have a collection db.activities, each item of which has a dueDate. I need to present data in a following format, which basically a list of activities which are due today and this week:
{
"today": [
{ _id: 1, name: "activity #1" ... },
{ _id: 2, name: "activity #2" ... }
],
"thisWeek": [
{ _id: 3, name: "activity #3" ... }
]
}
I managed to accomplish this by simply querying for the last week's activities as a flat list and then grouping them with javascript on the client, but I suspect this is a very dirty solution and would like to do this on server.
look up mongo aggregation pipeline.
your aggregation has a match by date, group by date and a maybe a sort/order stage also by date.
lacking the data scheme it will be along the lines of
db.collection.aggregate([{ $match: {"duedate": { "$gte" : start_dt, "$lte" : end_dt} } ,
{ $group: {_id: "$duedate", recordid : "$_id" , name: "$name" },
{"$sort" : {"_id" : 1} } ] );
if you want 'all' records remove the $match or use { $match: {} } as one does with find.
in my opinion, you cannot aggregate both by day and week within one command. the weekly one may be achieved by projecting duedate using mongos $dayOfWeek. along the lines of
db.collection.aggregate([
{ $match: {"duedate": { "$gte" : start_dt, "$lte" : end_dt} } ,
{ $project : { dayOfWeek: { $dayOfWeek: "$duedate" } },
{ $group: {_id: "$dayOfWeek", recordid : "$_id" , name: "$name" },
{"$sort" : {"_id" : 1} } ] );
check out http://docs.mongodb.org/manual/reference/operator/aggregation/dayOfWeek/
I've just got stuck with this problem. I've got two Mongoose schemas:
var childrenSchema = mongoose.Schema({
name: {
type: String
},
age: {
type: Number,
min: 0
}
});
var parentSchema = mongoose.Schema({
name : {
type: String
},
children: [childrenSchema]
});
Question is, how to fetch all subdocuments (in this case, childrenSchema objects) from every parent document? Let's suppose I have some data:
var parents = [
{ name: "John Smith",
children: [
{ name: "Peter", age: 2 }, { name: "Margaret", age: 20 }
]},
{ name: "Another Smith",
children: [
{ name: "Martha", age: 10 }, { name: "John", age: 22 }
]}
];
I would like to retrieve - in a single query - all children older than 18. Is it possible? Every answer will be appreciated, thanks!
You can use $elemMatch as a query-projection operator in the most recent MongoDB versions. From the mongo shell:
db.parents.find(
{'children.age': {$gte: 18}},
{children:{$elemMatch:{age: {$gte: 18}}}})
This filters younger children's documents out of the children array:
{ "_id" : ..., "children" : [ { "name" : "Margaret", "age" : 20 } ] }
{ "_id" : ..., "children" : [ { "name" : "John", "age" : 22 } ] }
As you can see, children are still grouped inside their parent documents. MongoDB queries return documents from collections. You can use the aggregation framework's $unwind method to split them into separate documents:
> db.parents.aggregate({
$match: {'children.age': {$gte: 18}}
}, {
$unwind: '$children'
}, {
$match: {'children.age': {$gte: 18}}
}, {
$project: {
name: '$children.name',
age:'$children.age'
}
})
{
"result" : [
{
"_id" : ObjectId("51a7bf04dacca8ba98434eb5"),
"name" : "Margaret",
"age" : 20
},
{
"_id" : ObjectId("51a7bf04dacca8ba98434eb6"),
"name" : "John",
"age" : 22
}
],
"ok" : 1
}
I repeat the $match clause for performance: the first time through it eliminates parents with no children at least 18 years old, so the $unwind only considers useful documents. The second $match removes $unwind output that doesn't match, and the $project hoists children's info from subdocuments to the top level.
In Mongoose, you can also use the elegant .populate() function like this:
parents
.find({})
.populate({
path: 'children',
match: { age: { $gte: 18 }},
select: 'name age -_id'
})
.exec()
A. Jesse Jiryu Davis's response works like a charm, however for later versions of Mongoose (Mongoose 5.x) we get the error:
Mongoose 5.x disallows passing a spread of operators to Model.aggregate(). Instead of Model.aggregate({ $match }, { $skip }), do Model.aggregate([{ $match }, { $skip }])
So the code would simply now be:
> db.parents.aggregate([{
$match: {'children.age': {$gte: 18}}
}, {
$unwind: '$children'
}, {
$match: {'children.age': {$gte: 18}}
}, {
$project: {
name: '$children.name',
age:'$children.age'
}
}])
{
"result" : [
{
"_id" : ObjectId("51a7bf04dacca8ba98434eb5"),
"name" : "Margaret",
"age" : 20
},
{
"_id" : ObjectId("51a7bf04dacca8ba98434eb6"),
"name" : "John",
"age" : 22
}
],
"ok" : 1
}
(note the array brackets around the queries)
Hope this helps someone!