How to use find for nested object of objects in MongoDB - mongodb

I have nested object of objects. Each document in collection looks like this:
{
anything: "whatever",
something: {
// find inside of these document
a: { getThis: "wow" },
b: { getThis: "just wow" },
c: { getThis: "another wow" }
}
}
I would like to find in every getThis from each document in something.
For example I would like to find document which has getThis: "wow".
I've tried to use something like wildcard with *:
{"something.*.getThis": "wow" }
I've also tried $elemMatch but it seems it works only with array;
{ something: { $elemMatch: { getThis: "wow" } } }

You can try using $objectToArray,
$addFields to convert something to array in somethingArr
$match condition getThis is wow or not
$project to remove somethingArr
db.collection.aggregate([
{
$addFields: {
somethingArr: { $objectToArray: "$something" }
}
},
{ $match: { "somethingArr.v.getThis": "wow" } },
{ $project: { somethingArr: 0 } }
])
Playground
Second possible way
$filter input something as array, convert using $objectToArray
filter will check condition getThis is equal to wow or not
db.collection.aggregate([
{
$match: {
$expr: {
$ne: [
[],
{
$filter: {
input: { $objectToArray: "$something" },
cond: { $eq: ["$$this.v.getThis", "wow"] }
}
}
]
}
}
}
])
Playground

Related

MongoDB - Modify the field name in a nested list of lists

As the title says, the field I need to modify is nested like this:
basicData.owners.relatedJson.basicData.devices.equipmentID
which owners and devices are both lists.
The object looks like this:
{
"basicData": {
"owners": [
{
"relatedJson": {
"basicData": {
"devices": [
{
"equipmentID": "abcd",
"type": "camera"
}
],
"otherFieldsBasicData": "other values",
"evenMoreFieldsBasicData": "other values"
},
"otherFieldsRelatedJson": "other values",
"evenMoreFieldsRelatedJson": "other values"
}
}
]
}
}
I want to rename equipmentID to equipmentId.
I've also asked this question, and I've been trying to create a query using that as a starting point, but with no success.
I was able to build a query that could get as far down as the devices list, but. then I wanted to call $set on that list, and I get an error because you can't call set inside $mergeObjects.
I thought there was some way I could use $[] to help iterate through the first array, but I can't get it to work. I think this approach is wrong.
This is what I've ended up with, which doesn't work:
db.myCollection.updateMany({"basicData.owners.relatedJson.basicData.devices.equipmentID": {$exists: true}},
[
{
$set: {
"basicData.owners$[].relatedJson.basicData.devices": {
$map: {
input: "$basicData.owners$[].relatedJson.basicData.devices", in: {
$mergeObjects: ["$$this",
{equipmentId: "$$this.equipmentID",}]
}
}
}
}
},
{
$unset: "basicData.owners.relatedJson.basicData.devices.equipmentID"
}
])
Any advice would be greatly appreciated.
Think you need two $map (with nested) operators.
First $map to iterate basicData.owners array while another $map is used to iterate relatedJson.basicData.devices array.
db.collection.updateMany({
"basicData.owners.relatedJson.basicData.devices.equipmentID": {
$exists: true
}
},
[
{
$set: {
"basicData.owners": {
$map: {
input: "$basicData.owners",
in: {
$mergeObjects: [
"$$this",
{
"relatedJson": {
$mergeObjects: [
"$$this.relatedJson",
{
"basicData": {
$mergeObjects: [
"$$this.relatedJson.basicData",
{
"devices": {
$map: {
input: "$$this.relatedJson.basicData.devices",
in: {
$mergeObjects: [
"$$this",
{
equipmentId: "$$this.equipmentID",
}
]
}
}
}
}
]
}
}
]
}
}
]
}
}
}
}
},
{
$unset: "basicData.owners.relatedJson.basicData.devices.equipmentID"
}
])
Demo # Mongo Playground

Mongodb $expr on array

Mongodb: 4.0.13
I'm having troubles in understand and get working $expr with arrays.
Let' start and create a new collection (dbRepeatElement) with following document:
db.testRepeatElement.insert([
{
"data" : {
"FlsResSemires_2" : {
"Sospensione" : [
{
"DataInizio" : 1548806400000,
"DataFine" : 1549065600000,
"Motivazione" : "1"
}
]
}
},
"derived" : {
"DATAFINEANNORIFERIMENTO" : 1609372800000,
"regione190" : "190",
"REGAOEROG" : "190209820300",
"REGASLEROG" : "190209"
}
}
])
In a bigger aggregation, following part is not working:
db.testRepeatElement.aggregate([
{
$match: {
$expr: {
$gt: ["$data.FlsResSemires_2.Sospensione.DataInizio", "$derived.DATAFINEANNORIFERIMENTO"]
}
}
}
])
Result: return a match ( wrong! just check dates)
Reading mongodb documentation seems to be, using combination with arrays, aggregation and $expr does not return expected result and you have to specify with element of the array you want to check, like:
db.testRepeatElement.aggregate([
{
$match: {
$expr: {
$gt: ["$data.FlsResSemires_2.0.Sospensione.DataInizio", "$derived.DATAFINEANNORIFERIMENTO"]
}
}
}
])
Result: return no match (right!)
Question: my requirement is to check every element in the array, so how to solve this, without using $unwind? Why there is this kind of result ?
The $filter aggregation operator is used to do the match operation on array elements. The following aggregation query will result only the Sospensione array elements which match the $gt condition:
db.testRepeatElement.aggregate( [
{
$addFields: {
"data.FlsResSemires_2.Sospensione": {
$filter: {
input: "$data.FlsResSemires_2.Sospensione",
cond: {
$gt: [ "$$this.DataInizio", "$derived.DATAFINEANNORIFERIMENTO" ]
}
}
}
}
},
{
$match: {
$expr: {
$gt: [ { $size: "$data.FlsResSemires_2.Sospensione" }, 0 ]
}
}
}
] ).pretty()

