Adding multiple key/values - mongodb

In my database.collection i.e. db.blog.posts I am trying to add a key and value that itself has multiple keys and values.
Current collection:
db.blog.posts.findOne()
"title":"blog posts"
I tried using $set, $push but nothing seems to work.
This also didn't work when I tried adding single collection:
db.blog.posts.updateOne({"title":"blog posts"}, {"$set":{"comments":[{"comment":"good post", "author":"john","votes":0}]}})
Nor insertOne instead of updateOne and I even tried with:
var myEmployee=[
{"comment":"good post", "author":"john", "votes":0},
{"comment":"i thought it was too short", "author":"claire","votes":3},
{"comment":"free watches", "author":"claire","votes":-1},
];
db.blog.posts.insert(myEmployee)
This is what I want:
"title" : "A blog post",
"comments" : [
{
"name" : "joe",
"email" : "joe#example.com",
"content" : "nice post."
},
{
"name" : "bob",
"email" : "bob#example.com",
"content" : "good post."
}
]

The updateOne command you have should have created an array for comments with a single entry. If you wanted multiple entries, you can just add multiple objects to the array in the update. The $set operator will change the value of the key to what you set as the second parameter.
db['blog.posts'].updateOne({"title":"blog posts"}, {
"$set": {
"comments":[
{
"name" : "joe",
"email" : "joe#example.com",
"content" : "nice post."
},
{
"name" : "bob",
"email" : "bob#example.com",
"content" : "good post."
}
]
}
})
If you want to add additional items to the comments, this can be done with $push. The $push operator adds to the array.
db['blog.posts'].updateOne({"title":"blog posts"}, {
"$push": {
"comments": {
"comment": "good post",
"author": "john",
"votes": 0
}
}
})
Docs for $set
Docs for $push
NB the examples above are for a collection named 'blog.posts' rather than a database named 'blog' and a collection names 'posts'. Ideally, brackets should be used for the property accessor where the collection name is not a valid JavaScript identifier although the dot notation in the question still works.

Related

Mongodb query to return field value

