How to retrieve object values with same key inside an array - mongodb

I'm trying to get (filter) all the objects from a document array that match the same key.
Document Schema example:
{
...
country: "ES",
aut_comms:
[
...
{name: "Aragon", province: "Huesca"},
{name: "Aragon", province: "Teruel"},
{name: "Aragon", province: "Zaragoza"},
{name: "Madrid", province: "Madrid"}
...
]
}
If it is possible, im tying to retrieve from the query only the values from the objects that match the same key. Resulting in an array composed like : ["Huesca", "Teruel", "Zaragoza"]
An Array of objects that match the filter will also do the trick:
[
{name: "Aragon", province: "Huesca"},
{name: "Aragon", province: "Teruel"},
{name: "Aragon", province: "Zaragoza"}
]
Thanx

You will be able to get this array by first unwinding the array and then manipulating it
db.demo.aggregate([
{
$unwind:"$aut_comms"
},
{
$match:{"aut_comms.name":"Aragon"}
},
{
$group:{
_id:null,
result: {$push:"$aut_comms.province"}
}
}
])

Edit
It is indeed possible to do such query and output in your expected format. You can do either $unwind or $match first. Personally, I prefer to do a $match first as it would limit the number of (unnecessary) documents generated by the $unwind operation.
db.getCollection('col').aggregate([
{$match: {"aut_comms.name": "Aragon"}},
{$project: {_id: 0, "aut_comms": 1}},
{$unwind: "$aut_comms"},
{$match: {"aut_comms.name": "Aragon"}},
{$project: {"province": "$aut_comms.province"}},
{$group: {
_id: null,
province: {$push: "$province"}
}}
])
The output will be:
/* 1 */
{
"_id" : null,
"province" : [
"Huesca",
"Teruel",
"Zaragoza"
]
}

Related

MongoDB Aggregate functions convert object array to string array

I have some documents in a collection. Every document has a challenge_id field. I want to map those document array into a string array. The final string array should consist of challenge ids from each document
input:
[
{
_id: ObjectId("62c3e31931e7df585c39e4e1"),
activity_id: ObjectId("62c3e31931e7df585c39e4df"),
challenge_id: ObjectId("62bd543c3a3937000958f2dd"),
status: "active",
createdAt: ISODate("2022-07-05T07:07:05.823Z"),
updatedAt: ISODate("2022-07-05T07:07:05.823Z")
},
{
_id: ObjectId("62c3e33f299750585cc70b23"),
activity_id: ObjectId("62c3e33e299750585cc70b21"),
challenge_id: ObjectId("62bd543c3a3937000958f2dd"),
status: "active",
createdAt: ISODate("2022-07-05T07:07:43.612Z"),
updatedAt: ISODate("2022-07-05T07:07:43.612Z")
},
{
_id: ObjectId("62c3e359341e86585c65c714"),
activity_id: ObjectId("62c3e359341e86585c65c712"),
challenge_id: ObjectId("62bd543c3a3937000958f2dd"),
status: "active",
createdAt: ISODate("2022-07-05T07:08:09.409Z"),
updatedAt: ISODate("2022-07-05T07:08:09.409Z")
}
]
output should looks like:
['62bd543c3a3937000958f2dd','62bd543c3a3937000958f2dd', '62bd543c3a3937000958f2dd' ]
Is it possible to do this with an aggregate function ? How ?
You can use $group like this:
db.collection.aggregate([
{$group: {_id: 0, res: {$push: {$toString: "$challenge_id"}}}},
{$project: {res: 1, _id: 0}}
])
See how it works on the playground example

Add key value on all objects inside an array in MongoDB

I have a dataset like this on MongoDB:
{
"_id" : ObjectId("5f7b02a197cca91d3476a3b7"),
arrayHolder: [
{name: 'Peter'},
{name: 'John'}
]
},
{
"_id" : ObjectId("5f7b02a197cca91d3476a3b8"),
arrayHolder: [
{name: 'Harry'},
{name: 'Nik'}
]
}
Now I want to add one more key values to all objects under arrayHolder objects. I am sharing the Expected Output:
{
"_id" : ObjectId("5f7b02a197cca91d3476a3b7"),
arrayHolder: [
{name: 'Peter', valid: true },
{name: 'John', valid: true}
]
},
{
"_id" : ObjectId("5f7b02a197cca91d3476a3b8"),
arrayHolder: [
{name: 'Harry', valid: true},
{name: 'Nik', valid: true}
]
}
For this I wrote the query but that query will add the new object inside the array rather than adding one more key-value under the objects.
My Query is:
db.Collection.updateMany({}, {$push: {arrayHolder: {$each: {valid: true}}}})
This Query will add the new Objects inside an array. I am sharing the output which I am getting after this query run:
{
"_id" : ObjectId("5f7b02a197cca91d3476a3b7"),
arrayHolder: [
{name: 'Peter'},
{name: 'John'},
{valid: true}
]
},
{
"_id" : ObjectId("5f7b02a197cca91d3476a3b8"),
arrayHolder: [
{name: 'Harry'},
{name: 'Nik'},
{valid: true}
]
}
Is there anyone who guide me where I have done the mistakes and how to fire the proper query so that expected output will achieved.
Thanks in advance for the people who interacted with this problem
You can use all positional operator $[] to do that:
db.Collection.updateMany({}, {$set: {'arrayHolder.$[].valid': true}})

