mongodb make dynamic query for sort field options - arrays

I'm using aggregate query for showing some results given some code snippets
if (name) {
sort = { '$sort': { name: -1 } }
} else {
sort = ''
}
User.aggregate([sort, { $match: {} }])
Problem is when value of name is not set aggregate query is giving error how to make this aggregate query dynamic in nodejs. I need to fit sort option anywhere depending on filter select by user.

this should work :
var query = [{
$match: {}
}]
if(name)
{
query.unshift({ '$sort': { name: -1 } });
}
User.aggregate(query)

Related

Mongoose FindOne - only return fields which match condition

I am trying to query my collection of matches (games) and find if a certain user has already sent data to the 'reportMessages' array of Objects.
const results = await Match.findOne({ 'users': req.params.userIdOfReportSender, '_id': req.params.matchId, 'reportMessages.sentBy': req.params.userIdOfReportSender }, 'reportMessages' )
However, the above query returns the following:
{
_id: 5fd382c65d5395e0778f2f8a,
reportMessages: [
{
_id: 5fd610f27ae587189c45b6ca,
content: 'jajatest',
timeStamp: 2020-12-13T13:02:42.102Z,
sentBy: 'XbVvm6g3nsRmPg3P1pBvVl84h6C2'
},
{ sentBy: "'anotheruser123" }
]
}
How can I get it to only return the first reportMessage, i.e. the one sent by XbVvm6g3nsRmPg3P1pBvVl84h6C2?
Mongoose findOne docs (https://mongoosejs.com/docs/api.html#model_Model.findOne) show that you can provide arguments to say which fields to select (in their case 'name length' but don't show a way to only select the fields in case they match a certain condition.
Is this even possible? Tried googling this seemingly easy question for quite some time without success
Kind regards
You can get only the subdocument you want with this aggregation query:
Match.aggregate([
{
$match: { _id: req.params.matchId }
},
{
$project: {
reportMessages: {
$filter: {
input: '$reportMessages',
as: 'msg',
cond: { $eq: ['$$msg.sentBy', req.params.userIdOfReportSender] }
}
}
}
},
{
$project: {
reportMessage: { $arrayElemAt: [ '$reportMessages', 0 ] },
}
},
{ $replaceWith: '$reportMessage' }
]);
Note that you only need to specify the document _id to get a single result, since _ids are unique.

How to pull data from deeply nested array in mongodb?

Hi I have deeply nested array in my mongoose schema. I want to pull record from that deeply nested array. Can anyone please help in writing the moongoose query. I tried this
var condition = {
_id: user,
$and: [
{
'cardInfo.cards': {
$elemMatch: {
_id: cardId,
isActive: '0'
}
}
},
{
'cardInfo.cards': {
$elemMatch: {
_id: { $exists: true }
}
}
}
]
};
var dataToUpdate = {
$pull: {'cardInfo.cards': { _id: cardId }}
};
var options = {
lean: true
}
for schema please look at MyAnotherQuestion and please try to answer that question as well. Above query is not working but in mongodb it is working fine if I use ObjectId for cardId
Ok I have been able to resolve the issue. What I did is just added an another parameter in options variable like:
var options = { strict: false, lean: true}
strict: false is the parameter which made my query to work and my mongoose query is same i.e
Customer.update(condition, dataToUpdate , options, anycallback)
and yes it is working for me.

Mongodb: Querying array of subdocuments

I have users' collection whose schema is like:
{
_id: unique number,
name: 'asdf',
age: '12',
gender: 'm',
address: [
{area: 'sdf',
city: 'sdq',
state: 'wfw'},
{area: 'asdf',
city: 'sdfs',
state: 'vfdwd'}
]
}
I want to find out the users for whom all the values of state in address should be the value I pass. If even one of the state value doesn't match with the value I pass the user shouldn't be returned.
I tried simple find, aggregation framework with $unwind, $match but nothing seemed to get solution. Can you please help me out...
Thanks
P.S. please bear with multiple addresses for the sake of question. :)
To find out if all array entries match the state "wfw", do an aggregation like the following:
db.users.aggregate([
{ "$project" : {
"test" : {
"$allElementsTrue" : [{
"$map" : {
"input" : "$address",
"as" : "a",
"in" : { "$eq" : ["wfw", "$$a.state"] }
}
}]
}
} },
{ "$match" : { "test" : true } }
])
This aggregation takes each document, maps "state equals 'wfw'" over the address array to get a boolean array, and tests if the entire array is true, storing the result in `test, and then filtering the results based on test. You will need MongoDB 2.6 for support of some of the operators.
I don't know if I understand.
I replicated your document. When you want to retrieve an user by state you can do in many ways
If you search with single value you can do
db.g.find({ "address.state": "wfw" })
and retrieve an user
You can use $all
db.g.find( { "address.state": { $all: ["wfw","vfdwd"] } } ) // retrieve User
db.g.find( { "address.state": { $all: ["wfw","vfdwd","foo"] } } ) // don't retrieve User
or you can use $and
db.g.find( { $and: [ { "address.state":"wfw" },{ "address.state":"vfdwd" }] } )
But I don't know if I understand your question
Update and the correct answer
db.g.find( { "address.state": { $nin: ["wfw"] } } )
Let me Know

MongoDB - Querying based on the value of a property of an array of objects

Brand new to MongoDB. I'm having difficulty querying by a property of an object nested in an array.
I have the following data structure:
{
"sales":[
{
"item":1234,
"seller":"SellerA",
"buyer":"BuyerA"
},
{
"item":5678,
"seller":"SellerB",
"buyer":"SellerB"
},
{
"item":1122,
"seller":"SellerA",
"buyer":"BuyerA"
},
{
"item":1199,
"seller":"SellerB",
"buyer":"SellerB"
}
]
}
I have other properties at the same level as the "sales" array, but I've simplified for the example.
I am attempting to pull every sales document which has a buyer value of "BuyerB."
I've tried the following:
db.data.find({"sales": {$elemMatch: {buyer: "BuyerB"}}})
db.data.find({"sales.buyer": "BuyerB"}})
These queries run, but return every row of data and not just the ones that have a buyer value of "BuyerB"
I know there has to be a simple answer, but what my searches have turned up is what I've already tried without success.
As you may return more than one element from an array and .find() cannot do this, you can try aggregate such as:
db.c.aggregate({
$match : {
"sales.buyer" : "BuyerB"
}
}, {
$unwind : "$sales"
}, {
$match : {
"sales.buyer" : "BuyerB"
}
}, {
$group : {
_id : "$_id",
sales : {
$push : "$sales"
}
}
});

MongoDB rename database field within array

I need to rename indentifier in this:
{ "general" :
{ "files" :
{ "file" :
[
{ "version" :
{ "software_program" : "MonkeyPlus",
"indentifier" : "6.0.0"
}
}
]
}
}
}
I've tried
db.nrel.component.update(
{},
{ $rename: {
"general.files.file.$.version.indentifier" : "general.files.file.$.version.identifier"
} },
false, true
)
but it returns: $rename source may not be dynamic array.
For what it's worth, while it sounds awful to have to do, the solution is actually pretty easy. This of course depends on how many records you have. But here's my example:
db.Setting.find({ 'Value.Tiers.0.AssetsUnderManagement': { $exists: 1 } }).snapshot().forEach(function(item)
{
for(i = 0; i != item.Value.Tiers.length; ++i)
{
item.Value.Tiers[i].Aum = item.Value.Tiers[i].AssetsUnderManagement;
delete item.Value.Tiers[i].AssetsUnderManagement;
}
db.Setting.update({_id: item._id}, item);
});
I iterate over my collection where the array is found and the "wrong" name is found. I then iterate over the sub collection, set the new value, delete the old, and update the whole document. It was relatively painless. Granted I only have a few tens of thousands of rows to search through, of which only a few dozen meet the criteria.
Still, I hope this answer helps someone!
Edit: Added snapshot() to the query. See why in the comments.
You must apply snapshot() to the cursor before retrieving any documents from the database.
You can only use snapshot() with unsharded collections.
From MongoDB 3.4, snapshot() function was removed. So if using Mongo 3.4+ ,the example above should remove snapshot() function.
As mentioned in the documentation there is no way to directly rename fields within arrays with a single command. Your only option is to iterate over your collection documents, read them and update each with $unset old/$set new operations.
I had a similar problem. In my situation I found the following was much easier:
I exported the collection to json:
mongoexport --db mydb --collection modules --out modules.json
I did a find and replace on the json using my favoured text editing utility.
I reimported the edited file, dropping the old collection along the way:
mongoimport --db mydb --collection modules --drop --file modules.json
Starting Mongo 4.2, db.collection.update() can accept an aggregation pipeline, finally allowing the update of a field based on its own value:
// { general: { files: { file: [
// { version: { software_program: "MonkeyPlus", indentifier: "6.0.0" } }
// ] } } }
db.collection.updateMany(
{},
[{ $set: { "general.files.file": {
$map: {
input: "$general.files.file",
as: "file",
in: {
version: {
software_program: "$$file.version.software_program",
identifier: "$$file.version.indentifier" // fixing the typo here
}
}
}
}}}]
)
// { general: { files: { file: [
// { version: { software_program: "MonkeyPlus", identifier: "6.0.0" } }
// ] } } }
Literally, this updates documents by (re)$setting the "general.files.file" array by $mapping its "file" elements in a "version" object containing the same "software_program" field and the renamed "identifier" field which contains what used to be the value of "indentifier".
A couple additional details:
The first part {} is the match query, filtering which documents to update (in this case all documents).
The second part [{ $set: { "general.files.file": { ... }}}] is the update aggregation pipeline (note the squared brackets signifying the use of an aggregation pipeline):
$set is a new aggregation operator which in this case replaces the value of the "general.files.file" array.
Using a $map operation, we replace all elements from the "general.files.file" array by basically the same elements, but with an "identifier" field rather than "indentifier":
input is the array to map.
as is the variable name given to looped elements
in is the actual transformation applied on elements. In this case, it replaces elements by a "version" object composed by a "software_program" and a "identifier" fields. These fields are populated by extracting their previous values using the $$file.xxxx notation (where file is the name given to elements from the as part).
I had to face the issue with the same schema. So this query will helpful for someone who wants to rename the field in an embedded array.
db.getCollection("sampledocument").updateMany({}, [
{
$set: {
"general.files.file": {
$map: {
input: "$general.files.file",
in: {
version: {
$mergeObjects: [
"$$this.version",
{ identifer: "$$this.version.indentifier" },
],
},
},
},
},
},
},
{ $unset: "general.files.file.version.indentifier" },
]);
Another Solution
I also would like rename a property in array: and I used thaht
db.getCollection('YourCollectionName').find({}).snapshot().forEach(function(a){
a.Array1.forEach(function(b){
b.Array2.forEach(function(c){
c.NewPropertyName = c.OldPropertyName;
delete c["OldPropertyName"];
});
});
db.getCollection('YourCollectionName').save(a)
});
The easiest and shortest solution using aggregate (Mongo 4.0+).
db.myCollection.aggregate([
{
$addFields: {
"myArray.newField": {$arrayElemAt: ["$myArray.oldField", 0] }
}
},
{$project: { "myArray.oldField": false}},
{$out: {db: "myDb", coll: "myCollection"}}
])
The problem using forEach loop as mention above is the very bad performance when the collection is huge.
My proposal would be this one:
db.nrel.component.aggregate([
{ $unwind: "$general.files.file" },
{
$set: {
"general.files.file.version.identifier": {
$ifNull: ["$general.files.file.version.indentifier", "$general.files.file.version.identifier"]
}
}
},
{ $unset: "general.files.file.version.indentifier" },
{ $set: { "general.files.file": ["$general.files.file"] } },
{ $out: "nrel.component" } // carefully - it replaces entire collection.
])
However, this works only when array general.files.file has a single document only. Most likely this will not always be the case, then you can use this one:
db.nrel.componen.aggregate([
{ $unwind: "$general.files.file" },
{
$set: {
"general.files.file.version.identifier": {
$ifNull: ["$general.files.file.version.indentifier", "$general.files.file.version.identifier"]
}
}
},
{ $unset: "general.files.file.version.indentifier" },
{ $group: { _id: "$_id", general_new: { $addToSet: "$general.files.file" } } },
{ $set: { "general.files.file": "$general_new" } },
{ $unset: "general_new" },
{ $out: "nrel.component" } // carefully - it replaces entire collection.
])

Resources