Display object list with a parameter on Mongoose - mongodb

I have a find query that returns me a list of objects:
{
"_id": "5fb94fda487b9348c4291450",
"name": [
{
"NewConfirmed": 642686,
"TotalConfirmed": 49315431,
"NewDeaths": 9555,
"TotalDeaths": 1242785,
"NewRecovered": 288131,
"TotalRecovered": 32473892
},
{
"NewConfirmed": 116262,
"TotalConfirmed": 6014461,
"NewDeaths": 4640,
"TotalDeaths": 371913,
"NewRecovered": 77575,
"TotalRecovered": 2492884
},
{
...
Its all fine but I'm trying to make a new query with a status parameter with the value NewConfirmed or TotalConfirmed or NewDeaths to display only that specific field. So the endpoints would look like /something/status/:status.
I already tried an aggregation with filter and a simple find but still havent figured nothing out.
Anyone has any idea?

First of all, you need a query with this estructure:
db.collection.aggregate([
{
/**Your match object*/
},
{
"$project": {
"YourStatus": {
"$first": "$name.YourStatus"
}
}
}
])
Example here.
Using mongoose you need to create the object query in this way:
var query = {}
query[status] = {"$first": "$name."+status}
And do the mongoose query replacing the object by query object.
var aggregate = await model.aggregate([
{
//Your $match stage here
},
{
"$project": query
}
])
Also, I've tested in local but my mongo version (I think) doesn't recognize $first so I've used $arrayElemAt. According to mongo docs is the same as $first.
var status = "NewConfirmed"
var query = {}
query[status] = { $arrayElemAt: ["$name."+status, 0]}
Also you can add _id: 0 into $project aggregate to not return this field.
var query = {_id:0} //Here add _id: 0 to project object
query[status] = { $arrayElemAt: ["$name."+status, 0]} //And the rest of the stage

Related

Mongoose: search for ObjectID by Array

I want to filter my collection by aggregation for one of many ObjectIDs.
Because of some DocumentDB restrictions I can not build a single pipeline with uncorrelated subqueries. So my fix is to do it in two queries.
for example: I have an aggregation that returns all teamIds, for some conditions as an array of Object with the IDs.
[{_id: ObjectID("abcdef")}, {_id: ObjectID("ghijkl")}, {_id: ObjectID("vwxyz")}, ...]
I now want to have a second aggregation filter another collection using the ObjectIDs.
This would work in Mongo Compass:
{
"team": {
"$in": [ObjectId("60aabcb05c7462f42b3d7zyx"), ObjectId("60aabc7b05c7462f42b3dxyz")]
},
....
}
My issue is that i can not find the correct syntax for JS to generate such a pipeline.
What ever I try, JS always converts my Array of ObjectIDs to something like this:
{
"team": {
"$in": [{
"_id": "60aabcb05c7462f42b3d7zyx"
},{
"_id": "60aabc7b05c7462f42b3dxyz"
}]
},
I fixed it like this. I am not 100% why this syntax works because it is still just an array of objects, formatted like before, but I guess there is some stuff mongoose does, that is opaque to me.
let teams = await TeamMgmt.getTeamsAggregatedByFilter( teamFilter )
// make an array of ObjectIds so we can filter for them.
let idArray = []
Object.keys( teams ).map( function ( key, index ) {
idArray.push( new mongoose.Types.ObjectId( teams[ index ]._id.toString() ) )
} );
const shiftFilter = [
{
'$match': {
'team': {
"$in": idArray
},
....
}

Aggregate and update with mongoose

I want to use the aggregate method to make my querys and modify my database by a value, I tried with $set but my database is not modified.
Here is how I do my query:
var filter = req.body.filter
var search = [ { $match: filter }, { $set: {item: "2"} }, { $sample: { size: 1 } }]
const result = await dataModel.aggregate(search)
I know there is also findOneAndUpdate but I would like to keep aggregate because I also want to use $project in my pipelines
thanks in advance !
You can use FindOneAndUpdate for change your db

How to deselect one object in array MongoDB? [duplicate]

This question already has answers here:
Retrieve only the queried element in an object array in MongoDB collection
(18 answers)
Closed 3 years ago.
In my situation I just need my result but without my objectID in my array.
This is my method :
return Room.findOne(
{
_id: idRoom,
participants: {$elemMatch: {$ne: this.currentUser.profile}},
},
{
'participants.$': 1,
}
)
With elementMatch, the problem is when you found the object, only the first object is returned.
This is my result :
"result": {
"_id": "5da5e77f51e08708b79565e8",
"participants": [
"5da4d5b40cc94f04a7aaad40"
],
And this is the real result I need
"result": {
"_id": "5da5e77f51e08708b79565e8",
"participants": [
"5da4d5b40cc94f04a7aaad40"
"fwnert9248yrhnqwid13982r" // I have another participants
],
And my model is just like this :
const RoomSchema = new Schema({
participants: [{type: Schema.Types.ObjectId,ref: 'Profile'}],
...
}, options)
For others reasons, I can't use aggregate, thank you if you have the solution
So I think you are trying to shape a resultset in mongo with the findOne() method, and any use of the aggregation pipeline framework is out of the question and unavailable for other reasons.
I am not sure this is possible. I believe you will need to perform multiple steps to achieve your desired results. If you can use aggregation pipeline framework here is a pipeline to suit the desired results (I believe)...
db.Room.aggregate(
[
{
"$match": { _id: ObjectId(idRoom)}
},
{
$project: {
"participants": {
$filter: {
input: "$participants",
as: "participant",
cond: {$ne: ["$$participant", this.currentUser.profile] }
}
}
}
}
]
)
...but if you cannot use aggregation pipeline then here is a mongoshell script that accomplishes the task in several steps. The strategy is to capture the whole document by _id then remove the data element from the array then echo the results...
var document = db.Room.findOne( { _id: ObjectId("5da64a62cd63abf99d11f210") } );
document.participants.splice(document.participants.indexOf("5da4d5b40cc94f04a7aaad40"), 1);
document;

Most efficient way to put fields of an embedded document in its parent for an entire MongoDB collection?

I am looking for the most efficient way to modify all the documents of a collection from this structure:
{
[...]
myValues:
{
a: "any",
b: "content",
c: "can be found here"
}
[...]
}
so it becomes this:
{
[...]
a: "any",
b: "content",
c: "can be found here"
[...]
}
Basically, I want everything under the field myValues to be put in its parent document for all the documents of a collection.
I have been looking for a way to do this in a single query using dbCollection.updateMany(), put it does not seem possible to do such thing, unless the content of myValues is the same for all documents. But in my case the content of myValues changes from one document to the other. For example, I tried:
db.getCollection('myCollection').updateMany({ myValues: { $exists: true } }, { $set: '$myValues' });
thinking it would perhaps resolve the myValues object and use that object to set it in the document. But it returns an error saying it's illegal to assign a string to the $set field.
So what would be the most efficient approach for what I am trying to do? Is there a way to update all the documents of the collection as I need in a single command?
Or do I need to iterate on each document of the collection, and update them one by one?
For now, I iterate on all documents with the following code:
var documents = await myCollection.find({ myValues: { $exists: true } });
for (var document = await documents.next(); document != null; document = await documents.next())
{
await myCollection.updateOne({ _id: document._id }, { $set: document.myValues, $unset: { myValues: 1} });
}
Since my collection is very large, it takes really long to execute.
You can consider using $out as an alternative, single-command solution. It can be used to replace existing collection with the output of an aggregation. Knowing that you can write following aggregation pipeline:
db.myCollection.aggregate([
{
$replaceRoot: {
newRoot: {
$mergeObjects: [ "$$ROOT", "$myValues" ]
}
}
},
{
$project: {
myValues: 0
}
},
{
$out: "myCollection"
}
])
$replaceRoot allows you to promote an object which merges the old $$ROOT and myValues into root level.

Replace a word from a string

I have mongodb documents with a field like this:
Image : http://static14.com/p/Inc.5-Black-Sandals-5131-2713231-7-zoom.jpg
How can I replace the zoom part in the string value with some other text in order to get:
Image : http://static14.com/p/Inc.5-Black-Sandals-5131-2713231-7-product2.jpg
You could use mongo's forEach() cursor method to do an atomic update with the $set operator :
db.collection.find({}).snapshot().forEach(function(doc) {
var updated_url = doc.Image.replace('zoom', 'product2');
db.collection.update(
{"_id": doc._id},
{ "$set": { "Image": updated_url } }
);
});
Given a very large collection to update, you could speed up things a little bit with bulkWrite and restructure your update operations to be sent in bulk as:
var ops = [];
db.collection.find({}).snapshot().forEach(function(doc) {
ops.push({
"updateOne": {
"filter": { "_id": doc._id },
"update": { "$set": { "Image": doc.Image.replace('zoom', 'product2') } }
}
});
if ( ops.length === 500 ) {
db.collection.bulkWrite(ops);
ops = [];
}
})
if ( ops.length > 0 )
db.collection.bulkWrite(ops);
db.myCollection.update({image: 'http://static14.com/p/Inc.5-Black-Sandals-5131-2713231-7-zoom.jpg'}, {$set: {image : 'http://static14.com/p/Inc.5-Black-Sandals-5131-2713231-7-product2.jpg'}})
If you need to do this multiple times to multiple documents, you need to iterate them with a function. See here: MongoDB: Updating documents using data from the same document
Nowadays,
starting Mongo 4.2, db.collection.updateMany (alias of db.collection.update) can accept an aggregation pipeline, finally allowing the update of a field based on its own value.
starting Mongo 4.4, the new aggregation operator $replaceOne makes it very easy to replace part of a string.
// { "Image" : "http://static14.com/p/Inc.5-Black-Sandals-5131-2713231-7-zoom.jpg" }
// { "Image" : "http://static14.com/p/Inc.5-Black-Sandals-5131-2713231-7-boom.jpg" }
db.collection.updateMany(
{ "Image": { $regex: /zoom/ } },
[{
$set: { "Image": {
$replaceOne: { input: "$Image", find: "zoom", replacement: "product2" }
}}
}]
)
// { "Image" : "http://static14.com/p/Inc.5-Black-Sandals-5131-2713231-7-product2.jpg" }
// { "Image" : "http://static14.com/p/Inc.5-Black-Sandals-5131-2713231-7-boom.jpg" }
The first part ({ "Image": { $regex: /zoom/ } }) is just there to make the query faster by filtering which documents to update (the ones containing "zoom")
The second part ($set: { "Image": {...) is the update aggregation pipeline (note the squared brackets signifying the use of an aggregation pipeline):
$set is a new aggregation operator (Mongo 4.2) which in this case replaces the value of a field.
The new value is computed with the new $replaceOne operator. Note how Image is modified directly based on the its own value ($Image).