Returning only subdocuments in MongoDB - mongodb

I have the following documents in my MongoDB collection:
{
"name": "name",
"items": [
{
"raw": { ... }
"processed": { ... }
},
{
"raw": { ... }
"processed": { ... }
}
]
}
And I'm trying to aggregate / query the database such that I get these items:
[
{"raw": { ... }},
{"raw": { ... }}
]
I'm using the aggregation framework now, but I'm stuck at the part where I want to exclude fields of the outer document.
My current query is:
db.mycollections.aggregate([
{ $unwind: "$items" },
{ $project: { "items.raw": 1 } }
])
And it returns:
[
{"items: {"raw": { ... }}},
{"items: {"raw": { ... }}}
]
Is there a way to only return the subdocuments from the query above?

If you write aggregation with unwind as :
db.mycollections.aggregate({"$unwind":"$items"})
then output looks like :
{ "_id" : ObjectId(), "name" : "name", "items" : { "raw" : {... }, "processed" : { ... } } }
{ "_id" : ObjectId() , "name" : "name", "items" : { "raw" : { ...}, "processed" : { ...} } }
$project passes along the documents with only the specified fields to the next stage in the pipeline. The specified fields can be existing fields from the input documents or newly computed fields.
and you pass $project as your existing fields with items.raw so instead of passing this existing field to project use expression with new field name as raw and changed your aggregation as
db.mycollections.aggregate({"$unwind":"$items"},{"$project":{"raw":"$items.raw"}})
For more details check mongo aggregation pipeline

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

MongoDB concat two fields using aggregation where actual field is in an array

I have a mongo document like below;
{
"_id" : "123",
"info" : {
"batch" : "Batch1-Minor"
},
"batchElements" : {
"elements" : [
{
"_id" : "elementId1",
"type": "ABC"
},
{
"_id" : "elementId2",
"type": "ABC"
}
]
}
}
How can generate an aggregated output by changing the _id field inside elements by concatenating $info.batch and $batchElements.elements._id
Expected Output:
{
"_id" : "123",
"info" : {
"batch" : "Batch1-Minor"
},
"batchElements" : {
"elements" : [
{
"_id" : "Batch1-Minor-elementId1",
"type": "ABC"
},
{
"_id" : "Batch1-Minor-elementId2",
"type": "ABC"
}
]
}
}
With below query we're iterating through batchElements.elements & forming objects with type & _id & finally $map would push an array of objects back to batchElements.elements where $addFields would add the field to actual document, Try this :
db.yourCollectionName.aggregate([{
$addFields: {
'batchElements.elements': {
$map:
{
input: "$batchElements.elements",
as: "each",
in: { type: '$$each.type', '_id': { $concat: ["$info.batch", "-", "$$each._id"] } }
/** So if you've multiple fields in each object then instead of listing all, You need to use
in: { $mergeObjects: ["$$each", { '_id': { $concat: ["$info.batch", "-", "$$each._id"] } }] } */
}
}
}
}])

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"
}
]
}
}

MongoDB Query Embedded Document Field

My collection structure is like in the picture below:
I'm trying to query the field "name" inside every engine:
db.getCollection('scan').find({},
{
"engines": {
"$elemMatch": {
"name": 1
}
}
}
)
but the returning results contain only the "_id":
Does anyone know why?
Thanks!
I'm trying to query the field "name" inside every engine:
Assuming this data sample:
db.collection.insert([
{ engines: { ahnlab: { name: "x", value: "1" }}},
{ engines: { ahnlab: { name: "y", value: "2" }}},
])
You can query all embedded fields name using the dot-notation in the projection operator:
> db.collection.find({},{"engines.ahnlab.name": 1, "_id":0 })
{ "engines" : { "ahnlab" : { "name" : "x" } } }
{ "engines" : { "ahnlab" : { "name" : "y" } } }
"engines.ahnlab.name": 1 will instruct MongoDB to keep (1) the embedded name field;
"_id": 0 will instruct MongoDB to not keep the _id field.
If you need your output as a flat data structure, you should use the aggregation framework and the $project operator to rewrite your documents:
> db.collection.aggregate([
{$project: { name: "$engines.ahnlab.name", _id: 0 }}
])
{ "name" : "x" }
{ "name" : "y" }

How to retrieve all matching elements present inside array in Mongo DB?

I have document shown below:
{
name: "testing",
place:"London",
documents: [
{
x:1,
y:2,
},
{
x:1,
y:3,
},
{
x:4,
y:3,
}
]
}
I want to retrieve all matching documents i.e. I want o/p in below format:
{
name: "testing",
place:"London",
documents: [
{
x:1,
y:2,
},
{
x:1,
y:3,
}
]
}
What I have tried is :
db.test.find({"documents.x": 1},{_id: 0, documents: {$elemMatch: {x: 1}}});
But, it gives first entry only.
As JohnnyHK said, the answer in MongoDB: select matched elements of subcollection explains it well.
In your case, the aggregate would look like this:
(note: the first match is not strictly necessary, but it helps in regards of performance (can use index) and memory usage ($unwind on a limited set)
> db.xx.aggregate([
... // find the relevant documents in the collection
... // uses index, if defined on documents.x
... { $match: { documents: { $elemMatch: { "x": 1 } } } },
... // flatten array documennts
... { $unwind : "$documents" },
... // match for elements, "documents" is no longer an array
... { $match: { "documents.x" : 1 } },
... // re-create documents array
... { $group : { _id : "$_id", documents : { $addToSet : "$documents" } }}
... ]);
{
"result" : [
{
"_id" : ObjectId("515e2e6657a0887a97cc8d1a"),
"documents" : [
{
"x" : 1,
"y" : 3
},
{
"x" : 1,
"y" : 2
}
]
}
],
"ok" : 1
}
For more information about aggregate(), see http://docs.mongodb.org/manual/applications/aggregation/