how to use $elemMatch on array specifying an upper field as part of the query

I'd like to retrieve for a specific user, his chats with unread messages.
Lets say I have a simplified chat model like that :
{
lastMessageAt: Date,
participants: [
{
user: String(id),
lastReadAt: Date
}
]
}
How can I achieve my query ?
I have tried several thing like with $elemMatch, but lastMessageAt is unknown at this level...
ChatDB.find({
'participants': {
$elemMatch: { user: '12345', lastReadAt: { $lt: '$lastMessageAt' } }
}
}
Thanks in advance for your help ! :)
$elemMatch operator will find those documents in ChatDB collection that have at least 1 element in participants that matches your criteria. Also my research ended with the conslusion that it is not yet possible to access other document field in $elemMatch operator. Anyway, if this is your goal, then you can use this query:
ChatDB.aggregate([
{
$match: {
"participants.user": "12345",
$expr: {
$lt: [
"$participants.lastReadAt",
"$lastMessageAt"
]
}
}
}
])
Mongo playground
If you also want to filter participants that really matched the criteria, then you need to add a projection stage:
ChatDB.aggregate([
{
$match: {
"participants.user": "12345",
$expr: {
$lt: [
"$participants.lastReadAt",
"$lastMessageAt"
]
}
}
},
{
$project: {
participants: {
$filter: {
input: "$participants",
as: "participant",
cond: {
$and: [
{
$eq: [
"$$participant.user",
"12345"
]
},
{
$lt: [
"$$participant.lastReadAt",
"$lastMessageAt"
]
}
]
}
}
}
}
}
])
Mongo playground
I have found the solution witch is to use the aggregator with the $unwind operator.
await ChatDB.aggregate([
{
$unwind: '$participants'
},
{
$match: {
'participants.user': '12345',
$expr: {
$lt: [
'$participants.lastReadAt',
'$lastMessageAt'
]
}
}
}]);
Hope this will be usefull

Renaming Field Within an Array in MongoDB

I need to update the name of field in a collection. The problem is that the field in question is within an array. So I'm trying to determine the correct way do this. I tried this to accomplish renaming the field that exists within the "plans" array: :
db.customers.updateMany( {}, { $rename: { "plans" : { "subscriptionType": "membershipType" } } } );
But this won't work. What's the correct way to handle this kind of transformation of a field within an array?
The data looks like this:
{
_id: 123,
prop1: value,
prop2: value,
prop3: value,
plans: [
subscriptionType: value,
otherProp: value,
otherProp: value
]
}
You can use Aggregation Framework's $addFields to override plans field and $map operator to rename field inside an array. Then you can use $out to override existing collection:
db.customers.aggregate([
{
$addFields: {
plans: {
$map:{
input: "$plans",
as: "plan",
in: {
membershipType: "$$plan.subscriptionType",
otherField: "$$plan.otherField",
otherField2: "$$plan.otherField2"
}
}
}
}
},
{
$out: "customers"
}
])
Alternatively you can do that dynamically. In this solution you don't have to explicitly specify other field names:
db.customers.aggregate([
{
$addFields: {
plans: {
$map:{
input: "$plans",
as: "plan",
in: {
$mergeObjects: [
{ membershipType: "$$plan.subscriptionType" },
{
$arrayToObject: {
$filter: {
input: { $objectToArray: "$$plan" },
as: "plan",
cond: { $ne: [ "$$plan.k", "subscriptionType" ] }
}
}
}
]
}
}
}
}
},
{
$out: "customers"
}
])
Using $objectToArray to $filter out old key-value pair and the using $mergeObjects to combine that filtered object with new renamed field.

How to find several items from the same array in Mongodb?

In the collection "friendship" I would like to find all Bart's friends that are 10. The $elemMatch query only returns the first matching element of the array : I only get Milhouse. How can I get Milhouse and Martin ?
{ name:'Bart',
age:10
friends:[
{ name:'Milhouse',
age:10
},
{ name:'Nelson',
age:11
},
{ name:'Martin',
age:10
}
]
},
{ name:'Lisa',
age:8
friends:[
...
]
}
Try to use aggregation framework for this task (especially $unwind operator):
db.friendship.aggregate([
{ $match: { name: "Bart" } },
{ $unwind: "$friends" },
{ $match: { "friends.age": 10 } }
]);