MongoDB: Get only specific fields in nested array documents - mongodb

I'm trying to project the values from nested array documents which are in the below format. What I expect is to display only the specValue of the specific specType selected in the find query.
{
"carId": "345677"
"car" : {
"model" : [
{
"specs" : [
{
"specType": "SEDAN"
"specValue" : "1"
},
{
"specType": "BR_INC"
"specValue" : "yes"
},
{
"specType": "PLAN"
"specValue" : "gold"
}
]
}
]
}
}
This is what I have tried.
db.cars.find({carId:'345677','car.model.specs.specType':'SEDAN'},{'car.model.specs.specValue':1})
This approach gives me the all the specValues instead like below.
{
"carId": "345677"
"car" : {
"model" : [
{
"specs" : [
{
"specValue" : "1"
},
{
"specValue" : "yes"
},
{
"specValue" : "gold"
}
]
}
]
}
}
How do I make this right to get in the right format like this. Could anyone please help.
{
"carId": "345677"
"car" : {
"model" : [
{
"specs" : [
{
"specType": "SEDAN"
"specValue" : "1"
}
]
}
]
}
}

You can use below aggregation
db.collection.aggregate([
{ "$project": {
"car": {
"model": {
"$filter": {
"input": {
"$map": {
"input": "$car.model",
"in": {
"specs": {
"$filter": {
"input": "$$this.specs",
"cond": { "$eq": ["$$this.specType", "SEDAN"] }
}
}
}
}
},
"cond": { "$ne": ["$$this.specs", []] }
}
}
}
}}
])

Related

Delete a value in a collection Mongo. Example attached

I have this document inside a collection in Mongo and I would like to delete the "7552" : "RTEST" in de bakendData and "7552" in data. How could do it using commands?
{
"_id" : ObjectId("xxxxxx"),
"contractId" : "55528fxxxxxxxxxxxx",
"field1": [
{
"name":"example",
"backendData": {
"map": {
"7552" : "RTEST",
"3511" : "TESTR",
"5312" : "JKTLE",
"5310" : "INVTS"
}
},
"data": {
"defaultOrder": [
"7552",
"3511",
"5312",
"5310"
]
}
}
]
}
Desired result:
{
"_id" : ObjectId("xxxxxx"),
"contractId" : "55528fxxxxxxxxxxxx",
"field1": [
{
"name":"example",
"backendData": {
"map": {
"3511" : "TESTR",
"5312" : "JKTLE",
"5310" : "INVTS"
}
},
"data": {
"defaultOrder": [
"3511",
"5312",
"5310"
]
}
}
]
}
I try something like that but it doesn't work:
db.collection.update(
{ contractId: "55528..............." },
{ $pull: {field1: [ { backendData: { map: {7552: "RTEST"} } }] }}
);
Also I would like to add new values. Example:
"backendData": {
"map": {
"3511" : "TESTR",
"5312" : "JKTLE",
"5310" : "INVTS",
"5355" : "IRZTS",
}
},
"data": {
"defaultOrder": [
"3511",
"5312",
"5310",
"5355"
]
}
Can someone help me?
Thank in advance
I attached an image with the document in Robot 3T
I expect someone will provide a much shorter answer, but here's one way to remove and add the items you want with one update.
db.collection.update({
// match the doc by _id
"_id": ObjectId("55528f000000000000000000")
},
[
{ // rebuild field1
"$set": {
"field1": {
"$map": {
"input": "$field1",
"in": {
// merge what's there plus filtered map plus new object
"$mergeObjects": [
"$$this",
{ "backendData": {
"map": {
"$mergeObjects": [
{ "$arrayToObject": {
"$filter": {
"input": {
"$objectToArray": "$$this.backendData.map"
},
"as": "stuff",
"cond": {
// keep everything except object with 7552 key
"$ne": [ "$$stuff.k", "7552" ]
}
}
}
},
{ "5355": "IRZTS" }
]
}
}
},
{ "data": {
"defaultOrder": {
"$concatArrays": [
{ "$filter": {
"input": "$$this.data.defaultOrder",
"as": "someNum",
"cond": {
// keep everything except 7552
"$ne": [ "$$someNum", "7552" ]
}
}
},
[ "5355" ]
]
}
}
}
]
}
}
}
}
}
])
Try it on mongoplayground.net.

