Mongoose query $in with latest document on specific field - mongodb

I'm trying to query documents in MongoDB using find(), but it's not working to get documents I want. Say we have a documents
Pet = [
{project: "foo", date: 11111, data: "Lion"},
{project: "bar", date: 1111, data: "Tiger"},
{project: "foo", date: 2222, data: "Cat"},
{project: "bee", date: 3333, data: "Rat"},
{project: "pet", date: 4444, data: "Cow"},
{project: "yeti", date: 2233, data: "Dog"}, ...];
Then, I just want to query only 1 document of each project in the array of ["foo", "pet"]. From this sample data, i expect to get
[{project: "foo", date: 11111, data: "Lion"},
{project: "pet", date: 4444, data: "Cow"}]
I try
Pet.find({project: {$in: ["foo","pet"]}, {},{ sort: { date: -1 },limit: 1});
I get only 1 document as I set limit equal to 1. How can I do to get the expected query?

If I understood correctly your question this could help.
Assumption: You have a collection called projects with the fields from your example.
db.projects.aggregate(
[
{ $match: { project: { $in: [ "foo","pet" ] } } },
{ $sort: { project: 1, date: 1 } },
{
$group:
{
_id: "$project",
firstProject: { $first: "$date" }
}
}
]
)
Note: You could play a little more with the $group section of your aggregate to show other records within the group

you have to use aggregation
db.collection.aggregate([
{
"$match": {
"project": {
"$in": [
"pet",
"foo"
]
}
}
},
{
"$group": {
"_id": "$project",
"date": {
"$first": "$date"
},
"data": {
"$first": "$data"
}
}
},
{
"$project": {
"date": 1,
"data": 1
}
},
{
"$sort": {
"date": -1
}
}
])
here is working example : https://mongoplayground.net/p/rIDuXTeHg4i

Related

MongoDb aggregate format output

I have the following mongoose aggregate query:
Model.aggregate([
{
$group: {
_id: "$user",
total: {
$sum: {
$toDecimal: "$amount"
}
}
}
}
])
which produces the following output:
[{
"_id": "ABC",
"total": {
"$numberDecimal": "XYZ"
}
}, ...]
Is there any way I could format it to produce slightly more readable results such as
[{
"_id": "ABC",
"total": "XYZ"
}, ...]

Nested output by group in mongoDB

I have a collection like this.
let Movies= [
{year: '2000', language: 'English', genre: 'Romance' , name: 'A beautiful day'},
{year: '2000', language: 'English', genre: 'Action' , name: 'A Dangerous day'},
{year: '2000', language: 'French', genre: 'Romance' , name: 'someromancename'},
{year: '2000', language: 'French', genre: 'Action' , name: 'someactionname'},]
I need to get a output in group format which may look something like this:
{
"2000" : {
"English": {
"Romance": [{
"name":"A beautiful day"
//other fields
}]
"Action": [{
"name":"A dangerous day"
}]
},
"French": {
"Romance": [{
"name":"Some Romance Name"
}]
"Action": [{
"name":"Some Action Name"
}]
},
}
}
I have tried using aggregation but not able to get the exact query as I am new to MongoDB. This is what I tried to do but the expected result is not achieved.
db.getCollection('Movies').aggregate([
{$group: {_id: {"year" : "$year",
"language": "$language" ,
"genre": "$genre" ,
},
"movies": {"$push": "$$ROOT"}
}
},
{$group: {_id: "$_id.year",
"movies": {"$push": {
"language": "$_id.language",
"genre": "$_id.genre" ,
"movies": "$movies"
}}
}
},
])
You can try,
$group by year, language and genre, and make array of movies name in movies
$group by year and language and make array of with genre in k and v format
$group by year and make array of language after converting above genre array to object using $arrayToObject
convert language array to object using $arrayToObject, convert year with languages object to object using $arrayToObject and replace that object to root using $replaceRoot
db.getCollection('Movies').aggregate([
{
$group: {
_id: {
"year": "$year",
"language": "$language",
"genre": "$genre"
},
"movies": { "$push": { name: "$name" } }
}
},
{
$group: {
_id: {
year: "$_id.year",
language: "$_id.language"
},
"movies": {
"$push": {
k: "$_id.genre",
v: "$movies"
}
}
}
},
{
$group: {
_id: "$_id.year",
movies: {
$push: {
k: "$_id.language",
v: { $arrayToObject: "$movies" }
}
}
}
},
{
$replaceRoot: {
newRoot: {
$arrayToObject: [[
{
k: "$_id",
v: { $arrayToObject: "$movies" }
}
]]
}
}
}
])
Playground

Query multiple properties in at the same time getting an overall average and an array

Given the following data, I'm trying to get an average of all their ages, at the same time I want to return an array of their names. Ideally, I want to do this in just one query but can't seem to figure it out.
Data:
users:[
{user:{
id: 1,
name: “Bob”,
age: 23
}},
{user:{
id: 1,
name: “Susan”,
age: 32
}},
{user:{
id: 2,
name: “Jeff”,
age: 45
}
}]
Query:
var dbmatch = db.users.aggregate([
{$match: {"id" : 1}},
{$group: {_id: null, avg_age: { $avg: "$age" }}},
{$group: {_id : { name: "$name"}}}
)]
Running the above groups one at a time outputs the results I expect, either an _id of null and an average of 27.5, or an array of the names.
When I combine them as you see above using a comma, I get:
Issue Generated Code:
[ { _id: {name: null } } ]
Expected Generated Code:
[
{name:"Bob"},
{name:"Susan"},
avg_age: 27.5
]
Any help would be greatly appreciated!
Not sure if this is exactly what you want, but this query
db.users.aggregate([
{
$match: {
id: 1
}
},
{
$group: {
_id: "$id",
avg_age: {
$avg: "$age"
},
names: {
$push: {
name: "$name"
}
}
}
},
{
$project: {
_id: 0
}
}
])
Results in this result:
[
{
"avg_age": 27.5,
"names": [
{
"name": "Bob"
},
{
"name": "Susan"
}
]
}
]
This will duplicate names, so if there are two documents with the name Bob, it will be two times in the array. If you don't want duplicates, change $push to $addToSet.
Also, if you want names to be just an array of names instead of objects, change names query to
names: {
$push: "$name"
}
This will result in
[
{
"avg_age": 27.5,
"names": ["Bob", "Susan"]
}
]
Hope it helps,
Tomas :)
You can use $facet aggregation to run the multiple queries at once
db.collection.aggregate([
{ "$facet": {
"firstQuery": [
{ "$match": { "id": 1 }},
{ "$group": {
"_id": null,
"avg_age": { "$avg": "$age" }
}}
],
"secondQuery": [
{ "$match": { "id": 1 }},
{ "$group": { "_id": "$name" }}
]
}}
])

