Change an existing object in an array but still preserve key uniqueness - mongodb

I have a document:
{ 'profile_set' :
[
{ 'name' : 'nick', 'options' : 0 },
{ 'name' : 'joe', 'options' : 2 },
{ 'name' : 'burt', 'options' : 1 }
]
}
If I want to add new object to the profile_set only if the name of the object isn't already taken, regardless of the options, I can qualify my update with a query object that prevents the update if the name is already present in profile_set. In the shell:
db.coll.update(
{_id: id, 'profile_set.name': {$ne: 'nick'}},
{$push: {profile_set: {'name': 'nick', 'options': 2}}})
So this will only perform the $push for a doc with a matching _id and where there isn't a profile_set element where name is 'nick'.
Question But if I later need to change Nick's name (and maybe his options too...), that is change an existing array object, not add a new one. Is there a way to do that in one atomic update operation that still honor the unique constraint of name?

There are two conditions, I think:
var newName = "somename";
var oldName = "nick";
var newOption = 3;
// if not change the name
db.coll.update({
_id : id,
'profile_set.name' : oldName
}, {
$set : {
"profile_set.$.options" : newOption
}
});
// if change the name
db.coll.update({
_id : id,
$and : [ {
'profile_set.name' : {
$ne : newName
}
}, {
'profile_set.name' : oldName
} ]
}, {
$set : {
"profile_set.$.name" : newName,
"profile_set.$.options" : newOption
}
});

Related

Mongoose remove an element from array

