Mongodb: How to retrieve element from array? - mongodb

Consider this mongodb document:
{
"_id:"0,
"firstname":"Tom",
"profiles" : [
{
"profile_name": "tom",
"reward_programs:[
{
'program_name':'American',
'username':'tomdoe',
},
{
'program_name':'Delta',
'username':'tomdoe',
}
]
"settings": {
'auto_update': "False"
}
},
{
"profile_name": "harry",
"reward_programs:[
{
'program_name':'American',
'username':'car',
'account':'train',
},
{
'program_name':'Delta',
'username':'harrydoe',
}
]
"settings": {
'auto_update': "False"
}
}
]
}
How would I retrieve just the 'settings' dictionary for a particular profile name? Let's use 'harry' in the example. I have tried:
result = users.find_one({'_id': request._id, 'profiles.profile_name': 'harry'}, {'_id': 0, 'profiles.$.settings': 1})
But this retrieves the entire dictionary of profile_name:'harry'
{
'profiles':
{
"profile_name": "harry",
"reward_programs:[
{
'program_name':'American',
'username':'car',
'account':'train',
},
{
'program_name':'Delta',
'username':'harrydoe',
}
]
"settings": {
'auto_update': "False"
}
}
}
I would prefer getting the result as
{
"profiles" : [
{
"settings": {
'auto_update': "False"
}
}
]
}
And if simple enough I would even prefer:
{
"settings": {
'auto_update': "False"
}
}
I obviously have my projection messed up but I don't know how to fix it. Suggestions?

You need to use aggregation framework for this. Something like this will give what you want -
result = db.users.aggregate( [
{ $unwind : "$profiles"},
{ $match : { "profiles.profile_name" : "harry"}},
{ $project : { settings : "$profiles.settings" } }
] )
If you don't want '_id' field then you can hide it like this -
result = db.users.aggregate( [
{ $unwind : "$profiles"},
{ $match : { "profiles.profile_name" : "harry"}},
{ $project : { '_id' : 0, settings : "$profiles.settings" } }
] )
The result variable will have -
{ "settings" : { "auto_update" : "False" } }

I guess mongodb has added some functionality since the question was answered. Just adding an answer for being current. In the projection you could just give the key from the embedded document and get the required embedded document alone.
result = users.find_one({'_id': request._id, 'profiles.profile_name': 'harry'},{'_id': 0,'settings': 1})

Related

how can I modify a field name / key in a nested array of objects in mongodb?