find the string from mongo sub array

I have a collection with documents that look like this:
{
"_id" : ObjectId("56b53b7ddd81cc134fac76a5"),
"name_info" : [
"name c" : [
{
"aliases": ["bill", "william"]
}
],
"name e" : [
{
"aliases": ["robert", "bill"]
}
],
]
}
{
"_id" : ObjectId("87653b745481cc134fac7235"),
"name_info" : [
"name b" : [
{
"aliases": ["stan", "stanley"]
}
],
"name c" : [
{
"aliases": ["robert", "bill"]
}
],
]
}
{
"_id" : ObjectId("65433b7563a1cc134fac7634"),
"name_info" : [
"name b" : [
{
"aliases": ["tom", "tommy"]
}
]
]
}
What query should it be used in a find() command to return all objects that have alias containing 'bill'?
check this
db.collection.aggregate([
{
$project: {
"results": {
"$map": {
"input": "$name_info",
"as": "el",
"in": {
"data": {
$objectToArray: "$$el"
},
}
}
}
}
},
{
$unwind: "$results"
},
{
$unwind: "$results.data"
},
{
$unwind: "$results.data.v"
},
{
$unwind: "$results.data.v.aliases"
},
{
$match: {
"results.data.v.aliases": "bill"
}
},
])
example:
https://mongoplayground.net/p/VTNXMLNNXis
check your collection also

Mongo Unwind child object and Filter

Here is the MongoPlayground about this problem
Today I have 2 nested arrays of object:
I have an array of meetings and inside of it an array of invites.
I need to project only the meetings, but with the invites filtered by the PartnerId. So I have many invites to different partners, but when each partner logs to the system, he can only see the invites for him and not the others. In other words, the array Invites will have only invites to it´s PartnerId.
I could get to the project part, but filter the the invites I am lost. Should I use a $filter or $group to another array?
here is what I got so far:
MongoPlayground
db.collection.aggregate([
{
"$match": {
"$nor": [
{
"Meetings": {
"$exists": false
}
},
{
"Meetings": {
"$size": 0.0
}
}
]
}
},
{
"$unwind": {
"path": "$Meetings"
}
},
{
"$project": {
"Meetings": "$Meetings"
}
},
{
"$replaceRoot": {
"newRoot": "$Meetings"
}
}
])
ANSWER that I did...
db.getCollection("ClientProject").aggregate(
[
{
"$match" : {
"$and" : [
{
"PartnerIds" : {
"$in" : [
"5f9b247f0ca60a000232cacf"
]
}
},
{
"$nor" : [
{
"Meetings" : {
"$exists" : false
}
},
{
"Meetings" : {
"$size" : 0.0
}
},
{
"Meetings" : {
"$eq" : null
}
}
]
}
]
}
},
{
"$unwind" : {
"path" : "$Meetings"
}
},
{
"$project" : {
"Meetings" : "$Meetings"
}
},
{
"$replaceRoot" : {
"newRoot" : "$Meetings"
}
},
{
"$addFields" : {
"Invites" : {
"$filter" : {
"input" : "$Invites",
"as" : "invite",
"cond" : {
"$eq" : [
"$$invite.PartnerId",
"5f9b247f0ca60a000232cacf"
]
}
}
}
}
}
],
{
"allowDiskUse" : false
}
);
Using $filter is a good way to achieve your goal, and here is how you can do that:
db.collection.aggregate([
{
"$match": {
"$nor": [
{
"Meetings": {
"$exists": false
}
},
{
"Meetings": {
"$size": 0.0
}
}
]
}
},
{
"$unwind": {
"path": "$Meetings"
}
},
{
"$project": {
"invites": {
"$filter": {
"input": "$Meetings.Invites",
"as": "invite",
"cond": {
"$eq": [
"$$invite.PartnerName",
"Thiago Parceiro "
]
}
}
}
}
}
])