Unable to filter by date the array field of a Collection in MongoDB

I am struggling with MongoDb in order to achieve a desirable result.
My Collection looks like:
{
_id: ...
place: 1
city: 6
user: 306
createDate: 2014-08-10 12:20:21,
lastUpdate: 2014-08-14 10:11:01,
data: [
{
customId4: 4,
entryDate: 2014-07-12 12:01:11,
exitDate: 2014-07-12 13:12:12
},
{
customId4: 4,
entryDate: 2014-07-14 00:00:01,
},
{
customId4: 5,
entryDate: 2014-07-15 11:01:11,
exitDate: 2014-07-15 11:05:15
},
{
customId4: 5,
entryDate: 2014-07-22 21:01:11,
exitDate: 2014-07-22 21:23:22
},
{
customId4: 4,
entryDate: 2014-07-23 14:00:11,
},
{
customId4: 4,
entryDate: 2014-07-29 22:00:11,
exitDate: 2014-07-29 23:00:12
},
{
customId4: 5,
entryDate: 2014-08-12 12:01:11,
exitDate: 2014-08-12 13:12:12
},
]
}
So what I would like to achieve is the array data that meets the requirements of a certain interval and that has both, entryDate and exitDate values set.
For example, if I filter by the interval "2014-07-23 00:00:00 to 2014-08-31 00:00:00" I would like the result like:
{
result: [
{
_id: {
place: 1,
user: 306
},
city: 6,
place: 1,
user: 306,
data: [
{
customMap: 4,
entryDate: 2014-07-22 21:01:11,
exitDate: 2014-07-22 21:23:22
},
{
customId4: ,
entryDate: 2014-07-29 22:00:11,
exitDate: 2014-07-29 23:00:12
},
]
}
],
ok: 1
}
My custom mongodb query looks like (from, to and placeIds are variables properly configured)
db.myColl.aggregate(
{ $match: {
'user': 1,
'data.entryDate': { $gte: from, $lte: to },
'place': { $in: placeIds },
}},
{ $unwind : "$data" },
{ $project: {
'city': 1,
'place': 1,
'user': 1,
'lastUpdate': 1,
'data.entryDate': 1,
'data.exitDate': 1,
'data.custom': 1,
fromValid: { $gte: ["$'data.entryDate'", from]},
toValid: { $lte: ["$'data.entryDate'", to]}}
},
{ $group: {
'_id': {'place': '$place', 'user': '$user'},
'city': {'$first': '$city'},
'place': {'$first': '$place'},
'user': {'$first': '$user'},
'data': { '$push': '$data'}
}}
)
But this doesn't filter the way I want because it outputs every document that meets the $match operand conditions, inside the $project operand I am unable to define the condition (I don't know if this is how it has to be done in mongoDB)
Thanks in advance!
You were on the right track, but what you might be missing with the aggregation "pipeline" is that just like the "|" pipe operator in the unix shell you "chain" the pipeline stages together just as you would chain commands.
So in fact to can have a second $match pipeline stage that does the filtering for you:
db.myColl.aggregate([
{ "$match": {
"user": 1,
"data.entryDate": { "$gte": from, "$lte": to },
"place": { "$in": "placeIds" },
}},
{ "$unwind": "$data" },
{ "$match": {
"data.entryDate": { "$gte": from, "$lte": to },
}},
{ "$group": {
"_id": "$_id",
"place": { "$first": "$place" },
"city": { "$first": "$city" },
"user": { "$first": "$user" },
"data": { "$push": "$data" }
}}
])
Using the actual _id of the document as a grouping key presuming that you want the document back but just with a filtered array.
From MongoDB 2.6, as long as your matching array elements are unique, you could just do the same thing within $project using the $map and $setDifference** operators:
db.myColl.aggregate([
{ "$match": {
"user": 1,
"data.entryDate": { "$gte": from, "$lte": to },
"place": { "$in": "placeIds" },
}},
{ "$project": {
"place": 1,
"city": 1,
"user": 1,
"data": {
"$setDifference": [
{ "$map": {
"input": "$data",
"as": "el",
"in": {"$cond": [
{ "$and": [
{ "$gte": [ "$$el.entryDate", from ] },
{ "$lte": [ "$$el.entryDate", to ] }
]},
"$$el",
false
]}
}},
[false]
]
}
}}
])
That does the same logical thing by processing each array element and evaluating whether it meets the conditions. If so then the element content is returned, if not the false is returned. The $setDifference filters out all the false values so that only those that match remain.