I have a mongodb collection with a number of objects like this:
{
"_id" : "1234",
"type" : "automatic",
"subtypes" : [
{
"_id" : "dfgd",
"name" : "test subtype",
"subjetRequired" : true,
},
{
"_id" : "dfgd",
"name" : "test subtype2",
"subjetRequired" : false,
}
],
"anotherField" : "some value"
}
As you can see, one of the keys in the subtypes array is incorrectly spelled - "subjetRequired" instead of "subjectRequired".
I want to correct that key name. How can I do that.
I'll preface this by saying I've not worked with mongodb very much in the past.
After a lot of researching, the best I could come up with is the following (which doesn't work):
function remap(doc) {
subtypes = doc.subtypes;
var count = 0;
subtypes.forEach(function(subtype){
db.taskType.update({"_id": subtype._id}, {
$set: {"subtypes.subjectRequired" : subtype.subjetRequired},
$unset: {"subtypes.subjetRequired": 1}
});
}
)
}
db.taskType.find({"subtypes.subjetRequired":{$ne:null}}).forEach(remap);
This doesn't work.
I know the loop is correct, as if I replace the other logic with print statements I can access and print the fields who's names I want to modify.
What am I doing wrong here?
You can use this update and avoid using any code, it's also stable so you can execute it multiple times with no fear.
db.collection.updateMany({
"subtypes.subjetRequired": {
$exists: true
}
},
[
{
$set: {
subtypes: {
$map: {
input: "$subtypes",
in: {
$mergeObjects: [
"$$this",
{
subjectRequired: "$$this.subjetRequired",
}
]
}
}
}
}
},
{
$unset: "subtypes.subjetRequired"
}
])
Mongo Playground
I could modify your loop to override the whole array of subtypes:
function remap(doc) {
correctSubtypes = doc.subtypes.map(({ subjetRequired, ...rest }) => ({
...rest,
subjectRequired: subjetRequired,
}));
var count = 0;
db.taskType.findByIdAndUpdate(doc._id, {
$set: {
subtypes: correctSubtypes,
},
});
}

Trying to iterate through a collection of MongoDb results to update another collection

I'm trying to update some documents from DB-collection1 (source db) over to DB-collection2 (destination DB) .. all on the same MongoDb (with same permissions, etc).
So for each document from DB-Collection1, update a specific document in DB-collectoin2, if it exists.
The documents in DB-collection1 have following shape:
{
"_id": {
"commentId": "082f3de6-a268-46b5-803f-89bafd172621"
},
"appliesTo": {
"targets": [
{
"_id": {
"documentId": "b1eb1ad5-e74c-4a64-a4f3-bdc67ba70b35"
},
"type": "Document"
}
]
}
}
And the matching document in DB-collection2 is:
{
"_id": {
"documentId": "b1eb1ad5-e74c-4a64-a4f3-bdc67ba70b35"
},
"name": "jill"
},
I'm using a cursor to iterate through the source collection but I'm not sure how I can do this?
This is the javascript code mongo shell script I'm trying right now, when I run the following command on a machine where mongo is installed:
CLI: root#f0cc2f13e70c:/src/scripts# mongo --host localhost --username root --password example copyFoosToBars.js
// copyFoosToBars.js
function main() {
print('Starting script.')
print()
var foosDb = db.getSiblingDB('foos');
var barsDb = db.getSiblingDB('bars');
// Grab all the 'foos' which have a some barId in some convoluted schema.
var sourceFoos = foosDb.getCollection('foos')
.find(
{
"appliesTo.targets.type" : "Document",
"_meta.deleted": null
},
{
"_id" : 0,
"appliesTo.targets._id.documentId" : 1
}
);
sourceFoos.forEach(function(foo){
// Check if this document exists in the bars-db
var desinationBars = barsDb.getCollection('bars')
.find(
{
"_id.documentId" : foo.appliesTo.targets._id.documentId,
},
);
printjson(desinationBars);
// If destinationBars document exists, then add a the field 'Text' : 'hi there' to the document -or- update the existing field, if the 'Text' field already exists in this document.
});
print()
print()
print('----------------------------------------------')
}
main()
So here's some sample json output for the first part of the query -> which proves that I have some data which passes that 'find/search' clause:
Starting script.
{
"appliesTo" : {
"targets" : [
{
"_id" : {
"barId" : "810e66e2-66d1-44f4-be0e-980309d8df8f"
}
}
]
}
}
{
"appliesTo" : {
"targets" : [
{
"_id" : {
"barId" : "54f25223-67bb-4d5d-ad47-24392e4acbdf"
}
}
]
}
}
{
"appliesTo" : {
"targets" : [
{
"_id" : {
"barId" : "34c83da3-eafd-41bf-93af-3a45d1644225"
}
}
]
}
}
This doesn't work.
MongoDB server version: 4.0.22
WARNING: shell and server versions do not match
Starting script.
uncaught exception: TypeError: comment.appliesTo.targets._id is undefined :
main/
<snip>
Can someone please suggest some clues as to how I can fix this, please?
First of all you need to safeguard against multiple items in the appliesTo.targets.
A document
{
"_id": {
"commentId": "082f3de6-a268-46b5-803f-89bafd172621"
},
"appliesTo": {
"targets": [
{
"_id": {
"documentId": "should-not-be-updated"
},
"type": "AnyOtherType"
},
{
"_id": {
"documentId": "b1eb1ad5-e74c-4a64-a4f3-bdc67ba70b35"
},
"type": "Document"
}
]
}
}
Will be selected by
.find(
{
"appliesTo.targets.type" : "Document",
"_meta.deleted": null
},
{
"_id" : 0,
"appliesTo.targets._id.documentId" : 1
}
);
with the resulting document:
{
"appliesTo": {
"targets": [
{
"_id": {
"documentId": "should-not-be-updated"
}
},
{
"_id": {
"documentId": "b1eb1ad5-e74c-4a64-a4f3-bdc67ba70b35"
}
}
]
}
}
so foo.appliesTo.targets[0]._id.documentId will be "should-not-be-updated".
Structure of the document does not allow to use $elemMatch, so you have to either use aggregation framework or filter the array clientside. The aggregation has benefit of running serverside and reduce amount of data to transfer to the client.
Secondly, there is no point to find documents from DB-collection2. You can update all matching ones straight away, like in "update...where" SQL .
So the code must be something like following:
var sourceFoos = db.foos.aggregate([
{
$unwind: "$appliesTo.targets"
},
{
$match: {
"appliesTo.targets.type": "Document",
"appliesTo.targets._id.documentId": {
$exists: true
},
"_meta.deleted": null
}
},
{
$project: {
_id: 0,
"documentId": "$appliesTo.targets._id.documentId"
}
}
]);
sourceFoos.forEach(function(foo){
db.bars.updateMany(
{"_id.documentId" : foo.documentId},
{$set:{'Text' : 'hi there'}}
)
})
If there are a lot of documents expected in the cursor I would recommend to look at bulk updates to speed it up, but as I mentioned earlier in this case mongo shell might not be an ideal tool.
target is an array so for accessing to the _id try like this :
foo.appliesTo.targets[0]._id.barId
use async/await with try/catch and use .toArray() after find query
// copyFoosToBars.js
async function main() {
try {
var foosDb = db.getSiblingDB("foos");
var barsDb = db.getSiblingDB("bars");
// Grab all the 'foos' which have a some barId in some convoluted schema.
var sourceFoos = await foosDb
.getCollection("foos")
.find(
{
"appliesTo.targets.type": "Bar",
"_meta.deleted": null,
},
{
_id: 0,
"appliesTo.targets._id.fooId": 1,
}
).toArray();
for (foo of sourceFoos) {
var desinationBars = await barsDb
.getCollection("bars")
.find({
"_id.barId": foo.appliesTo.targets[0]._id.barId,
})
.toArray();
console.log(desinationBars);
if(desinationBars.length> 0){
//do somthings
}
}
} catch (error) {
console.log(error)
}
}
in the first find query you select "appliesTo.targets._id.fooId" : 1so select ( fooId but in the result json show "barId" : "810e66e2-66d1-44f4-be0e-980309d8df8f" it's conflict, Anyway, I hope this solution solves your problem

Trim string values of whitespace, from an array of sub-documents with string field

On all documents of my collection I want to perform a $trim operation to a specific field of an object that is in an array.
Example:
{
"_id" : ObjectId("53857680f7b2eb611e843a32"),
"company": Testcompany
"customer" :
"name": Testuser,
"addresses" : [
{
"_id" : ObjectId("5d6d2f96e3fdc8001077ac6c"),
"street" : "Teststreet. ",
"houseNr" : "133",
},
{
"_id" : ObjectId("5d6d2f96e3fdc8001077ac7b"),
"street" : " Simplestreet. ",
"houseNr" : "12",
}
],
}
In the example, I want to $trim all values of the field: "customer.addresses.street"
To answer the upcoming questions:
I know the article you mentioned (Removing white spaces (leading and trailing) from string value) but theres no example how to do it within an array.
My problem is, how to access the attributes within an array, heres the example of plain values:
[{ $set: { category: { $trim: { input: "$category" } } } }],
Yes, I want to update the values of all documents within the collection
One possible way to do:
db.YOUR_COLLECTION.find({}).forEach(
function(doc) {
db.Trim.update(
{ "_id":doc._id },
{
"$set": {
"customer.addresses":doc.customer.addresses.map(
function(child) {
return Object.assign(
child,
{ street: child.street.trim() }
)
}
)
}
}
)
}
)
Obs: Solution with Javascript Executed in MongoShell.
You can use $map and $trim in an updateMany aggregation pipeline like this :
db.YOUR_COLLECTION.updateMany({"customer.addresses":{$ne:null}},[
{
$set: {
"customer.addresses":
{
$map: {
input: "$customer.addresses",
as: "address",
in: { $trim: { input: "$$address" } }
}
}
}
}
])

Sum Embedded Document Fields in Mongo Aggregate Query

Data looks like:
{
"_id":5345345345,
"city": "Detroit",
"people":[
{
"name":"Larry",
"value":1200
},
{
"name":"Steve",
"value":1100
},
{
"name":"Larry",
"value":1000
},
{
"name":"Joe",
"value":800
},
{
"name":"Larry",
"value":500
},
{
"name":"Joe",
"value":700
}
]
}
I have this in the middle of an aggregate query. What can I do from here to sum the value field and have unique entry for each person in "people"? i.e.
{
"_id":5345345345,
"city": "Detroit",
"people":[
{
"name":"Larry",
"value":2700
},
{
"name":"Steve",
"value":1100
},
{
"name":"Joe",
"value":1500
}
]
}
Also, cannot have query that returns empty document in case the "people" array is empty, e.g., cannot do:
{$unwind:{
path:people
}
},
{$group :
{
_id:"$people.name",
total_value:{"$sum":"people.value"}
}
}
which would work fine, if it didn't return an empty document when people array is empty.
Need to use $addToSet and $sum somehow? How to do this keeping value associated with name?
Use
{
$unwind: {
path : "$people",
preserveNullAndEmptyArrays : true // this line!
}
}

How to add key to $addToSet in mongoDB

I want to add a key inside mongodb add function. I am doing this right now.
$addToSet : {
"msges":{
time:{"from":uname,"title":title,"msg":msg,"read":false}
}
}
time is a variable that is coming from the paramater. It has time inside it as hh:mm:ss A. But when the query runs, instead of time as key, string "time" gets print as key. Any ideas what should I do?
Enclose your variable in [] :
$addToSet: {
"msges": {
[time]: { "from": uname, "title": title, "msg": msg, "read": false }
}
}
For instance :
var myfield = "custom_field";
db.test.update({
_id: 1
}, {
$addToSet: {
letters: [{
[myfield]: 1
}, {
[myfield]: 2
}]
}
})
It gives :
{ "_id" : 1, "letters" : [ [ { "custom_field" : 1 }, { "custom_field" : 2 } ] ] }