Get only last created document per id when using $in - mongodb

I have 10.000 readings (documents) in MongoDB and I have an array of deviceIds. I need to get in Mongoose the lastly created document for each device.
My approach:
Reading.find({
"deviceId": { "$in": deviceIds}
},
function(err, readings) {
callback(err,readings);
}).sort({createdAt:-1}).limit(1);
This obviusly returns only one document. I need to get one per each deviceId in the deviceIds array.
EDIT 21/02/2018: Thanks to the help of #mickl I changed my code to:
Reading.aggregate([
{ $match: { "deviceId": { "$in": finalDevices} } },
{
$group: {
deviceId: "$deviceId",
maxCreatedAt: { $max: "$createdAt" }
}
}
],function(err, reading) {
if (err) {
callback(err, null);
} else {
callback(null, reading)
}
})
Now I am getting the following error: "The field 'deviceId' must be an accumulator object"

You can use aggregation framework's $match to filter your collection to specified devices and then use $group with $max to get the dates.
Device.aggregate([
{ $match: { "deviceId": { "$in": deviceIds} } },
{
$group: {
_id: "$deviceId",
maxCreatedAt: { $max: "$createdAt" }
}
}
],function(err, readings) {
callback(err,readings);
})

Related

MongoDB sorting does not work with inner array

I'm trying to query specific fields in my document and sort them by one of the fields, however, the engine seems to completely ignore the sort.
I use the query:
db.symbols.find({_id:'AAPL'}, {'income_statement.annual.totalRevenue':1,'income_statement.annual.fiscalDateEnding':1}).sort({'income_statement.annual.totalRevenue': 1})
This is the output:
[
{
_id: 'AAPL',
income_statement: {
annual: [
{
fiscalDateEnding: '2021-09-30',
totalRevenue: '363172000000'
},
{
fiscalDateEnding: '2020-09-30',
totalRevenue: '271642000000'
},
{
fiscalDateEnding: '2019-09-30',
totalRevenue: '256598000000'
},
{
fiscalDateEnding: '2018-09-30',
totalRevenue: '265595000000'
},
{
fiscalDateEnding: '2017-09-30',
totalRevenue: '229234000000'
}
]
}
}
]
I would expect to have the entries sorted by fiscalDateEnding, starting with 2017-09-30 ascending.
However, the order is fixed, even if I use -1 for sorting.
Any ideas?
The sort you are using is for the ordering of documents in the result set. This is different from the ordering of array elements inside the document.
For your case, if you are using a newer version of MongoDB (5.2+), you can use the $sortArray.
db.symbols.aggregate([
{
$project: {
_id: 1,
annual: {
$sortArray: {
input: "$income_statement.annual",
sortBy: {
fiscalDateEnding: 1
}
}
}
}
}
])
If you are using older version of MongoDB, you can do the followings to perform the sorting.
db.collection.aggregate([
{
"$unwind": "$income_statement.annual"
},
{
$sort: {
"income_statement.annual.fiscalDateEnding": 1
}
},
{
$group: {
_id: "$_id",
annual: {
$push: "$income_statement.annual"
}
}
},
{
"$project": {
_id: 1,
income_statement: {
annual: "$annual"
}
}
}
])
Here is the Mongo Playground for your reference.

Delete document that has size greater than a specific value

I have a collection which contains a multiple documents whose size has increased from 16MBs or is about to reach 16MBs.
I want query that finds documents which have size greater than 10MBs and delete all of them.
I am using following to find the size of document.
Object.bsonsize(db.test.findOne({type:"auto"}))
Is there a way to embed this query inside db.test.deleteMany() query?
This following query deletes the documents with size greater than the specified size (the size is specified in bytes). This query is valid with MongoDB v4.4 or higher.
db.collection.deleteMany( {
$expr: { $gt: [ { $bsonSize: "$$ROOT" }, SIZE_LIMIT ] },
type: "auto"
} )
The following script runs for MongoDB v4.2 or earlier:
const SIZE_LIMIT = 75 // substitute your value here in bytes
let idsToDelete = [ ]
let crsr = db.collection.find()
while(crsr.hasNext()) {
let doc= crsr.next()
if (Object.bsonsize(doc) > SIZE_LIMIT) {
idsToDelete.push(doc._id)
}
}
db.collection.deleteMany( { _id: { $in: idsToDelete } } )
I think you have to do it like this:
db.test.aggregate([
{ $match: { type: "auto" } },
{ $project: { bsonSize: { $bsonSize: "$$ROOT" } } },
{ $match: { bsonSize: { $gt: 16e6 } } },
]).forEach(function (doc) {
db.test.deleteOne({ _id: doc._id });
})
Or if you prefer deleteMany:
var ids = db.test.aggregate([
{ $match: { type: "auto" } },
{ $project: { bsonSize: { $bsonSize: "$$ROOT" } } },
{ $match: { bsonSize: { $lt: 16e6 } } }
]).toArray().map(x => x._id);
db.test.deleteMany({ _id: { $in: ids } });

Mongodb update duplicate array value

