Meteor upsert - using addToSet and each - mongodb

I have a Groups collection that contains an eventId and an array of guest ids. I am trying to use upsert to check if an eventId and current guest id already exist, and if so update the collection with the new guest id pushed to the guests array, otherwise insert the new collection with both the current guest and new guest. Here's my upsert code:
var groupId = Groups.upsert({
$and:[
{eventId:groupAttributes.eventId}, // find all the Groups associated w/ this event
{ "guests": {$in: [guest]}}, // and include this guest
]
},{$set: group,
$addToSet: {guests: {$each: [ guest, groupAttributes.guest ]} }
});
What actually happens is a new Group is created everytime, and it's because the $each modifier isn't allowing $addToSet to add multiple values to an array as described here http://docs.mongodb.org/manual/reference/operator/update/addToSet/#up._S_addToSet
Instead what happens is the guests array is added with an "$each" array of size 2.
[{"$each":["yQZfEXfs7J9E4Nbqf","s2rk5KAxq4dYtDFNG"]}]
Two questions. 1) Is there a better way to do what I am trying to do, and if not 2) What am I doing incorrectly? Thanks.
Edit: Trying the following code in meteor mongo
db.groups.update({
$and:[
{eventId:"wGMhaP7t4nsiTNHt5"},
{ "guests": {$in: ["yQZfEXfs7J9E4Nbqf"]}},
]
},{$set: { userId: 'gBuR448nsJMcpwjsT',
author: 'Martin W',
weddingId: 'rz9xjtDm3bFAeiCxM',
eventId: 'wGMhaP7t4nsiTNHt5' },
$addToSet: {guests: {$each: [ "yQZfEXfs7J9E4Nbqf","s2rk5KAxq4dYtDFNG" ]} }
}, {upsert: true})
The result is the following
{ "_id" : ObjectId("528fa31a098141f336688d96"), "author" : "Martin W", "eventId"
: "wGMhaP7t4nsiTNHt5", "guests" : [ "yQZfEXfs7J9E4Nbqf", "s2rk5KAxq4dYtDFNG"
], "userId" : "gBuR448nsJMcpwjsT", "weddingId" : "rz9xjtDm3bFAeiCxM" }
Now I try the same code in Meteor
var groupId = Groups.update({
$and:[
{eventId:"wGMhaP7t4nsiTNHt5"},
{ "guests": {$in: ["yQZfEXfs7J9E4Nbqf"]}},
]
},{$set: { userId: 'gBuR448nsJMcpwjsT',
author: 'Martin W',
weddingId: 'rz9xjtDm3bFAeiCxM',
eventId: 'wGMhaP7t4nsiTNHt5' },
$addToSet: {guests: {$each: [ "yQZfEXfs7J9E4Nbqf","s2rk5KAxq4dYtDFNG" ]} }
}, {upsert: true})
And the result is different (and undesirable)
{ "_id" : "4XgaF6pBGEohQR6pa", "userId" : "gBuR448nsJMcpwjsT", "author" : "Marti
n W", "weddingId" : "rz9xjtDm3bFAeiCxM", "eventId" : "wGMhaP7t4nsiTNHt5", "guest
s" : [ { "$each" : [ "yQZfEXfs7J9E4Nbqf", "s2rk5KAxq4dYtDFNG" ] } ] }
I am a mongo newb so am not sure what to make of it, but I notice how the _id is an ObjectId in the first result and not in the second. And the guests array is the way I would expect using meteor mongo, but using $each as a value in the second result set. Any ideas?

Related

how to update model from a list of documents to an array in mongodb

We have a MongoDB instance with a collection of users that is something like this:
{
"Username": "Amin-AMD",
"FriendsList": [
{
"UserId": "5e076f4b19e8cd000162c962",
"NickName": "Amin-Mobile",
"ClanName": null,
"ClanId": null,
"Level": NumberInt(1),
"ActiveCosmeticItems": [
"hair0",
"skin0",
"eye0",
"mouth0",
"daub0",
"acc"
],
"IsOnline": false
},
{
"UserId": "5e08a4a119e8cd000167929e",
"NickName": "saeed",
"ClanName": null,
"ClanId": null,
"Level": NumberInt(7),
"ActiveCosmeticItems": [
"hair5",
"skin2",
"eye2",
"mouth10",
"daub0",
"acc0"
],
"IsOnline": false
}
]
}
As shown above, I have embedded a list of Friends in our User's collection. but for a reason, we need to change this model to reference Friends. So I need to write a query to replace the whole FriendModel with just a UserId.
I have reached to this query but it throws an exception.
db.Users.updateMany({ "FriendsList" : { $ne : [] }}, { $set : { "FriendsList.$" : "FriendsList.$.UserId" }})
In fact, for each friend, I just need the UserId so the new FriendsList will be an array and it should be something like this:
{
"Username": "Amin-AMD",
"FriendsList": [
"5e076f4b19e8cd000162c962",
"5e08a4a119e8cd000167929e"
]
}
MongoDB version: 4.2.1
You have to do it in two steps :
Update existing data in DB :
As you can use aggregation pipeline in updates starting MongoDB version 4.2.
Query to update data :
db.collection.updateMany({ "FriendsList" : { $ne : [] }},[{$set :{FriendsList: '$FriendsList.UserId'}}])
But if your FriendsList.UserId are strings better convert them to ObjectId() as like below :
db.colleciton.updateMany({ "FriendsList" : { $ne : [] }},[{$set :{FriendsList: { $map: { input: '$FriendsList.UserId', in: {$toObjectId: '$$this'}} }}}])
Update existing mongoose model to restrict future writes on DB :
Mongoose Model :
So FriendsList will be an array of ObjectId()'s which will be referred to another new schema via ref field.
FriendsList:[{
type: mongoose.Schema.Types.ObjectId,
ref: "FriendList" /** 'FriendList' will be a mongoose schema refers to a collection */
}]
Ref : mongoose-populate
Try using User model
FriendsList:{
type: mongoose.Schema.Types.ObjectId,
ref: "FriendList"
}

PyMongo(3.6.1) - db.collection.aggregate(pipeline) NOT WORKING

I'm having trouble with PyMongo, I have been googling for hours, but found no solution.
I'm working with some Python Scripts just to practice with MongoDb, which is running on my local machine. I have populated my mongoDb instance with one database "moviesDB", which contains 3 different collections:
1.Movies collection, here is an example of a document from this coll:
{'_id': 1,
'title': 'Toy Story (1995)',
'genres': ['Adventure', 'Animation', 'Children', 'Comedy', 'Fantasy'],
'averageRating': 3.87,
'numberOfRatings': 247,
'tags': [
{'_id': ObjectId('5b04970b87977f1fec0fb6e9'),
'userId': 501,
'tag': 'Pixar',
'timestamp': 1292956344}
]
}
2.Ratings collection, which looks like this:
{ '_id':ObjectId('5b04970c87977f1fec0fb923'),
'userId': 1,
'movieId': 31,
'rating': 2.5,
'timestamp': 1260759144}
3.Tags collection, that I won't use here, so it's not important.
Now, what I'm trying to do is: given a user (in this example, user 1), find all the genres of movies that he rated and per each genre list all the movieIds regarding that genre.
Here's my code:
"""
This query basically retrieves movieIds,
so from the result list of several documents like this:
{
ObjectId('5b04970c87977f1fec0fb923'),
'userId': 1,
'movieId': 31,
'rating': 2.5,
'timestamp': 1260759144},
retrieves only an array of integers, where each number represent a movie
that the user 1 rated."""
movies_rated_by_user = list(db.ratings.distinct(movieId, {userId: 1}))
pipeline = [
{"$match": {"_id ": {"$in": movies_rated_by_user}}},
{"$unwind": "$genres"},
{"$group": {"_id": "$genres", "movies": {"$addToSet": "$_id"}}}]
try:
"""HERE IS THE PROBLEM, SINCE db.movies.aggregate() RETURNS NOTHING!
so the cursor is empty."""
cursor = db.movies.aggregate(pipeline, cursor={})
except OperationFailure:
print("Something went Wrong", file=open("operations_log.txt", "a"))
print(OperationFailure.details, file=open("operations_log.txt", "a"))
sys.exit(1)
aggregate_genre = []
for c in cursor:
aggregate_genre.append(c)
print(aggregate_genre)
The point is that the aggregate function on the movies collection retrieves NOTHING, whereas it really should, since I tried this query on the MongoShell and it worked just fine. Here's how the mongoDB shell-query looks like:
db.movies.aggregate(
[
{$match:{_id : {$in: ids}}},
{$unwind : "$genres"},
{$group :
{
_id : "$genres",
movies: { $addToSet : "$_id" }}}
]
);
Where the 'ids' variables is defined like this, just like the movies_rated_by_user variable in the code:
ids= db.ratings.distinct("movieId", {userId : 1});
The result from the aggregate method looks like this (this is what the aggregate_genre variable in the code, should contain):
{ "_id" : "Western", "movies" : [ 3671 ] }
{ "_id" : "Crime", "movies" : [ 1953, 1405 ] }
{ "_id" : "Fantasy", "movies" : [ 2968, 2294, 2193, 1339 ] }
{ "_id" : "Comedy", "movies" : [ 3671, 2294, 2968, 2150, 1405 ] }
{ "_id" : "Sci-Fi", "movies" : [ 2455, 2968, 1129, 1371, 2105 ] }
{ "_id" : "Adventure", "movies" : [ 2193, 2150, 1405, 1287, 2105, 2294,
2968, 1371, 1129 ] }
Now the problem is the aggregate method, is there any error with the pipeline string??
PLEASE HELP!!
Thank you
I think you need to cast the cursor to a list before iterating through it. Hope this helps.
cursor = list(db.movies.aggregate(pipeline, cursor={}))

Can I use something like findAndModify on a deep nested document with MongoDB?

I have the Collection contexts with documents like the following in my MongoDB (a context has some users with roles)
{
"__v" : 0,
"_id" : ObjectId("53dea71a713d636e1fea705a"),
"name" : "team4",
"users" : [
{
"userId" : "53a347467f311d7c1731f91b",
"roles" : [
"admin"
]
},
{
"userId" : "536d2e8be0f9697e13b95c87",
"roles" : []
}
]
}
Is there a way to add (or remove) a role to a user with findAndModify.
Yes, it's possible. You can do it using both update and findAndModify commands.
To add role to a user call:
db.contexts.update({
_id: ObjectId('53dea71a713d636e1fea705a'),
'users.userId': '536d2e8be0f9697e13b95c87'
}, {
$push: {
'users.$.roles': 'admin'
}
})
To remove role from a user call:
db.contexts.update({
_id: ObjectId('53dea71a713d636e1fea705a'),
'users.userId': '53a347467f311d7c1731f91b'
}, {
$pull: {
'users.$.roles': 'admin'
}
})
Note that I'm using positional operator $ to specify an exact user subdocument to
update.
Here is an example of using findAndModify for the same task:
db.contexts.findAndModify({
query: {
_id: ObjectId('53dea71a713d636e1fea705a'),
'users.userId': '536d2e8be0f9697e13b95c87'
},
update: {
$push: {
'users.$.roles': 'admin'
}
},
new: true
})
The only real difference between update and findAndModify commands is that findAndModify returns you a modified document, while update only returns operation status.
findAndModify operation is an equivalent of calling both find and update operations, except that findAndModify performs both operations in a single transaction. For more information see this question.

Mongodb: Trying to find all documents with specific subdocument field, why is my query not working?

Here is an example of a document from the collection I am querying
meteor:PRIMARY> db.research.findOne({_id: 'Z2zzA7dx6unkzKiSn'})
{
"_id" : "Z2zzA7dx6unkzKiSn",
"_userId" : "NtE3ANq2b2PbWSEqu",
"collaborators" : [
{
"userId" : "aTPzFad8DdFXxRrX4"
}
],
"name" : "new one",
"pending" : {
"collaborators" : [ ]
}
}
I want to find all documents within this collection with either _userId: 'aTPzFad8DdFXxRrX4' or from the collaborators array, userId: 'aTPzFad8DdFXxRrX4'
So I want to look though the collection and check if the _userId field is 'aTPzFad8DdFXxRrX4'. If not then check the collaborators array on the document and check if there is an object with userId: 'aTPzFad8DdFXxRrX4'.
Here is the query I am trying to use:
db.research.find({$or: [{_userId: 'aTPzFad8DdFXxRrX4'}, {collaborators: {$in: [{userId: 'aTPzFad8DdFXxRrX4'}]}}] })
It does not find the document and gives me a syntax error. What is my issue here? Thanks
The $in operator is basically a simplified version of $or but you really only have one argument here so you should not even need it. Use dot notation instead:
db.research.find({
'$or': [
{ '_userId': 'aTPzFad8DdFXxRrX4'},
{ 'collaborators.userId': 'aTPzFad8DdFXxRrX4'}
]
})
If you need more than one value then use $in:
db.research.find({
'$or': [
{ '_userId': 'aTPzFad8DdFXxRrX4'},
{ 'collaborators.userId': {
'$in': ['aTPzFad8DdFXxRrX4','aTPzFad8DdFXxRrX5']
}}
]
})

How to remove duplicate entries from an array?

In the following example, "Algorithms in C++" is present twice.
The $unset modifier can remove a particular field but how to remove an entry from a field?
{
"_id" : ObjectId("4f6cd3c47156522f4f45b26f"),
"favorites" : {
"books" : [
"Algorithms in C++",
"The Art of Computer Programming",
"Graph Theory",
"Algorithms in C++"
]
},
"name" : "robert"
}
As of MongoDB 2.2 you can use the aggregation framework with an $unwind, $group and $project stage to achieve this:
db.users.aggregate([{$unwind: '$favorites.books'},
{$group: {_id: '$_id',
books: {$addToSet: '$favorites.books'},
name: {$first: '$name'}}},
{$project: {'favorites.books': '$books', name: '$name'}}
])
Note the need for the $project to rename the favorites field, since $group aggregate fields cannot be nested.
The easiest solution is to use setUnion (Mongo 2.6+):
db.users.aggregate([
{'$addFields': {'favorites.books': {'$setUnion': ['$favorites.books', []]}}}
])
Another (more lengthy) version that is based on the idea from #kynan's answer, but preserves all the other fields without explicitly specifying them (Mongo 3.4+):
> db.users.aggregate([
{'$unwind': {
'path': '$favorites.books',
// output the document even if its list of books is empty
'preserveNullAndEmptyArrays': true
}},
{'$group': {
'_id': '$_id',
'books': {'$addToSet': '$favorites.books'},
// arbitrary name that doesn't exist on any document
'_other_fields': {'$first': '$$ROOT'},
}},
{
// the field, in the resulting document, has the value from the last document merged for the field. (c) docs
// so the new deduped array value will be used
'$replaceRoot': {'newRoot': {'$mergeObjects': ['$_other_fields', "$$ROOT"]}}
},
// this stage wouldn't be necessary if the field wasn't nested
{'$addFields': {'favorites.books': '$books'}},
{'$project': {'_other_fields': 0, 'books': 0}}
])
{ "_id" : ObjectId("4f6cd3c47156522f4f45b26f"), "name" : "robert", "favorites" :
{ "books" : [ "The Art of Computer Programmning", "Graph Theory", "Algorithms in C++" ] } }
What you have to do is use map reduce to detect and count duplicate tags .. then use $set to replace the entire books based on { "_id" : ObjectId("4f6cd3c47156522f4f45b26f"),
This has been discussed sevel times here .. please seee
Removing duplicate records using MapReduce
Fast way to find duplicates on indexed column in mongodb
http://csanz.posterous.com/look-for-duplicates-using-mongodb-mapreduce
http://www.mongodb.org/display/DOCS/MapReduce
How to remove duplicate record in MongoDB by MapReduce?
function unique(arr) {
var hash = {}, result = [];
for (var i = 0, l = arr.length; i < l; ++i) {
if (!hash.hasOwnProperty(arr[i])) {
hash[arr[i]] = true;
result.push(arr[i]);
}
}
return result;
}
db.collection.find({}).forEach(function (doc) {
db.collection.update({ _id: doc._id }, { $set: { "favorites.books": unique(doc.favorites.books) } });
})
Starting in Mongo 4.4, the $function aggregation operator allows applying a custom javascript function to implement behaviour not supported by the MongoDB Query Language.
For instance, in order to remove duplicates from an array:
// {
// "favorites" : { "books" : [
// "Algorithms in C++",
// "The Art of Computer Programming",
// "Graph Theory",
// "Algorithms in C++"
// ]},
// "name" : "robert"
// }
db.collection.aggregate(
{ $set:
{ "favorites.books":
{ $function: {
body: function(books) { return books.filter((v, i, a) => a.indexOf(v) === i) },
args: ["$favorites.books"],
lang: "js"
}}
}
}
)
// {
// "favorites" : { "books" : [
// "Algorithms in C++",
// "The Art of Computer Programming",
// "Graph Theory"
// ]},
// "name" : "robert"
// }
This has the advantages of:
keeping the original order of the array (if that's not a requirement, then prefer #Dennis Golomazov's $setUnion answer)
being more efficient than a combination of expensive $unwind and $group stages.
$function takes 3 parameters:
body, which is the function to apply, whose parameter is the array to modify.
args, which contains the fields from the record that the body function takes as parameter. In our case "$favorites.books".
lang, which is the language in which the body function is written. Only js is currently available.