MongoDB Query Embedded Document Field - mongodb

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

Related

mongodb get all documents but only specific sub document

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

How to update particular value inside array - Mongodb

I have a mongodb collection Users that looks like this:
{
"_id" : ObjectId("5dba8987b4a39c13bc104a23"),
"contact" : {
"firstName" : "Mark",
"lastName" : "Doe",
"parentsBloodType" : [
{
"type" : "AB+",
},
{
"type" : "A+",
},
],
},
"createdAt" : ISODate("2019-10-31T07:13:11.278Z"),
"updatedAt" : ISODate("2019-11-26T09:59:41.611Z")
}
I need to run a raw query to update from AB+ to O-. I would also like to check if they match before updating.
I tried this but it added an extra field to the User:
db.getCollection('users').update(
{"_id": ObjectId("5dba8987b4a39c13bc104a23")},
{"$set" : { 'parentsBloodType.' + '0' + '. type' : "O-"}}
)
This one would do the update:
db.users.update(
{ "_id": ObjectId("5dba8987b4a39c13bc104a23") },
{ "$set": { 'contact.parentsBloodType.0.type' : "O-"} }
)
You an check match with this:
db.users.update(
{
"_id": ObjectId("5dba8987b4a39c13bc104a23"),
"contact.parentsBloodType.type": "AB+"
},
{ "$set": { 'contact.parentsBloodType.0.type': "AB+" } }
)
This updates the document only if type AB+ exist (at any parent).
Or if you like to check whether the first type is AB+ then use
db.users.update(
{
"_id": ObjectId("5dba8987b4a39c13bc104a23"),
"contact.parentsBloodType.0.type": "AB+"
},
{ "$set": { 'contact.parentsBloodType.0.type': "AB+" } }
)
However, I assume you are actually looking for this:
db.users.update(
{ "_id": ObjectId("5dba8987b4a39c13bc104a23") },
{ "$set": { 'contact.parentsBloodType.$[p].type': "0-" } },
{ arrayFilters: [{ "p.type": "AB+" }] }
)
Which will update any AB+ to 0- no matter on which position it appears in the array.
You could use the positional $ operator to update the first element that matches the query document:
db.getCollection('users').update(
{ "_id": ObjectId("5dba8987b4a39c13bc104a23"), "parentsBloodType.type": "AB+" },
{ "$set" : { 'parentsBloodType.$.type' : "O-" } }
)
note that:
the positional $ operator acts as a placeholder for the first element that matches the query document, and
the array field must appear as part of the query document.
Or use the filtered positional operator $[] if you want to update all elements that match an array filter condition or conditions:
db.students.update(
{ "_id": ObjectId("5dba8987b4a39c13bc104a23") },
{ $set: { "parentsBloodType.$[element].type" : "O-" } },
{
arrayFilters: [ { "element.type": "AB+" } ]
}
)

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

Returning only subdocuments in 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