This is my spa schema.
const spaSchema = new Schema({
name: String,
contactNumbers: String,
address: String,
images: [String],
});
I want to delete the element in the images array where the element name is 'abc.jpg' and documant id is x. How do I achive this?
I not really sure about your question, so I will cover 2 options. On both, I will assume that you are making an update on the spa collection.
1) Removing the images field from the document
To totally remove the field images from the desired document you can use the MongoDB $unset operator, as:
Original document:
{
_id : ObjectId('some_id'),
name : 'abc.jpg',
contactNumbers: '...',
address: '...',
images: ['...']
}
Update method:
Spa.update(
{ name : 'abc.jpg', _id : ObjectId('some_id') },
{ $unset : { images : null } }
);
That will result in the :
{
_id : ObjectId('some_id'),
name : 'abc.jpg',
contactNumbers: '...',
address: '...'
}
2) Removing one element within the images field
If you are trying to remove just one element with a specific value from the images array, you can use the MongoDB $pull operator, like:
Original document:
{
_id : ObjectId('some_id'),
name : '...',
contactNumbers: '...',
address: '...',
images: ['123.jpg','abc.jpg','def.jpg']
}
Update method:
Spa.update(
{ _id : ObjectId('some_id') },
{ $pull : { images : 'abc.jpg' } }
);
That will result in the :
{
_id : ObjectId('some_id'),
name : '...',
contactNumbers: '...',
address: '...',
images: ['123.jpg','def.jpg']
}
I hope that is what you looking for. =]
To remove one element from images(where you store image's name) you should use $pull as bellow
Spa.update(
{ _id : mongoose.Types.ObjectId('some_id') },
{ $pull : { images : 'IMAGE_NAME_THAT_YOU_WANT_TO_REMOVE_FROM_ARRAY' } }
);

How to group in mongodb withtout including _id

so I want to result as :
{
"id" : 888789999,
"name" : "Malaysian with Attendance Allowance",
}
but I tried
{$group : {
'id' : '$profiles.id',
'name' : {$first:'$profiles.name'},
}}
an I get an error :
"errmsg" : "The field 'id' must be an accumulator object",
you can try this, to group by profile id and get first name, add project if you need id without underscore.
In group, except _id other fields should have accumulation or aggregation
{$group : {
'_id' : '$profiles.id',
'name' : {$first:'$profiles.name'},
}}
If you don't want to include _id then
{$group : {
_id: null,
'id' : {$first:'$profiles.id'},// any accumulation which you need
'name' : {$first:'$profiles.name'},
}}
db.collectionName.aggregate(
[
{
$group : {
id : { profiles: { $profiles: "$id" }},
name : {$first:{profiles:{$profiles:"name"}}}
}
}
]
)

How to query MongoDb documents using the indices of embedded arrays

I am trying to learn how to use mongo queries to reach deep into a data tree. Specifically, I'm trying to remove the object below {"object": 'to remove'}
{
"_id" : ObjectId("7840f22736341b09154f7ebf"),
"username" : "nmay",
"fname" : "Nate",
"lname" : "May",
"data" : [
{
"monthNum" : 1,
"year" : 2016,
"days" : [
{
"date" : "2016-01-01T06:00:00.000Z",
"type1" : [],
"type2" : []
},
{
"date" : "2016-01-02T06:00:00.000Z",
"type1" : [
{"object": 'to remove'}
],
"type2" : []
}
]
}
]
}
so far I know how to query for the user _id, but I'm not sure how to remove the desired object using the indices in each array. In this example I want to remove data[0].days[1].type1[0]
Here is the query that I have so far:
app.delete('/user/:id/data/:monthIndex/days/:dayIndex/type1/:type1Index', function (req, res, next) {
var monthIndex = parseInt(req.params.monthIndex); // these console the value properly
var dayIndex = parseInt(req.params.dayIndex); // -1 is applied to the parameter to translate to array position
var type1Index = parseInt(req.params.type1Index);
db.users.update(
{ _id: mongojs.ObjectId(req.params.id) },
{ $pull: data.monthIndex.days.dayIndex.type1.type1Index }
);
}
It gives me the error
ReferenceError: data is not defined
Can someone demonstrate how I can pass this query my index parameters to remove the desired object?
Unfortunately, there is no way to remove an array element by its numerical index with a single operation in MongoDB. In order to do this, you need to unset desired element(s) first, and remove the resulting null-valued fields afterwards.
Your code should look something like this:
db.users.update(
{ _id : mongojs.ObjectId(req.params.id) },
{ $unset : { 'data.0.days.1.type1.0' : 1 } }
);
db.users.update(
{ _id : mongojs.ObjectId(req.params.id) },
{ $pull : { 'data.0.days.1.type1' : null } }
);
Edit by #bob: to pass in the parameters you have to build the query string, which is ugly:
var unset = {};
unset['$unset'] = {};
unset.$unset['data.' + req.params.monthIndex + '.days.' + req.params.dayIndex + '.foods.' + req.params.foodIndex] = 1;
db.users.update( { _id : mongojs.ObjectId(req.params.id) }, unset );
var pull = {};
pull['$pull'] = {};
pull.$pull['data.' + req.params.monthIndex + '.days.' + req.params.dayIndex + '.foods'] = null;
db.users.update( { _id : mongojs.ObjectId(req.params.id) }, pull );

How to project specific fields from a document inside an array?

here is a typical document
{
title : 'someTitle',
places : [{name : 'someName', location : 'someLocation'}, {name ...}]
}
I have the following query
var qs = {title : 'someTitle', places : {$elemMatch : {name : 'someName' } } };
where I select a document which matches the title and which contains a document entry within its 'places' array that has name equal to 'someName'. However the issue is that the entries within the places array are large documents, and I only need a couple of fields from that document. I tried projecting the fields like so but it did not work.
var projection = {'places.$.name': 1, 'places.$.location' : 1};
Which is supposed to return an array with a document containing only the 'name' and 'location' property.
I got the following error
Can't canonicalize query: BadValue Cannot specify more than one positional proj. per query.
to be clear, I would like to accomplish this without the aggregate framework
You are doing it wrong. According to the documentation
Only one positional $ operator may appear in the projection document.
But you still need to use the $ operator to get the expected result:
var qs = { title : 'someTitle', 'places.name' : 'someName' };
var projection = {'places.$': 1 };
db.collection.find(qs, projection);
Which returns:
{
"_id" : ObjectId("564f52d7d9a433df958b5630"),
"places" : [
{
"name" : "someName",
"location" : "someLocation"
}
]
}
Also you don't need the $elemMatch operator here use "dot notation" instead.
Now if what you want is an array of "name" and "location" for each subdocument in the array then aggregation is the way to go.
db.collection.aggregate([
{ '$match': {
'title' : 'someTitle',
'places.name' : 'someName'
}},
{ '$project': {
'places': {
'$map': {
'input': '$places',
'as': 'place',
'in': {
'name': '$$place.name',
'location': '$$place.location'
}
}
}
}}
])
Which yields:
{
"_id" : ObjectId("564f52d7d9a433df958b5630"),
"places" : [
{
"name" : "someName",
"location" : "someLocation"
},
{
"name" : "bar",
"location" : "foo"
}
]
}
For the fields inside an array, you can project them the same as in embedded object
var projection = {'places.name': 1, 'places.location' : 1};
Check this guideline
https://docs.mongodb.com/manual/reference/operator/aggregation/project/#include-specific-fields-from-embedded-documents

Find the documents in mongoose whose array fields exactly match

I'm using Mongoose and have a schema like this:
var chat = new mongoose.Schema({
chatId : String,
members : [{
id : String,
name : String
}]
});
Suppose I have two chat document like this
{
chatId : 'Edcjjb',
members : [
{
id : 'a1',
name : 'aaa'
},
{
id : 'b1',
name : 'bbb'
}
]
}
{
chatId : 'Fxcjjb',
members : [
{
id : 'a1',
name : 'aaa'
},
{
id : 'b1',
name : 'bbb'
},
{
id : 'c1',
name : 'ccc'
}
]
}
I want to find all those documents which have only specfied members Id.
For example, if I specify a1 and b1
then only the first document should be retrieved as the second document contains id c1 as well.
And if I specifiy a1,b1,c1
then only second document should be specified.
Please tell me how to do this in mongoose
You can specify a clause on the array size, like
{ members : { $size : 2 } } in your first example and
{ members : { $size : 3 } } in the second one.
Can that work for you?
EDIT: I should also mention that the other part of the query should be
{ "members.id": { $all: [ "a1" , "b1" ] } }
and, for the second example,
{ "members.id": { $all: [ "a1" , "b1", "c1" ] } }