I am trying to construct a Mongodb query to return a field value. My JSON looks like this:
"question" : "Global_Deployment",
"displayOrder" : 1,
"answerOptions" : {
"fieldId" : "1001",
"fieldType" : "radiobutton",
"fieldName" : "Global Deployment?",
"fieldLabel" : "Global Deployment?",
"helpText" : "Help will go here",
"emailTagFormControl" : "Global_Deployment?",
"source" : "custom",
"status" : "active",
"required" : "true",
"multiSelect" : "false",
"purgeFlag" : "false",
"enableAuditTrack" : "false",
"fields" : [],
"fieldValue" : "Yes",
"options" : [
{
"optionName" : "Yes"
},
{
"optionName" : "No"
}
],
"comments" : {
"commentId" : "C1001",
"commentDetails" : []
}
My query to reach the field with the fieldName "Global Deployment" is this:
db.getCollection('requests').find({"sections.questions.answerOptions.fieldName":"Global Deployment?"})
What I want to know is what to add to this query to return the value of "fieldValue", which is on a different line in the JSON. I am new to Mongodb. Any help would be greatly appreciated.
1) If you've multiple documents in DB with "fieldName" : "Global Deployment?", then .find() would return all the matching documents i.e; in the output what you get is an array of documents then you need to iterate through the array to get answerOptions.fieldValue for each document, Check the below scenario, as I've explained there are chances of getting multiple documents if "sections.questions.answerOptions.fieldName" is not an unique field.
db.getCollection('requests').find({"sections.questions.answerOptions.fieldName":"Global Deployment?"}, {'sections.questions.answerOptions.fieldValue':1})
Output of find :
/* 1 */
[{
"_id" : ObjectId("5d4e19826e173840500f5674"),
"answerOptions" : {
"fieldValue" : "Yes"
}
},
/* 2 */
{
"_id" : ObjectId("5d4e19826e073840500f5674"),
"answerOptions" : {}
}]
If you only need documents which has fieldValue in it then do this :
db.getCollection('requests').find({"sections.questions.answerOptions.fieldName":"Global Deployment?", 'sections.questions.answerOptions.fieldValue':{$exists: true}}, {'answerOptions.fieldValue':1})
Ok now you've array of documents then do iterate thru each to retrieve your value, check this mongoDB cursor tutorial .
2) If you think fieldName is unique across collection, then you can use .findOne() , which would exactly return one document (In case if you've multiple matching documents it would return first found doc) :
db.getCollection('requests').findOne({"sections.questions.answerOptions.fieldName":"Global Deployment?"}, {'sections.questions.answerOptions.fieldValue':1})
Output of findOne :
{
"_id" : ObjectId("5d4e19826e173840500f5674"),
"answerOptions" : {
"fieldValue" : "Yes"
}
}
If you see .find({},{}) has two arguments, second one is called projection which literally be useful if you want to retrieve only required fields in the response, By default mongoDB will return the entire document what ever you've posted in the question will be retrieved, Data in mongoDB flows as JSON's so operating will be similar to using JSON's, Here you can retrieve the required fields out of result, but for best use of network efficiency if you don't need entire document you'll only get the required fields using projection.
You can specify the second condition separated by comma. Either you are trying to filter data with $and or with $or
With simple approach:
{"sections.questions.answerOptions.fieldName":"Global Deployment?","sections.questions.answerOptions.fieldValue":"Yes" }
By using $and method:
.find(
{
$and: [
{"sections.questions.answerOptions.fieldName":"Global Deployment?"},
{"sections.questions.answerOptions.fieldValue":"Yes"}
]
}
)
Same way you can use $or method. Just replace $and with $or.
Edit:
If you want to retrieve specific value (in your case fieldValue), query would be:
db.getCollection('requests').find({
"sections.questions.answerOptions.fieldName":"Global Deployment?"
}).map(function(item){
return item.fieldValue
})
The correct answer here is the method .distinct() (docs)
In your case try it like this:
db.getCollection('requests').find({"sections.questions.answerOptions.fieldName":"Global Deployment?"}).distinct('fieldValue');
That will return only the value you want.
If you use findOne you can use dot notation.
For example, if we start with creating a collection to test using the following to get close to your sample:
db.stackOverflow.insertOne({
sections: {
questions: {
question: "Global_Deployment",
displayOrder: 1,
answerOptions: {
fieldId: "1001",
fieldType: "radiobutton",
fieldName: "Global Deployment?",
fieldLabel: "Global Deployment?",
helpText: "Help will go here",
emailTagFormControl: "Global_Deployment?",
source: "custom",
status: "active",
required: "true",
multiSelect: "false",
purgeFlag: "false",
enableAuditTrack: "false",
fields: [],
fieldValue: "Yes",
options: [
{
optionName: "Yes",
},
{
optionName: "No",
},
],
comments: {
commentId: "C1001",
commentDetails: [],
},
},
},
},
})
then, this query will return "Yes".
db.stackOverflow.findOne({}).sections.questions.answerOptions.fieldValue

Meteor/MongoDB Reference specific subdocument from another document

I'm working on prototyping a note-taking application in Meteor; functional requirements include:
users have access to shared notes
notes contain distinct sections
each user needs to be able to add notations to notes/sections
notations can be preserved over time (e.g. add to existing notations without updating or deleting previously created notation)
notations should be private between users
Given the above, each document has a data key that contains the array of subdocuments - each section of the note. Something like this:
{
"_id" : ObjectId("someObjectID"),
"owner" : "Q5mpJZnAtFN5EMWT9",
"createdAt" : "2018-01-05T22:56:03.257Z",
"updatedAt" : "2018-01-06T12:07:03.123Z",
"parent" : null,
"title" : "Note Title",
"data" : [
{
"date" : "2018-01-05T22:56:03.257Z",
"title" : "Section 1 Title",
"text" : "Section content goes here..."
},
{
"date" : "2018-01-05T22:56:03.257Z",
"title" : "Section 2 Title",
"text" : "Section content goes here..."
}
]
}
For the main notes documents, the data array stores the sections as subdocuments; for user notations, the data array stores their personal notations as subdocuments. My thinking is to use the parent key to distinguish between shared notes and user notations:
parent : null for "top level", shared notes
something like parent : "yG8xrh6KiZXv7e8MD" to point back to the "top level" note or subdocument for user notations. (Hopefully this makes sense).
Two questions. First and foremost - is this a valid design?
If it IS a valid design, how do I then reference a specific subdocument? For example, in the above document, if a user wants to add a notation to Section 2 only? Can I add an _id to the subdocument and then use that value for the parent key in the notation document?
This not the complete solution, but just an example:
I would do it something like this. I'd modify your document a bit, adding notations field in every section:
{
"_id" : ObjectId("someObjectID"),
"owner" : "Q5mpJZnAtFN5EMWT9",
"createdAt" : "2018-01-05T22:56:03.257Z",
"updatedAt" : "2018-01-06T12:07:03.123Z",
"parent" : null,
"title" : "Note Title",
"data" : [
{
"date" : "2018-01-05T22:56:03.257Z",
"title" : "Section 1 Title",
"text" : "Section content goes here...",
"notations": [
{
_id: "some id",
version:1
userId: "fsajksffhj",
date: "2018-01-05T22:56:06",
note: "some note about this sectioon"
},
{
_id: "some id2",
version:1,
userId: "fsajksffhj",
date: "2018-01-05T22:56:06",
note: "some note about this sectioon"
},
{
_id: "some id1",
version:1,
userId: "fsajksffhj",
date: "2018-02-06T00:56:06",
note: "edited the first notation"
}
]
},
{
"date" : "2018-01-05T22:56:03.257Z",
"title" : "Section 2 Title",
"text" : "Section content goes here..."
}
]
}
notations should be private between users
This is harder part. I'd use Meteor Methods to do this. Another way could be to use MongoDB's aggregation functionality with match, unwind, re-match, group and create document again. You are using reactivity if using either of these.
Meteor.methods({
'notes.singleNote: function(noteId, notationsUserId) {
check(noteId, String);
check(notationsUserId);
let note = Notes.findOne(noteId);
// remove other users' notations
note.data = note.data.map(function(data) {
if (data.notations) {
data.notations = data.notations.filter(function(d) {
return d.userId === notationsUserId;
});
}
return data
});
});
return note;
}
});

