mongodb get all documents but only specific sub document - mongodb

i have documents like:
{
"_id" : ObjectId("5ed79a6750869a5738e679c2"),
"SerialNumber" : "867688034502264",
"sharing":[
{ from:"abc", to: "123"},
{ from:"123", to: "435"}]
},
{
"_id" : ObjectId("5ed79a6750869a5738e679c3"),
"SerialNumber" : "867688034502111"
}
i would like to get all documents and in sharing field i only want to get subdocuments where "from" has a specific value like "abc"
i would like to get:
{
"_id" : ObjectId("5ed79a6750869a5738e679c2"),
"SerialNumber" : "867688034502264",
"sharing":[
{ from:"abc", to: "123"}]
},
{
"_id" : ObjectId("5ed79a6750869a5738e679c2"),
"SerialNumber" : "867688034502111",
}

You can use Aggregation Pipeline with a $filter stage:
db.collection.aggregate([
{
$project: {
SerialNumber: "$SerialNumber",
sharing: {
$filter: {
input: "$sharing",
cond: {
"$eq": [
"$$this.from",
"abc"
]
}
}
}
}
}
])
https://mongoplayground.net/p/n41uUeDdv9Q
Note that it will set sharing to null if the property is missing. If that's an issue, you can further filter the document before filtering, or you can do another $project as a separate stage.

You and use find with $elemMatch in the projection:
db.collection.find({}, {SerialNumber:1, sharing: {$elemMatch: {from: "abc"}}})
Playground

Related

Only show certain nested fields in aggregate query

I have the aggregate query:
db.laureates.aggregate([
{ $match : { "nobelPrizes.affiliations.name.en" : "CERN" }},
{ $project : { _id: 0, "nobelPrizes.affiliations.country.en" : 1 }},
{ $limit : 1}]).pretty()
which results in this:
{
"nobelPrizes" : [
{
"affiliations" : [
{
"country" : {
"en" : "Switzerland"
}
}
]
}
]
}
The resulting value of the query is correct (Switzerland), but I am trying to only print certain fields in the result, namely country. It should look like this:
{ "country" : "Switzerland" }
How do I exclude the fields beside the "country" field? I'm aware that the projection part of the aggregate pipeline can exclude fields parallel to the one being targeted, but how can this be done for nested fields?
You can use $unwind to flat the array
db.collection.aggregate([
{ "$unwind": "$nobelPrizes"},
{ "$unwind": "$nobelPrizes.affiliations"},
{ $match: { "nobelPrizes.affiliations.country.en": "Switzerland"}},
{
$project: {
_id: 0,
"country": "$nobelPrizes.affiliations.country.en"
}
},
{ $limit: 1 }
])
Working Mongo playground

Remove element from subarray using an aggregation stage applied to a change stream in MongoDB?