I am getting in an array of data and then want to insert that into my mongodb. I want to overwrite any duplicate values with the new one (on Im uploading) and if no duplicates just add it onto the current array.
I currently have:
db.cases.updateOne(
{ companyID: 218 },
{
$addToSet: {
cases: [AN ARRAY OF CASES]
},
$currentDate: { lastModified: true }
})
The collection has multiple companies and each has an array of cases - see image below :
The other thing that doesn't seem to work is that the currentDate doesn't seem to change whenever I update the cases, not sure if thats the way I have written the query?
Thank you.
You can use $addToSet (https://docs.mongodb.com/manual/reference/operator/update/addToSet/) with $[] (https://docs.mongodb.com/manual/reference/operator/update/positional-filtered/) to accomplish this.
I scripted an example in Node.js to show you what I mean:
const { MongoClient } = require('mongodb');
async function main() {
/**
* Connection URI. Update <username>, <password>, and <your-cluster-url> to reflect your cluster.
*/
const uri = "mongodb+srv://<username>:<password>#<your-cluster-url>?retryWrites=true&w=majority";
/**
* The Mongo Client you will use to interact with your database
*/
const client = new MongoClient(uri, { useUnifiedTopology: true });
try {
// Connect to the MongoDB cluster
await client.connect();
// Make the appropriate DB calls
await updateArray(client, 'id1')
} finally {
// Close the connection to the MongoDB cluster
await client.close();
}
}
main().catch(console.error);
async function updateArray(client, id) {
const docId = "299";
const mynewdoc = {
id: docId,
priority: 5,
casenumber: 40,
new: "field"
}
const result = await client.db("NameOfYourDb").collection("NameOfYourCollection").updateOne(
{ _id: id },
{
$set: {
"cases.$[element]": mynewdoc
}
},
{
arrayFilters: [{ "element.id": docId }]
}
)
if (result) {
console.log(result);
} else {
console.log(`Not found '${id}'`);
}
const result2 = await client.db("NameOfYourDb").collection("NameOfYourCollection").updateOne(
{ _id: id },
{
$addToSet: {
"cases": mynewdoc
}
}
)
if (result2) {
console.log(result2);
} else {
console.log(`Not found '${id}'`);
}
}
See https://www.mongodb.com/blog/post/quick-start-nodejs-mongodb--how-to-get-connected-to-your-database for an explanation of how the Node.js code is structured.
So I have figured a hack around but it is not exactly what I wanted - maybe someone can use this and expand upon it to make it work better:
db.cases.aggregate(
{ $match: { 'companyID': 218 }},
{ $unwind: '$cases' },
{ $group: { "_id": "$cases.casenumber", cases: {$push:'$cases'}, "count": { $sum:1 }}},
{ $match: { "count": { "$gt": 1 }}}
{ $project: { "cases": { $slice: [ "$cases", -1, { $subtract: [ { $size: "$cases" }, 1 ]}] }}},
{ $project: { "cases": { $arrayElemAt: ['$cases', 0] }}},
{ $group: { _id: 1, cases: {$push:'$cases' }}},
{ $out: 'cases' }
)
The only problem is that using out overwrites the cases document so I need to figure out a way to write it to the cases array inside of the company id (218)
Hopefully this can help someone though.
Thanks.

return only the last level of the embedded property that is searched in a document

I have these documents.
db.test.find({"house.floor":1})
db.test.insertMany([{
"name":"homer",
"house": {
"floor": 1,
"room":
{
"bed": "bed_pink",
"chair":"chair_pink"
}
}
},
{
"name":"marge",
"house": {
"floor": 1,
"room":
{
"bed": "bed_blue",
"chair":"chair_red"
}
}
}]
)
db.test.find({"house.room.bed":"bed_blue"})
I want to return only the value of the last level of my search. in this case:
{
"bed": "bed_blue",
"chair":"chair_red"
}
the answer I get, is the whole document, I want to advance to a certain level of the query. how can I do it?
You can use aggregation for it
db.test.aggregate([
{ $match: { "house.room.bed":"bed_blue" } },
{ $replaceRoot: { newRoot: "$house.room" } }
])
You can use aggregate together with $match and $project for this as follows:
db.test.aggregate([
{
$match: {
"house.room.bed": "bed_blue"
}
},
{
$project: {
"_id": 0,
"bed": "$house.room.bed",
"chair": "$house.room.chair"
}
}
])
Further, you can modify the $project part as needed.
Here is a demo: https://mongoplayground.net/p/Fvll0LFIvQy

$match not finding objectID in mongoose 5.6+

I have checked various posts already made on this topic before , but none of the suggestion worked for me. I have seen those suggestions will not work in mongoose 5.0. I am using mongoose 5.6. I tried with find(), which is working. but I need aggregation fm for extended usage. most of the suggestions were on converting record id mongoose.Types.ObjectId() which I used . but not worked me. pl help
here is my test code.
function totalActualSale (recordId) {
Record.aggregate([
{
$match : {
'_id':mongoose.Types.ObjectId(recordId),
}
},
{
$unwind: { "path": '$SalesList'}
},
{ $group:
{_id:
{
item:"$SalesList.item",
},
pqty: { $sum: '$SalesList.pqty' },
}
},
], function (err, purc) {
console.log("\nStock:"+ JSON.stringify(purc));
});
}
try this :
function totalActualSale (recordId) {
Record.aggregate([
{
$match : {
'_id':new mongoose.mongo.ObjectId(recordId),
}
},
{
$unwind: { "path": '$SalesList'}
},
{ $group:
{_id:
{
item:"$SalesList.item",
},
pqty: { $sum: '$SalesList.pqty' },
}
},
], function (err, purc) {
console.log("\nStock:"+ JSON.stringify(purc));
});
}
Actually the actual issue was just reverse. the 'recordId' passed to the function was the ObjectID of a record. so, when comparing we should convert that into string. recordId.toString(). So the solution is
function totalActualSale (recordId) {
Record.aggregate([
{
$match : {
'_id':recordId.toString(),
}
},
{
$unwind: { "path": '$SalesList'}
},
{ $group:
{_id:
{
item:"$SalesList.item",
},
pqty: { $sum: '$SalesList.pqty' },
}
},
], function (err, purc) {
console.log("\nStock:"+ JSON.stringify(purc));
});
}