How to batch query by an array in mongodb?

I have a table Thread:
{
userId: String
messageId: String
}
Now I have an array of userIds, I need to query 20 messageIds for each of them, I can do it with a loop:
const messageIds = {}
for (const userId of userIds) {
const results = await Thread.find({ userId }).sort({ _id: -1 }).limit(20).exec()
messageIds[userId] = results.map(result => result.messageId)
}
But of course this doesn't perform well. Is there a better solution?
The problem with your approach is that you are issuing multiple separate queries to MongoDB.
The simplest workaround to this is using the $push and $slice approach. But this has the problem that the intermediate step would creating an array of huge size.
Another way could be to use $facet as part of aggregation query.
So you need a $facet step in the aggregation like -
[
{$facet: {
'userId1': [
{$match: {userId: 'userId1'} },
{$limit: 20},
{$group: {_id: '', msg: {$push: '$messageId'} } }
],
'userId2': [
{$match: {userId: 'userId2'} },
{$limit: 20},
{$group: {_id: '', msg: {$push: '$messageId'} } }
],
.... (for each userId in array)
}}
]
You can easily just generate this query by iterating over the list of users and adding keys for each user.
So you end up with an object where key is the userId and the value is the array of messages (obj[userId].msg)
You can use aggregation to group threads by userId, and return the top 20:
db.threads.aggregate([
{$match: {userId:{$in: userIds}}},
{$sort: {_id: -1}},
{$group: {_id: "$userId", threads: {$push: "$$ROOT"}}},
{$project: {_id:0, userId:"$_id", threads: {$slice:["$threads", 20]}}}
])

Distinct query in Mongo

I have a document (name, firstName, age).
This command gives me the different names of the document:
db.getCollection('persons').distinct("name")
How do I do to get the corresponding firstNames?
Thanks!
You could try an aggregation query which groups the name and firstName. Eventually you could also add a count to it to see which combinations are repeated (but that is not necessary).
Here is an example:
db.test1.aggregate(
[
{
$group: {_id: {name: "$name", firstName: "$firstName"}, count: {$sum: 1}}
}
]
)
Here is another another option to show an aggregated list:
db.test1.aggregate(
[
{
$group: {_id: {name: "$name"}, firstName: { $push: "$firstName" }}
}
]
)

mongo $project not projecting original values

I am new to Mongodb, and NoSQL in general and I am trying to use mongodbs aggregate function to aggregate data from one collection to be inserted into another. An example of the original collection would be this:
Original Collection
{
supplier: 'aldi',
timestamp: '1492807458',
user: 'eddardstark#gmail.com',
hasBeenAggregated:false,
items:[{
name: 'butter',
supplier: 'aldi',
expiry: '1492807458',
amount: 454,
measureSymbol: 'g',
cost: 2.19
},{
name: 'milk',
supplier: 'aldi',
expiry: '1492807458',
amount: 2000,
measureSymbol: 'ml',
cost: 1.49
}]
}
An example of the output I am trying to achieve would be:
New Collection
{
user:'eddardstark#gmail.com',
amount: 3.68,
isIncome: false,
title: 'food_shopping',
timestamp: '1492807458'
}
The aggregation function that I am using is:
Aggregation
var result = db.runCommand({
aggregate: 'food_transactions',
pipeline: [
{$match: {hasBeenAggregated: false}},
{$unwind: '$items'},
{$group:{_id: '$_id',amount:{$sum: '$items.cost'}}},
{$project: {
_id:0,
user:1,
amount:1,
isIncome: {$literal: false},
title:{$literal: 'food_shopping'},
timestamp:1
}}
]
});
printjson(result)
This aggregation function does not return the user or timestamp fields. Instead, I get the following output:
Output
{
"amount" : 3.6799999999999997,
"isIncome" : false,
"title" : "food_shopping"
}
If I don't group the results and perform the calculations in the $project stage, the fields are all projected correctly, but obviously, there is a new document created for each sub-document in the items array and that rather defeats the purpose of the aggregation.
What am I doing wrong?
Update your $group pipeline to include all the fields you wish to project further down the pipeline.
To include user field you can use $first
{$group:{_id: '$_id', user:{$first:'$user`}, amount:{$sum: '$items.cost'}}},
Additionally, if you are 3.4 version you can simplify your aggregation to below.
Use $reduce to sum all the item's cost in a single document. For all documents you can add $group after $reduce.
db.collection.aggregate([
{$match: {hasBeenAggregated: false}},
{$project: {
_id:0,
user:1,
amount: {
$reduce: {
input: "$items",
initialValue: 0,
in: { $add : ["$$value", "$$this.cost"] }
}
},
isIncome: {$literal: false},
title:{$literal: 'food_shopping'},
timestamp:1
}}
])