Using $addToSet to update an array field using another array field

I should start with: I'm knew to MongoDB, and document-style databases in general.
I have a collection that looks something like this:
{
"_id" : ObjectId("554a5e72b16f31ff0894310e"),
"title" : "ABC",
"admins" : [
"personA",
"personB",
],
"email_address" : "ABC#mysite.com"
}
{
"_id" : ObjectId("554a5e72b16f31ff0894310f"),
"title" : "Junk Site",
"admins" : [
"personA",
"personB"
],
"email_address" : "garbage#mysite.com"
}
{
"_id" : ObjectId("554a5e72b16f31ff08943110"),
"title" : "Company Three Site",
"admins" : [
"personC"
"personD",
],
"email_address" : "company2plus1#mysite.com"
}
What I need to do, is append the admins list from Company One, to Company Three such that Company Three now has four admins (A, B, C, D).
I tried the following, because it seemed pretty straight forward to me - get the data from the origin and append to destination directly:
db.runCommand({
findAndModify : 'sites',
query : {'title' : 'Company Three Site'},
update : { '$addToSet' :
{'admins' :
db.projects.find({'title' : 'ABC'}, {'_id' : 0, 'admins' : 1}
}
}
})
However, this does not work correctly.
I am still trying to figure out ways I could do this directly, but questions...
1) Is this even possible by using single command, or do I need to split this up?
2) Does my train of logical thought make sense, or should I be doing this some other/easier way that is more conventional for MongoDB style databases?
db.projects.find actually returns a cursor, which you definitely don't want to add to your set. Since you know ahead of time that you will be only finding one value, you can get the properties out of the cursor specifically by using .next().admin -- but remember that this will only work with the first value returned from .find. Otherwise, I think you will have to use a loop.
$addToSet will also add the array as a whole, so instead you have to append multiple values using $each
All together:
db.runCommand({
findAndModify: 'sites',
query: {'title': 'Company Three Site'},
update: {
$addToSet: {
"admins": {
$each: db.projects.find(
{"title": "ABC"},
{"_id": 0, "admins": 1}
).next().admins
}
}
}
})
This is not possible with an atomic update. However, a workaround is to query the source collection using the find() method and use the cursor's forEach() method to iterate over the results, get the array and update the destination collection using the $addToSet operator and the $each modifier.
Let's demonstrate this with the above sample documents inserted to a test collection:
db.test.insert([
{
"title" : "ABC",
"admins" : [
"personA",
"personB"
],
"email_address" : "ABC#mysite.com"
},
{
"title" : "Junk Site",
"admins" : [
"personA",
"personB"
],
"email_address" : "garbage#mysite.com"
},
{
"title" : "Company Three Site",
"admins" : [
"personC",
"personD"
],
"email_address" : "company2plus1#mysite.com"
}
])
The following operation will add the admins array elements from company "ABC" to the company "Company Three Site" admin array:
db.test.find({"title" : "ABC"}).forEach(function (doc){
var admins = doc.admins;
db.test.update(
{"title" : "Company Three Site"},
{
"$addToSet": {
"admins": { "$each": admins }
}
},
{ "multi": true }
);
});
Querying the collection for the document with company "Company Three Site" db.collection.find({"title" : "Company Three Site"});
will yield:
/* 0 */
{
"_id" : ObjectId("554a7dc35c5e0118072dd885"),
"title" : "Company Three Site",
"admins" : [
"personC",
"personD",
"personA",
"personB"
],
"email_address" : "company2plus1#mysite.com"
}