Mongodb Aggregation count array/set size

Here's my problem:
Model:
{ application: "abc", date: Time.now, status: "1" user_id: [ id1, id2,
id4] }
{ application: "abc", date: Time.yesterday, status: "1", user_id: [
id1, id3, id5] }
{ application: "abc", date: Time.yesterday-1, status: "1", user_id: [
id1, id3, id5] }
I need to count the unique number of user_ids in a period of time.
Expected result:
{ application: "abc", status: "1", unique_id_count: 5 }
I'm currently using the aggregation framework and counting the ids outside mongodb.
{ $match: { application: "abc" } }, { $unwind: "$users" }, { $group:
{ _id: { status: "$status"},
users: { $addToSet: "$users" } } }
My arrays of users ids are very large, so I have to iterate the dates or I'll get the maximum document limit (16mb).
I could also $group by
{ year: { $year: "$date" }, month: { $month: "$date" }, day: {
$dayOfMonth: "$date" }
but I also get the document size limitation.
Is it possible to count the set size in mongodb?
thanks
The following will return number of uniqueUsers per application. This will apply an group operation to a result of a group operation by using pipeline feature of mongodb.
{ $match: { application: "abc" } },
{ $unwind: "$users" },
{ $group: { _id: "$status", users: { $addToSet: "$users" } } },
{ $unwind:"$users" },
{ $group : {_id : "$_id", count : {$sum : 1} } }
Hopefully this will be done in an easier way in the following releases of mongo by a command which gives the size of an array under a projection. {$project: {id: "$_id", count: {$size: "$uniqueUsers"}}}
https://jira.mongodb.org/browse/SERVER-4899
Cheers
Sorry I'm a little late to the party. Simply grouping on the 'user_id' and counting the result with a trivial group works just fine and doesn't run into doc size limits.
[
{$match: {application: 'abc', date: {$gte: startDate, $lte: endDate}}},
{$unwind: '$user_id'},
{$group: {_id: '$user_id'}},
{$group: {_id: 'singleton', count: {$sum: 1}}}
];
Use $size to get the size of set.
[
{
$match: {"application": "abc"}
},
{
$unwind: "$user_id"
},
{
$group: {
"_id": "$status",
"application": "$application",
"unique_user_id": {$addToSet: "$user_id"}
}
},
{
$project:{
"_id": "$_id",
"application": "$application",
"count": {$size: "$unique_user_id"}
}
}
]