Sort subdocument using mongoose (MongoDB) - node.js

I am trying to do something very simple but an new to MongoDB! I have a document called Device and a sub-document called Movement. I want to get the last two movement sub-documents out of Device ordered by last_seen (a date). Here is what I have along with the error I am getting:
Device.findOne({device_id: "1234"}, {movements: { $sort: {last_seen: -1}, $slice: 2 }}, function(err, device){
...
});
The Error:
MongoError: >1 field in obj: { $sort: { last_seen: -1 }, $slice: 2 }

You can use aggregate:
Device.aggregate(
{ $match: { device_id: "1234"}}, // query documents (can return more than one element)
{ $unwind: '$movements'}, //deconstruct the documents
{ $sort: { '$movements.last_seen': -1}},
{ $limit: 2 },
{ $group: { _id: '$device_id', movements: { $push: '$movements }}} //reconstruct the documents
function(err, devices){
//returns an array, probably with one elements depending on `$match` query
});

Related

Mongoose get position of a data

I'm using this code to get my user leaderboard
var data = await db.models.user
.find({ points: { $ne: 0 } })
.collation({locale: "en_US", numericOrdering: true})
.sort('-points')
.limit(10)
.skip(page*10-10)
But I can't find how to get users position in the all leaderboard. How can I get it?
I don't think there is any good approach or method to do this, but you still want to do it then try using aggregate() and $unwind stage,
$match your conditions
$sort by points in descending order
$group by null and make array of all documents in docs
$unwind deconstruct docs array and pass includeArrayIndex: 'position' this will create a ordered index field position in each document, starting from zero(0),
$skip and $limit stages
var data = await db.models.user.aggregate([
{ $match: { points: { $ne: 0 } } },
{ $sort: { points: -1 } },
{
$group: {
_id: null,
docs: { $push: "$$ROOT" }
}
},
{
$unwind: {
path: "$docs",
includeArrayIndex: "position"
}
},
{ $skip: 10 },
{ $limit: page*10-10 }
])
Playground
This may heavy operation, might be take more execution time in big data!

MongoDB find $in sorting issue

I need to get docs from MongoDB collection where ID's are in array:
[
'5f80a44d0179262f7c2e6a42',
'5f8c00762fae890e9c4d029c',
'5f802cf8abac1116a46bf9d4'
]
The issue is, docs are not coming in sequence of my array ID's. They are coming (1, 0, 2) for above array ID's.
How can I make them in sequence of my ID's array? I am using, NodeJs + Mongoose.
My code:
var ids = ['5f80a44d0179262f7c2e6a42','5f8c00762fae890e9c4d029c','5f802cf8abac1116a46bf9d4']
Product.find({
_id: {
$in: ids
}
})
I don't think its possible with find(), or any functionality available in MongoDB related to this,
It is possible with aggregate() but this will just fulfil your expected result,
I am not recommending to use this because this will affect performance of query response, this could be a heavy transaction.
$match your conditions
$group by null and make array of all matching objects in root
$addFields to add ids array that we search for
$unwind deconstruct ids array, this will deconstruct in sequence as per we provided in ids array
$project, $reduce input as root array and check condition if id match then return object
$replaceWith to replace root object to root
var ids = [
ObjectId("5f802cf8abac1116a46bf9d4"),
ObjectId("5f8c00762fae890e9c4d029c"),
ObjectId("5f80a44d0179262f7c2e6a42")
];
Product.aggregate([
{ $match: { _id: { $in: ids } } },
{
$group: {
_id: null,
root: { $push: "$$ROOT" }
}
},
{ $addFields: { ids: ids } },
{ $unwind: "$ids" },
{
$project: {
root: {
$reduce: {
input: "$root",
initialValue: {},
in: { $cond: [{ $eq: ["$$this._id", "$ids"] }, "$$this", "$$value"] }
}
}
}
},
{ $replaceWith: "$root" }
])
Playground

Using $lookup and $group to aggregate data

I am aggregating a large data where i need to group the data according to their types and also i need to lookup the data from another collections.inside $group i want my lookup's data.
my code for aggregation goes like :
NotificationSchema.aggregate([{
$match: condition
}, {
$group: {
_id: "$type",
details: {
$push: "$$ROOT"
},
count: {
$sum: 1
}
}
}, {
$sort: {
_id: -1
}
}, {
$lookup: {
from: "vehicles",
localField: "details.device_id",
foreignField: "device_id",
as: "vehicle"
}
}], function(err, result) {
if (err) {
res.status(500);
return res.json({
result: err
});
}
console.log('res', result[0].details[0]);
res.json({
result: result
});
});
if i remove or comment the $group code i get the data with Vehicle array but using $group i get vehicle array empty, as i have only two types in records in the database, i get two empty array of vehicles. but i have 102 records so i need 102 arrays of vehicles how can i get such result.
what i am getting in console right now is
res [ { _id: 'Vehicle Delay Alert!',
details:
[ [Object],
....57 object...
[Object] ],
count: 57,
vehicle: [] },
and inside every object i dont find vehicle array so i wish to remove vehicle array from here and get a vehicle array that is generated from $lookup inside every object.
Any suggestions are highly appreciated.
You $lookup from details.device_id which comes from an array. To $lookup from a regular field, you can place $lookup after the $match :
NotificationSchema.aggregate([{
$match: condition
}, {
$lookup: {
from: "vehicles",
localField: "device_id",
foreignField: "device_id",
as: "vehicle"
}
}, {
$group: {
_id: "$type",
details: {
$push: "$$ROOT"
},
count: {
$sum: 1
}
}
}, {
$sort: {
_id: -1
}
}])

How to get documents with non unique array elements?

I have the following MongoDB documents:
{
_id: ObjectId('09de14821345dda65c471c99'),
items: [
_id: ObjectId('34de64871345dfa655471c99'),
_id: ObjectId('34de64871345dfa655471c91'),
_id: ObjectId('34de64871345dfa655471c99'),
]
},
{
_id: ObjectId('09de14821345dda65c471c98'),
items: [
_id: ObjectId('24de64871345dfa61271c10'),
_id: ObjectId('24de64871345dfa61271c11'),
_id: ObjectId('24de64871345dfa61271c11'),
]
},
{
_id: ObjectId('09de14821345dda65c471c07'),
items: [
_id: ObjectId('24de64871345dfa61271c05'),
_id: ObjectId('24de64871345dfa61271c06'),
_id: ObjectId('24de64871345dfa61271c07'),
]
}
I need to find all documents with repeated items array elements. So from the documents above I want to get the following result:
db.collection.documents.find({/** need query*/}).toArray(function (err, documents) {
console.dir(documents); // documents with id's 09de14821345dda65c471c99 and 09de14821345dda65c471c98
});
How could I do that?
In order to group and match results you will need to use the Aggregation Framework or Map/Reduce rather than a simple find() query.
Example data
Your example document include some errors: a few of the ObjectIDs are too short and the array elements should either be embedded documents ({_id: ObjectId(...)}) or simple values.
For test data I've used:
db.mydocs.insert([
{
_id: ObjectId('09de14821345dda65c471c99'),
items: [
ObjectId('34de64871345dfa655471c99'),
ObjectId('34de64871345dfa655471c91'),
ObjectId('34de64871345dfa655471c99')
]
},
{
_id: ObjectId('09de14821345dda65c471c98'),
items: [
ObjectId('24de64871345ddfa61271c10'),
ObjectId('24de64871345ddfa61271c11'),
ObjectId('24de64871345ddfa61271c11')
]
},
{
_id: ObjectId('09de14821345dda65c471c07'),
items: [
ObjectId('24de64871345ddfa61271c05'),
ObjectId('24de64871345ddfa61271c06'),
ObjectId('24de64871345ddfa61271c07')
]
}])
Aggregation query
Here is an aggregation query using the mongo shell:
db.mydocs.aggregate(
// Unpack items array into stream of documents
{ $unwind: "$items" },
// Group by original document _id and item
{ $group: {
_id: { _id: "$_id", item: "$items" },
count: { $sum: 1 }
}},
// Limit to duplicated array items (1 or more count per document _id)
{ $match: {
count: { $gt: 1 }
}},
// (Optional) clean up the result formatting
{ $project: {
_id: "$_id._id",
item: "$_id.item",
count: "$count"
}}
)
Sample results
{
"_id" : ObjectId("09de14821345dda65c471c98"),
"count" : 2,
"item" : ObjectId("24de64871345ddfa61271c11")
}
{
"_id" : ObjectId("09de14821345dda65c471c99"),
"count" : 2,
"item" : ObjectId("34de64871345dfa655471c99")
}

Count(field) on Mongo

I have a collection of songs and its metadata with the following structure:
[{
title:"title",
artist:"artist",
album:"album,
...
},...
Now I want to get a list of every artist with the number of songs and the number of albums it has using Node.js. So far, using the aggregation framework, I've been able to get an array of objects with each artist, its number of songs and an array with the album titles (instead of just the count), using the following pipeline:
collection.aggregate([
{ $project:{
artist:1,
album:1
}},
{ $group: {
_id: "$artist",
songs:{$sum: 1},
albums:{$addToSet:"$album"}
}},
{ $sort: { artist: 1 } }
]
If I replace $addToSet with $sum, I get albums:0 in every artist, because it expects numbers and not strings to sum.
I just can't get around it!
You need to add a couple of steps to your pipeline - the array of albums needs to be unwound and then counted. Here is what it would look like:
collection.aggregate([
{ $project:{
artist:1,
album:1
}},
{ $group: {
_id: "$artist",
songs:{$sum: 1},
albums:{$addToSet:"$album"}
}},
{ $unwind: "$albums"},
{ $group: {
_id: "$_id",
songs:{$first: 1},
albums:{$sum: 1}
}},
{ $sort: { artist: 1 } }
]
)

Resources