How do I query a hash sub-object that is dynamic in mongodb?

I currently have a Question object and am not sure how to query for it?
{ "title" : "Do you eat fast food?"
"answers" : [
{
"_id" : "506b422ff42c95000e00000d",
"title" : "Yes",
"trait_score_modifiers" : {
"hungry" : 1
}
},
{
"_id" : "506b422ff42c95000e00000e",
"title" : "No",
"trait_score_modifiers" : {
"not-hungry" : -1
}
}]
}
I am trying to find questions where the trait_score_modifieres is queried (sometimes it exists, sometimes not)
I have the following but it is not dynamic:
db.questions.find({"answers.trait_score_modifiers.not-hungry":{$exists: true}})
How could i do something like this?
db.questions.find({"answers.trait_score_modifiers.{}.size":{$gt: 0}})
You should modify the schema so you have consistent key names to query on. I ran into a similar problem using the aggregation framework, see question: Total values from all keys in subdocument
Something like this should work (not tested):
{
"title" : "Do you eat fast food?"
"answers" : [
{
"title" : "Yes",
"trait_score_modifiers" : [
{"dimension": "hungry", "value": 1}
]
},
{
"title" : "No",
"trait_score_modifiers" : [
{"dimension": "not-hungry", "value": -1}
]
}]
}
You can return all questions that have a dynamic dimension (e.g. "my new dimension") with:
db.questions.find("answers.trait_score_modifiers.dimension": "my new dimension")
Or limit the returned set to questions that have a specific value on that dimension (e.g. > 0):
db.questions.find(
"answers.trait_score_modifiers": {
"$elemMatch": {
"dimension": "my new dimension",
"value": {"$gt": 0}
}
}
)
Querying nested arrays can be a bit tricky, be sure to read up on the documentation In this case, $elemMatch is needed because otherwise you return a document that has some trait_score_modifier my new dimension but the matching value is in the dimension key of a different array element.
You need $elemMatch criteria in your query.
Refer to: http://docs.mongodb.org/manual/reference/projection/elemMatch/
Let me know if you need the query.

How to do query on multiple nested data fields in MongoDB

So, what I'm trying to do is query all documents that have a City of 'Paris' and a State of 'France'. I need to do some kind of join, but I haven't been able to figure out how to construct it.
I'm using the c# driver, but I'll gladly accept help using any method.
{
"_id" : ObjectId("519b407f3c22a73a7c29269f"),
"DocumentID" : "1",
"Meta" : [{
"Name" : "City",
"Value" : "Paris",
}, {
"Name" : "State",
"Value" : "France",
}
}]
}
{
"_id" : ObjectId("519b407f3c22a73a7c29269g"),
"DocumentID" : "2",
"Meta" : [{
"Name" : "City",
"Value" : "Paris",
}, {
"Name" : "State",
"Value" : "Texas",
}
}]
}
The $elemMatch operator is used to indicate that all the conditions within it must be matched by the same array element. So (to switch to shell syntax) to match all documents which have meta city Paris you would do
db.collection.find( {Meta:{$elemMatch:{Name:"City",Value:"Paris"}}} )
This assures you won't match something which has Name: "somethingelse", Value: "Paris" somewhere in its array with a different array element matching the Name:"City".
Now, default combination for combining query conditions is "and" so you can continue adding attributes:
db.collection.find( {Meta: {
$elemMatch:{Name:"City",Value:"Paris"},
$elemMatch:{Name:"State",Value:"France"}
}
}
)
Now if you want to add another condition you keep adding it but if you want a NOT then you do it like this:
db.collection.find( {Meta: {
$elemMatch:{Name:"City",Value:"Paris"},
$elemMatch:{Name:"State",Value:"France"},
$not: {$elemMatch:{Name:"Arrondissement",Value:"Louvre"}}
}
}
)
I might be answering my own question here, but I'm new to MongoDB, so while this appears to give me the results I'm after, it might not be the optimum approach.
var result = collection.Find(
Query.And(
Query.ElemMatch("Meta", Query.EQ("Name", "City")),
Query.ElemMatch("Meta", Query.EQ("Value", "Paris")),
Query.ElemMatch("Meta", Query.EQ("Name", "State")),
Query.ElemMatch("Meta", Query.EQ("Value", "France")))
);
Which leads to a follow up - how would I get all of the documents whose 'City' is 'Paris' and 'State' is 'France' but whose 'Arrondissement' is not 'Louvre'?