Match Items In Multi-Level Embedded Arrays

I use the following collection which represents sports > categories > tournaments.
{
"_id" : ObjectId("597846358bbbc4440895f2e8"),
"Name" : [
{ "k" : "en-US", "v" : "Soccer" },
{ "k" : "fr-FR", "v" : "Football" }
],
"Categories" : [
{
"Name" : [
{ "k" : "en-US", "v" : "France" },
{ "k" : "fr-FR", "v" : "France" }
],
"Tournaments" : [
{
"Name" : [
{ "k" : "en-US", "v" : "Ligue 1" },
{ "k" : "fr-FR", "v" : "Ligue 1" }
],
},
{
"Name" : [
{ "k" : "en-US", "v" : "Ligue 2" },
{ "k" : "fr-FR", "v" : "Ligue 2" }
],
}
]
},
{
"Name" : [
{ "k" : "en-US", "v" : "England" },
{ "k" : "fr-FR", "v" : "Angleterre" }
],
"Tournaments" : [
{
"Name" : [
{ "k" : "en-US", "v" : "Premier League" },
{ "k" : "fr-FR", "v" : "Premier League" }
],
},
{
"Name" : [
{ "k" : "en-US", "v" : "Championship" },
{ "k" : "fr-FR", "v" : "Championnat" }
],
}
]
},
]
}
I want to query the collection using the category’s name and the tournament’s name. I’ve successfully use “$elemMatch” with the following code:
db.getCollection('Sport').find({
Categories: {
$elemMatch: {
Name: {
$elemMatch: { v: "France" }
},
Tournaments: {
$elemMatch: {
Name: {
$elemMatch: { v: "Ligue 1" }
}
}
}
}
} },
{ "Categories.$": 1, Name: 1 })
However, I cannot receive only the matching tournament in the category object.
Using the answer in this question: MongoDB Projection of Nested Arrays, I’ve built an aggregation:
db.getCollection('Sport').aggregate([{
"$match": {
"Categories": {
"$elemMatch": {
"Name": {
"$elemMatch": {
"v": "France"
}
},
"Tournaments": {
"$elemMatch": {
"Name": {
"$elemMatch": {
"v": "Ligue 1"
}
}
}
}
}
}
}
}, {
"$addFields": {
"Categories": {
"$filter": {
"input": {
"$map": {
"input": "$Categories",
"as": "category",
"in": {
"Tournaments": {
"$filter": {
"input": "$$category.Tournaments",
"as": "tournament",
"cond": {
// stuck here
}
}
}
}
}
},
"as": "category",
"cond": {
// stuck here
}
}
}
}
}
])
I tried to use a condition but MongoDB doesn’t recognize (Use of undefined variable:) $$KEEP and $$PRUNE ($redact) when I use $anyElementTrue then $map on the “Name” property.
My question: how can I check that the collection of names contains my string?
I'm more surprised that on the answer you reference I did not not "strongly recommend you do not nest arrays" like this. Nesting in this way is impossible to update atomically until the next release of MongoDB, and they are notoriously difficult to query.
For this particular case you would do:
db.getCollection('Sport').aggregate([
{ "$match": {
"Categories": {
"$elemMatch": {
"Name.v": "France",
"Tournaments.Name.v": "Ligue 1"
}
}
}},
{ "$addFields": {
"Categories": {
"$filter": {
"input": {
"$map": {
"input": "$Categories",
"as": "c",
"in": {
"Name": {
"$filter": {
"input": "$$c.Name",
"as": "n",
"cond": { "$eq": [ "$$n.v", "France" ] }
}
},
"Tournaments": {
"$filter": {
"input": {
"$map": {
"input": "$$c.Tournaments",
"as": "t",
"in": {
"Name": {
"$filter": {
"input": "$$t.Name",
"as": "n",
"cond": {
"$eq": [ "$$n.v", "Ligue 1" ]
}
}
}
}
}
},
"as": "t",
"cond": {
"$ne": [{ "$size": "$$t.Name" }, 0]
}
}
}
}
}
},
"as": "c",
"cond": {
"$and": [
{ "$ne": [{ "$size": "$$c.Name" },0] },
{ "$ne": [{ "$size": "$$c.Tournaments" },0] }
]
}
}
}
}}
])
Which returns the result:
/* 1 */
{
"_id" : ObjectId("597846358bbbc4440895f2e8"),
"Name" : [
{
"k" : "en-US",
"v" : "Soccer"
},
{
"k" : "fr-FR",
"v" : "Football"
}
],
"Categories" : [
{
"Name" : [
{
"k" : "en-US",
"v" : "France"
},
{
"k" : "fr-FR",
"v" : "France"
}
],
"Tournaments" : [
{
"Name" : [
{
"k" : "en-US",
"v" : "Ligue 1"
},
{
"k" : "fr-FR",
"v" : "Ligue 1"
}
]
}
]
}
]
}
The whole point is that each array needs a $filter, and at the outer levels you are looking for $size not being 0 as a result of "inner" $filter operations on contained arrays.
Since the "inner" arrays can change in content as a result, the "outer" arrays need a $map in order to return the "changed" elements.
So in terms of the structure "Categories" needs a $map because it has inner elements. And the "inner" "Tournaments" needs a $map for the same reason. Every array all the way to the final properties need $filter, and each wrapping array with a $map has a $filter with a $size condition.
That's the general logic pattern, and it works by repeating that pattern for each nested level. As stated though, it's pretty horrible. Which is why you really should avoid "nesting" like this at all costs. The increased complexity just about always outweighs any perceived gains.
I should also note you went a little overboard with $elemMatch, You really only need it at the "Categories" array level since that's the only thing that has multiple conditions to be met for it's element.
The sub-elements can use plain "Dot Notation" since they are only "singular" conditions within their respective arrays. So that does cut down on the terse syntax somewhat and still matches exactly the same documents.

