I am imagining foo is doing update on the third comment, comments.2.value, while bar is $pull-ing, removing the first comment.
If foo finishes first, then the third comment is updated successfully, since the index is still correct.
But if bar finishes first, then the index has changed, and foo's comments.2.value would affect not the third comment anymore.
Is this scenario possible, and if it is, i wonder whether there are common solutions for array element updates and racing condition ?
Thank you !
The situation that you described is theoretically possible if multiple applications are accessing the database simultaneously. For this reason, it is best, if possible, to give each member of the array some unique identifier, rather than accessing elements in the array by position.
For example,
> db.myComments.save({_id:1,
comments:[
{cid:1, author:"Marc", comment:"Marc's Comment"},
{cid:2, author:"Mike", comment:"Mike's Comment"},
{cid:3, author:"Barrie", comment:"Barrie's Comment"}
]})
If we want to modify Mike's Comment, but we don't necessarily know that it will appear second in the array, we can update it like so:
> db.myComments.update({_id:1, "comments.cid":2}, {$set:{"comments.$.comment":"Mike's NEW Comment"}})
> db.myComments.find().pretty()
{
"_id" : 1,
"comments" : [
{
"cid" : 1,
"author" : "Marc",
"comment" : "Marc's Comment"
},
{
"author" : "Mike",
"cid" : 2,
"comment" : "Mike's NEW Comment"
},
{
"cid" : 3,
"author" : "Barrie",
"comment" : "Barrie's Comment"
}
]
}
We could even change the entire sub-document, like so:
> db.myComments.update({_id:1, "comments.cid":2}, {$set:{"comments.$":{cid:4, author:"someone else", comment:"A completely new comment!"}}})
> db.myComments.find().pretty()
{
"_id" : 1,
"comments" : [
{
"cid" : 1,
"author" : "Marc",
"comment" : "Marc's Comment"
},
{
"cid" : 4,
"author" : "someone else",
"comment" : "A completely new comment!"
},
{
"cid" : 3,
"author" : "Barrie",
"comment" : "Barrie's Comment"
}
]
}
The query document will find the first value in the array that matches, and the "$" in the update document references that position.
More information on the "$" operator may be found in the "The $ positional operator" section of the "Updating" documentation.
http://www.mongodb.org/display/DOCS/Updating#Updating-The%24positionaloperator
Hopefully this will give you an idea of how your application can modify values in an array without referencing their position. Good luck!
Related
I recently encountered an issue, and I'd like to solve it. If anyone would give any suggestion I'll be grateful.
I have documents that represent "users" and each document has a subarray that is responsible to save some codes, they can be many for each user. The matter is, each user cannot have duplicate codes in its specific array, but at the same time, in this case, each document should be isolated, for example, being possible to have two or more identical codes but since they are from different documents(users).
In short, the subarray("codes") cannot have individually duplicated codes(code), but that shouldn't interfere with other documents
I could do that in the application part, but I think doing that guarantee directly on DB, it's safer.
Is it possible to create indexes for this specific situation?
Example of two documents representing their respective users:
{ // Document of user 1
"_id" : "1", //user 1 and its codes
"codes" : [
{
"code" : "1111",
"description" : "code 1",
},
{
"code" : "2222",
"description" : "code 2",
},
{
"code" : "3333",
"description" : "code 3",
}
]
},
{ // Document of user 2
"_id" : "2", //user 2 and its codes
"codes" : [
{
"code" : "1111",
"description" : "code 1",
},
{
"code" : "4444",
"description" : "code 2",
},
{
"code" : "2222",
"description" : "code 3",
}
]
}
Thank you!
Use https://docs.mongodb.com/manual/reference/operator/update/addToSet/ to maintain uniqueness of code subdocuments. You will need to ensure that you always specify code fields in the same order (e.g. code, description).
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;
}
});
I'm new to mongodb and javascript, and have been reading the manual, but I can't seem to put the pieces together to solve the following problem.. I was wondering if you can kindly help.
I have two collections "places" and "reviews".
One document in "places" collection is as follows:
{
"_id" : "004571a7-afe4-4124-996e-b6ec779db494",
"name" : "wakawaka place",
"address" : {
"address" : "12 ad avenue",
"city" : "New York",
},
"review" : [
{
"id" : "i32347",
"review_list" : [
"r123456",
"r123457"
],
}
]
}
The "review" array can be empty for some documents.
And in the "reviews" collection, every document in the collection represents a review:
{
"_id" : ObjectId("53c913689c8e91a5a9c4047f"),
"user_id" : "useridhere",
"review_id" : "r123456",
"attraction_id" : "i32347",
"content" : "review content here"
}
What I would like to achieve is, for each place that has reviews, get the content of each review from the "review" collection and store them together in another new collection.
I'd be grateful for any suggestions on how to go about this.
Thanks
Is there a way to make a subdocument within a list have a unique field in mongodb?
document structure:
{
"_id" : "2013-08-13",
"hours" : [
{
"hour" : "23",
"file" : [
{
"date_added" : ISODate("2014-04-03T18:54:36.400Z"),
"name" : "1376434800_file_output_2014-03-10-09-27_44.csv"
},
{
"date_added" : ISODate("2014-04-03T18:54:36.410Z"),
"name" : "1376434800_file_output_2014-03-10-09-27_44.csv"
},
{
"date_added" : ISODate("2014-04-03T18:54:36.402Z"),
"name" : "1376434800_file_output_2014-03-10-09-27_44.csv"
},
{
"date_added" : ISODate("2014-04-03T18:54:36.671Z"),
"name" : "1376434800_file_output_2014-03-10-09-27_44.csv"
}
]
}
]
}
I want to make sure that the document's hours.hour value has a unique item when inserted. The issue is hours is a list. Can you ensureIndex in this way?
Indexes are not the tool for ensuring uniqueness in an embedded array, rather they are used across documents to ensure that certain fields do not repeat there.
As long as you can be certain that the content you are adding does not differ from any other value in any way then you can use the $addToSet operator with update:
db.collection.update(
{ "_id": "2013-08-13", "hour": 23 },
{ "$addToSet": {
"hours.$.file": {
"date_added" : ISODate("2014-04-03T18:54:36.671Z"),
"name" : "1376434800_file_output_2014-03-10-09-27_44.csv"
}
}}
)
So that document would not be added as there is already an element matching those exact values within the target array. If the content was different (and that means any part of the content, then a new item would be added.
For anything else you would need to maintain that manually by loading up the document and inspecting the elements of the array. Say for a different "filename" with exactly the same timestamp.
Problems with your Schema
Now the question is answered I want to point out the problems with your schema design.
Dates as strings are "horrible". You may think you need them but you do not. See the aggregation framework date operators for more on this.
You have nested arrays, which generally should be avoided. The general problems are shown in the documentation for the positional $ operator. That says you only get one match on position, and that is always the "top" level array. So updating beyond adding things as shown above is going to be difficult.
A better schema pattern for you is to simply do this:
{
"date_added" : ISODate("2014-04-03T18:54:36.400Z"),
"name" : "1376434800_file_output_2014-03-10-09-27_44.csv"
},
{
"date_added" : ISODate("2014-04-03T18:54:36.410Z"),
"name" : "1376434800_file_output_2014-03-10-09-27_44.csv"
},
{
"date_added" : ISODate("2014-04-03T18:54:36.402Z"),
"name" : "1376434800_file_output_2014-03-10-09-27_44.csv"
},
{
"date_added" : ISODate("2014-04-03T18:54:36.671Z"),
"name" : "1376434800_file_output_2014-03-10-09-27_44.csv"
}
If that is in it's own collection then you can always actually use indexes to ensure uniqueness. The aggregation framework can break down the date parts and hours where needed.
Where you must have that as part of another document then try at least to avoid the nested arrays. This would be acceptable but not as flexible as separating the entries:
{
"_id" : "2013-08-13",
"hours" : {
"23": [
{
"date_added" : ISODate("2014-04-03T18:54:36.400Z"),
"name" : "1376434800_file_output_2014-03-10-09-27_44.csv"
},
{
"date_added" : ISODate("2014-04-03T18:54:36.410Z"),
"name" : "1376434800_file_output_2014-03-10-09-27_44.csv"
},
{
"date_added" : ISODate("2014-04-03T18:54:36.402Z"),
"name" : "1376434800_file_output_2014-03-10-09-27_44.csv"
},
{
"date_added" : ISODate("2014-04-03T18:54:36.671Z"),
"name" : "1376434800_file_output_2014-03-10-09-27_44.csv"
}
]
}
}
It depends on your intended usage, the last would not allow you to do any type of aggregation comparison across hours within a day. Not in any simple way. The former does this easily and you can still break down selections by day and hour with ease.
Then again, if you are only ever appending information then your existing schema should be find. But be aware of the possible issues and alternatives.
I have a DB structure like below:
{
"_id" : 1,
"comments" : [
{
"_id" : 2,
"content" : "xxx"
}
]
}
I update a new subdocument in the comments feild. It is OK.
db.test.update(
{"_id" : 1, "comments._id" : 2},
{$push : {"comments.$.comments" : {_id : 3, content:"xxx"}}}
)
after that the DB structure:
{
"_id" : 1,
"comments" : [
{
"_id" : 2,
"comments" : [
{
"id" : 3,
"content" : "xxx"
}
],
"content" : "xxx"
}
]
}
But when I update a new subdocument in the comment field that _id is 3, There is a error:
db.test.update(
{"_id" : 1, "comments.comments.id" : 3},
{$push : {"comments.comments.$.comments" : {id : 4, content:"xxx"}}}
)
error message:
can't append to array using string field name: comments
Well, it makes total sense if you think about it. MongoDb has the advantage and the disadvantage of solving magically certain things.
When you query the database for a specific regular field like this:
{ field : "value" }
The query {field:"value"} makes total sense, it wouldn't in case value is part of an array but Mongo solves it for you, so in case the structure is:
{ field : ["value", "anothervalue"] }
Mongo iterates through all of them and matches "value" into the field and you don't have to think about it. It works perfectly.. at only one level, because it's impossible to guess what you want to do if you have multiple levels
In your case the first query works because it's the case in this example:
db.test.update(
{"_id" : 1, "comments._id" : 2},
{$push : {"comments.$.comments" : {_id : 3, content:"xxx"}}}
)
Matches _id in the first level, and comments._id at the second level, it gets an array as a result but Mongo is able to solve it.
But in the second case, think what you need, let's isolate the where clause:
{"_id" : 1, "comments.comments.id" : 3},
"Give me from the main collection records with _id:1" (one doc)
"And comments which comments inside have and id=3" (array * array)
The first level is solved easily, comments.id, the second is not possible due comments returns an array, but one more level is an array of arrays and Mongo gets an array of arrays as a result and it's not possible to push a document into all the records of the array.
The solution is to narrow your where clause to obtain an unique document in comments (could be the first one) but it's not a good solution because you never know what is the position of the document you're looking for, using the shell I think the only option to be accurate is to do it in two steps. Check this query that works (not the solution anyway) but "solves" the multiple array part fixing it to the first record:
db.test.update(
{"_id" : 1, "comments.0.comments._id" : 3},
{$push : {"comments.0.comments.$.comments" : {id : 4, content:"xxx"}}}
)