I'm using MongoDB 4.2 and I'm looking for a way to remove all elements from a subarray if it doesn't match a certain condition using an aggregation stage that is compatible with a change streams. The compatible stages are:
$addFields
$match
$project
$replaceRoot
$replaceWith
$redact
$set
$unset
For example consider that we have a collection, users, containing documents in this format:
{ "name" : "John Doe",
"access": [
{ "level" : "Gold", "rating" : 3.2 },
{ "level" : "Silver", "rating" : 2.1 },
{ "level" : "Gold", "rating" : 4.2 }
]
}
I'd like to use one, or a combination, of the compatible aggregation stages to remove elements in the "access" array that doesn't match a filter such as { $elemMatch : { "level" : "Gold" } }. I'd like the resulting document to look like this:
{ "name" : "John Doe",
"access": [
{ "level" : "Gold", "rating" : 3.2 },
{ "level" : "Gold", "rating" : 4.2 }
]
}
Is it possible to do this in MongoDB?
You can use $addFields / $set together with $filter
db.collection.aggregate({
$set: {
access: {
$filter: {
input: "$access",
cond: { $eq: ["$$this.level", "Gold"] } // your condition expression
}
}
}
})
Mongo Playground
If you want to update existing documents, you can do this with the update pipeline as follows
db.test.updateMany({
access: { $elemMatch: { level: { $ne: "Gold" } } }, // find elements that does not match your condition
[{
$set: {
access: {
$filter: {
input: "$access",
cond: { $eq: ["$$this.level", "Gold"] } // your condition expression
}
}
}
}]
)

How can I make $lookup embed the document directly instead of wrapping it into array?

I have a document like this:
{
"_id": ObjectId("5d779541bd4e75c58d598212")
"client": ObjectId("5d779558bd4e75c58d598213")
}
When I do $lookup like this:
{
from: 'client',
localField: 'client',
foreignField: 'id',
as: 'client',
}
I get:
{
"_id": ObjectId("5d779541bd4e75c58d598212")
"client":[
{
... client info wrapped in array
}
]
}
This forces me to add $unwind after the lookup stage.
This would work fine in this example because I know that it is a regular field (not array). But on other collections I have arrays of ObjectId's and I don't want to unwind them.
How should I tell mongo to unwind only if it's not an array?
Add $project stage with $arrayElemAt
{ $lookup ..... },
{ $project: { client: { $arrayElemAt: [ "$client" , 0 ]}} // Add other filed
The lookup always returns an array as it doesn't know if its a one-to-one or one-to-many mapping. But we can ensure that the lookup returns a single document and that document would hold all documents which were supposed to come as an array in the general lookup.
Following is the way:
db.collection.aggregate([
{
$lookup:{
"from":"client",
"let":{
"client":"$client"
},
"pipeline":[
{
$match:{
$expr:{
$eq:["$id","$$client"]
}
}
},
{
$group:{
"_id":null,
"data":{
$push:"$$ROOT"
}
}
},
{
$project:{
"_id":0
}
}
],
"as":"clientLookup"
}
},
{
$unwind:"$clientLookup"
}
]).pretty()
Query analysis: We are looking up into client collection and executing a pipeline inside that. The output of that pipeline would hold every matched document inside data field.
Data set:
Collection: collection
{
"client":1
}
{
"client":2
}
Collection: client
{
"id":1,
"name":"Tony"
}
{
"id":1,
"name":"Thor"
}
{
"id":1,
"name":"Natasha"
}
{
"id":2,
"name":"Banner"
}
Output:
{
"_id" : ObjectId("5d7792c6bd4e75c58d59820c"),
"client" : 1,
"clientLookup" : {
"data" : [
{
"_id" : ObjectId("5d779322bd4e75c58d59820e"),
"id" : 1,
"name" : "Tony"
},
{
"_id" : ObjectId("5d779322bd4e75c58d59820f"),
"id" : 1,
"name" : "Thor"
},
{
"_id" : ObjectId("5d779322bd4e75c58d598210"),
"id" : 1,
"name" : "Natasha"
}
]
}
}
{
"_id" : ObjectId("5d7792c6bd4e75c58d59820d"),
"client" : 2,
"clientLookup" : {
"data" : [
{
"_id" : ObjectId("5d779322bd4e75c58d598211"),
"id" : 2,
"name" : "Banner"
}
]
}
}

In a mongodb database, how do I find() and filter out any subdocuments?

If I have a mongodb database, is it possible to:
db.mydb.find(...)
Such that the result will filter out any subdocuments which may be present, without knowing what they are in advance?
For example, if I had:
{ "_id" : ObjectId("..."), "name" : "george", "address" : { "street" : "101 example way", "city" : "tutorial", "state" : "CA" }, "other thing" : "thing value" }
What arguments could I pass to find() that would result in getting:
{ "_id" : ObjectId("..."), "name" : "george", "other thing" : "thing value" }
without having to specify:
db.mydbl.find( {}, { "address" : 0} )
Does a method to suppress all subdocuments exist?
If you want to dynamically remove any nested objects without specifying any existing keys, you can achieve that using aggregation framework:
db.col.aggregate([
{
$project: {
keysAndValues: {
$objectToArray: "$$ROOT"
}
}
},
{
$addFields: {
keysAndValues: {
$filter: {
input: "$keysAndValues",
as: "kvPair",
cond: { $ne: [ { $type: "$$kvPair.v" }, "object" ] }
}
}
}
},
{
$replaceRoot: {
newRoot: { $arrayToObject: "$keysAndValues" }
}
}
])
Basically the idea is quite simple: we want to transform a document into a list of key value pairs using ($objectToArray). Then we can filter out those key-value pairs where value is of $type "object". In last step we can transform our array back to an object using $arrayToObject

Use array first field in mongo aggregate $lookup query to match a document

I want to use my array field 0th value to find a match in sale document using Mongo aggregate $lookup query. Here is my query:
db.products.aggregate([
{
$match : { _id:ObjectId("57c6957fb190ecc02e8b456b") }
},
{
$lookup : {
from : 'sale',
localField: 'categories.0',
foreignField: 'saleCategoryId',
as : 'pcSales'
}
}]);
Result :
{
"_id" : ObjectId("57c6957fb190ecc02e8b456b"),
"categories" : [
"57c54f0db190ec430d8b4571"
],
"pcSales" : [
{
"_id" : ObjectId("57c7df5f30fb6eacb3810d1b"),
"Title" : "Latest Arrivals",
}
]}
This query will return me a match but when i check it not a match. I don't get why is this happening, And when i removed 0th part from query its return blank array.
Like this:
{
"_id" : ObjectId("57c6957fb190ecc02e8b456b"),
"categories" : [
"57c54f0db190ec430d8b4571"
],
"pcSales" : []
}
saleCategoryId is also a array field which contain array of categoriesKey.
Please help.
Because your localField is an array, you'll need to add an $unwind stage to your pipeline before the lookup or use the $arrayElemAt in a $project pipeline step to get the actual element in the array.
Here are two examples, one which uses the $arrayElemAt operator:
db.products.aggregate([
{ "$match" : { "_id": ObjectId("57c6957fb190ecc02e8b456b") } },
{
"$project": {
"category": { "$arrayElemAt": [ "$categories", 0 ] }
}
},
{
"$lookup": {
from : 'sale',
localField: 'category',
foreignField: 'saleCategoryId',
as : 'pcSales'
}
}
]);
and this which uses $unwind to flatten the categories array first before applying the $lookup pipeline:
db.products.aggregate([
{ "$match" : { "_id": ObjectId("57c6957fb190ecc02e8b456b") } },
{ "$unwind": "$categories" },
{
"$lookup": {
from : 'sale',
localField: 'categories',
foreignField: 'saleCategoryId',
as : 'pcSales'
}
}
]);