Removing a single Item from a mongoDB array using $pull - arrays

I am trying to do what the title of the question says. Removing a single item from an array of objects. I am trying to remove a specific object using the _id (ObjectID). I have written this query as it can be seen on the code below : $pull: { 'ARRAY': {$elemMatch: {_id: idToRemove}}}. What happens next may shock you. ALL items from the array are removed...
Here is the code from the nodeJS app that sends the query:
var findOne = { 'User.username': req.user.User.username };
var query = { $pull: { 'User.Polls': {$elemMatch: {_id: req.body.pollID}}}};
User.findOneAndUpdate(findOne, query, function (err, response) {//EVERYTHING GOT REMOVED});
Here is a photo of the mongod structure

I solved it myself. I was using User.Polls._id which did not point into the array that i had(Polls). After that I got it to work by selecting the whole document using $elemMatch creating this bug. By removing $elemMatch my problem got solved. That's the final query $pull: { 'User.Polls': {_id: req.body.pollID}}

Related

MongoDB find in array in all documents

I have a collection of documents with this simplified structure:
{
cards: [
{
id: "some string",
....
},{...}{...}{...}
]
}
I need to query the whole collection of documents and find those documents where my defined ID of a card matches one of the objects in the array of cards.
I usually fetch all documents and do it locally but this is quite large collection and I have no clue how to achieve this if even possible.
Thank you for your time!
ps: I'm using nodejs mongoose
After playing around with mohammad Naimi's solution I figured the correct syntax:
db.collection.find({ cards:{ $elemMatch: { id: { $eq:"some string" }}}})
db.collection.find({cards:{$elemMatch:{id:{$eq:"some string"}}})

Mongodb query to find element value type of nested array or object in a field

I have mongodb data model where I have some array fields that contain embedded objects or arrays. I have some inconsistencies in the field in question because I've tweaked my application logic. Initially, my model looked like this:
Initial Setup of Results collection
"competition" : "competition1",
"stats" : [
{
"stat1" : [],
"stat2" : []
}
]
However, I saw that this wasn't the best setup for my needs. So I changed it to the following:
New Setup of Results collection
"competition" : "competition1",
"stats" : [
{
"stat1" : 3,
"stat2" : 2
}
]
My problem now is that documents that have the initial setup cause an error. So what I want is to find all documents that have the initial setup and convert them to have the new setup.
How can I accomplish this in mongodb?
Here is what I've tried, but I'm stuck...
db.getCollection('results').find({"stats.0": { "$exists": true }})
But what I want is to be able to do something like
db.getCollection('results').find({"stats.0".stat1: { "$type": Array}})
Basically I want to get documents where the value of stats[0].stat1 is of type array and override the entire stats field to be an empty array.
This would fix the errors I'm getting.
$type operator for arrays in older versions works little differently than what you might think than $type in 3.6.
This will work in 3.6
db.getCollection('results').find( { "stats.0.stat1" : { $type: "array" } } )
You can do it couple of ways for lower versions and It depends what you are looking for.
For empty arrays you can just check
{"stats.0.stat1":{$size:0}}
For non empty arrays
{"stats.0.stat1": {$elemMatch:{ "$exists": true }}}
Combine both using $or for finding both empty and non empty array.
For your use case you can use below update
db.getCollection('results').update({"stats.0.stat1":{$size:0}}, {$set:{"stats":[]}})

How can I perform a find in Mongo (meteor) that finds a document where an array matches? [duplicate]

This question already has answers here:
MongoDB Find Exact Array Match but order doesn't matter
(7 answers)
Closed 7 years ago.
I'm trying to return a Cursor in Meteor (using MongoDB).
I'm looking to find documents (MessageThreads collection) where a field of participants exactly matches an array I pass in.
Below is a sample MessageThread document
{
"_id": "YYSELCguhLurTeyNY",
"creator": "RLmnidY6GypdqDXZu",
"participants": [
"SNhRq4vQpwFBjnTSH",
"RLmnidY6GypdqDXZu"
],
}
When I perform an addMessage method, I'm trying to check first if a thread exists where participants exactly matches the array I pass in. This array will be formulated from the new message form tofield.
So, the documents should only be returned if all my array of participants are inside the documents participants field but no other. Eg: If a third person existed in the document who wasn't part of the new message to field then that document should not be returned.
Currently this is my query, which is obviously too simplistic.
existingThread = MessageThreads.findOne(participants: participants)
Any pointers? Thank you
EDIT: I'm having an issue using the provided duplicate answer (but not yet allowed to comment on that other thread)
For some reason existingThread is still finding a document if the array is different but the size is true.
EDIT 2:
Below is the code for my entire method in the event that it can help decipher where I am going wrong. In coffeescript (please excuse the tabbing, can't get it working in SO, sorry).
Meteor.methods
newMessage: (recipientIds, messageContent) ->
if !Meteor.userId()
return false
userId = Meteor.userId()
check recipientIds, [String]
check messageContent, String
participants = recipientIds
participants.push(userId)
participantCount = _.size(participants)
existingThread = MessageThreads.findOne participants:
$size: participantCount
$in: participants
if existingThread?
console.log "Thread exists: #{existingThread._id}"
MessageThreads.update existingThread,
$addToSet: messages: {sender: userId, content: messageContent}
$set: lastUpdated: new Date()
else
newThreadId = MessageThreads.insert
creator: userId
participants: participants
messages: [
{
sender: userId
content: messageContent
createdAt: new Date()
}
]
lastUpdated: new Date()
return newThreadId
You need the $all operator in your query which selects the documents where the value of a field is an array that contains all the specified elements. As you want to return a cursor, find() method is more appropriate since it returns a cursor. It does not immediately access the database or return documents. To access the documents in the cursor, cursors provide fetch() to return all matching documents, map() and forEach() to iterate over all matching documents, observe() and observeChanges() to register callbacks when the set of matching documents changes.
For your case, an example implementation would look something like this:
var existingThreadsCursor = MessageThreads.find({ "participants": { "$all": participants} });
var count = 0;
existingThreadsCursor.forEach(function (thread){
console.log("Thread with participants: " + JSON.stringify(thread.participants));
count++;
});

Find a Mongodb document if it contains an element in the array in mongoose

If I have a field in a mongodb document which has an array, for example:
"tags" : [ "tag", "etc1", "etc2", "etc3" ]
Is there a way that I can select that document if it contains the element 'etc1'?
If I try using the query:
db.coll.find({"tags" : { $elemMatch: { value0: 'etc1'} }})
but I need to know the position of the element in the array, which I don't know.
I have also tried:
db.coll.find({"tags" : { $elemMatch: 'etc1' }})
but it needs to be an object. Is there any way of doing this?
NB I am using mongoose but I wasn't sure how to construct the query
Use the $in operator as described in this question or this other question and as described in the mongodb docs and can be accessed in mongoose.
With it, you'll have a query that looks something like this...remembering that you must pass an array when using $in:
db.coll.find({"tags" : { $in : ['etc1'] } } );
Apart from using $in operator you can also use $all operator like:
db.col1.find({"tags":{$all :["etc1"]}})

mongodb - retrieve array subset

what seemed a simple task, came to be a challenge for me.
I have the following mongodb structure:
{
(...)
"services": {
"TCP80": {
"data": [{
"status": 1,
"delay": 3.87,
"ts": 1308056460
},{
"status": 1,
"delay": 2.83,
"ts": 1308058080
},{
"status": 1,
"delay": 5.77,
"ts": 1308060720
}]
}
}}
Now, the following query returns whole document:
{ 'services.TCP80.data.ts':{$gt:1308067020} }
I wonder - is it possible for me to receive only those "data" array entries matching $gt criteria (kind of shrinked doc)?
I was considering MapReduce, but could not locate even a single example on how to pass external arguments (timestamp) to Map() function. (This feature was added in 1.1.4 https://jira.mongodb.org/browse/SERVER-401)
Also, there's always an alternative to write storedJs function, but since we speak of large quantities of data, db-locks can't be tolerated here.
Most likely I'll have to redesign the structure to something 1-level deep, like:
{
status:1,delay:3.87,ts:138056460,service:TCP80
},{
status:1,delay:2.83,ts:1308058080,service:TCP80
},{
status:1,delay:5.77,ts:1308060720,service:TCP80
}
but DB will grow dramatically, since "service" is only one of many options which will append each document.
please advice!
thanks in advance
In version 2.1 with the aggregation framework you are now able to do this:
1: db.test.aggregate(
2: {$match : {}},
3: {$unwind: "$services.TCP80.data"},
4: {$match: {"services.TCP80.data.ts": {$gte: 1308060720}}}
5: );
You can use a custom criteria in line 2 to filter the parent documents. If you don't want to filter them, just leave line 2 out.
This is not currently supported. By default you will always receive the whole document/array unless you use field restrictions or the $slice operator. Currently these tools do not allow filtering the array elements based on the search criteria.
You should watch this request for a way to do this: https://jira.mongodb.org/browse/SERVER-828
I'm attempting to do something similar. I tried your suggestion of using the GROUP function, but I couldn't keep the embedded documents separate or was doing something incorrectly.
I needed to pull/get a subset of embedded documents by ID. Here's how I did it using Map/Reduce:
db.parent.mapReduce(
function(parent_id, child_ids){
if(this._id == parent_id)
emit(this._id, {children: this.children, ids: child_ids})
},
function(key, values){
var toReturn = [];
values[0].children.forEach(function(child){
if(values[0].ids.indexOf(product._id.toString()) != -1)
toReturn.push(child);
});
return {children: toReturn};
},
{
mapparams: [
"4d93b112c68c993eae000001", //example parent id
["4d97963ec68c99528d000007", "4debbfd5c68c991bba000014"] //example embedded children ids
]
}
).find()
I've abstracted my collection name to 'parent' and it's embedded documents to 'children'. I pass in two parameters: The parent document ID and an array of the embedded document IDs that I want to retrieve from the parent. Those parameters are passed in as the third parameter to the mapReduce function.
In the map function I find the parent document in the collection (which I'm pretty sure uses the _id index) and emit its id and children to the reduce function.
In the reduce function, I take the passed in document and loop through each of the children, collecting the ones with the desired ID. Looping through all the children is not ideal, but I don't know of another way to find by ID on an embedded document.
I also assume in the reduce function that there is only one document emitted since I'm searching by ID. If you expect more than one parent_id to match, than you will have to loop through the values array in the reduce function.
I hope this helps someone out there, as I googled everywhere with no results. Hopefully we'll see a built in feature soon from MongoDB, but until then I have to use this.
Fadi, as for "keeping embedded documents separate" - group should handle this with no issues
function getServiceData(collection, criteria) {
var res=db[collection].group({
cond: criteria,
initial: {vals:[],globalVar:0},
reduce: function(doc, out) {
if (out.globalVar%2==0)
out.vals.push({doc.whatever.kind.and.depth);
out.globalVar++;
},
finalize: function(out) {
if (vals.length==0)
out.vals='sorry, no data';
return out.vals;
}
});
return res[0];
};

Resources