Filter the sub array of an array by some criteria

Hi have a document in the format :
{
"_id":"someId",
"someArray":[
{
"subId":1,
"subArray":[
{
"field1":"A",
"filterMe":"NO"
},
{
"field1":"B",
"filterMe":"YES"
}
]
},
{
"subId":2,
"subArray":[
{
"field1":"C",
"filterMe":"YES"
},
{
"field1":"D",
"filterMe":"NO"
}
]
}
]
}
how can i filter the subArray based on some criteria. Example filter out the subArray if field filterMe is "YES". so finally the output json should be like
{
"_id":"someId",
"someArray":[
{
"subId":1,
"subArray":[
{
"field1":"A",
"filterMe":"NO"
}
]
},
{
"subId":2,
"subArray":[
{
"field1":"D",
"filterMe":"NO"
}
]
}
]
}
I tried in the below way but, its filtering the whole subArray.
db.testJson.aggregate([
{ $project: {
'someArray.subArray': {
$filter: {
input: '$someArray.subArray',
as: 'input',
cond: {$eq: ["$$input.filterMe", "YES"]}
}}
}}
]);
You just need a $filter inside a $map:
db.junk.aggregate([
{ "$project": {
"someArray": {
"$filter": {
"input": {
"$map": {
"input": "$someArray",
"as": "some",
"in": {
"subId": "$$some.subId",
"subArray": {
"$filter": {
"input": "$$some.subArray",
"as": "sub",
"cond": { "$ne": [ "$$sub.filterMe", "YES" ] }
}
}
}
}
},
"as": "some",
"cond": { "$gt": [ { "$size": "$$some.subArray" }, 0 ] }
}
}
}}
])
Produces:
{
"_id" : "someId",
"someArray" : [
{
"subId" : 1,
"subArray" : [
{
"field1" : "A",
"filterMe" : "NO"
}
]
},
{
"subId" : 2,
"subArray" : [
{
"field1" : "D",
"filterMe" : "NO"
}
]
}
]
}
I actually wrap that in an additional $filter to remove any someArray entries where the filtered subArray ended up being empty as a result. Mileage may vary on that being what you want to do.