Mongodb Update into three level embedded document only on field value - node.js

I am trying to update three level Embedded document only one field. i am posting my data below in which I am trying to update only one field into the document. here is my Collection in which i am trying to update View value. {
"_id": "5bbc7614b6160b29f05854c7",
"createdAt": "2018-10-09T09:34:12.604Z"
"subcategories": [
{
"status": "1",
"_id": "5bbc762fb6160b29f05854c8",
"createdAt": "2018-10-09T09:34:39.008Z",
"videos": [
{
"views": 0,
"createdAt": "2018-10-12T11:40:08.752Z",
"_id": "5bc08818ed05cf1c5e01103c",
"user_id": "5ba08df7a68f5f1e43f05983",
"description": "sdf",
"size": "30.76 MB",
"duration": "11:11",
"video": "https://www.gpnext.org"
},
{
"views": 10,
"createdAt": "2018-10-12T11:40:08.752Z",
"_id": "5bc08818ed05cf1c5e01103d",
"user_id": "5ba08df7a68f5f1e43f05984",
"description": "qwerty",
"size": "35.76 MB",
"duration": "10:10",
"video": "https://www.gpnext1.org"
}
]
},
]
}
I am trying to update Views value only but when i am executing bellow mentioned query it is deleting all video data and inserting only views field there. can someone guide me to update Views value without effecting other fields values. here is my query for updating view value.
VideoCategory.findOneAndUpdate(
{'subcategories.videos._id' : mongoose.Types.ObjectId(req.body.video_id)},
{ $set :
{
'subcategories.$.videos': {
'views' : 4
}
}
},
function(err, category){
if (err)
return res.send({ status: false, message: 'error in Increment'});
else
return res.json({ status: true, message: 'View incremented'});
}
);

Positional operator ($) works only for arrays with one-level depth. In your case you need positional filtered operator which is available in MongoDB 3.6 or higher. Try:
db.col.update(
{ _id: "5bbc7614b6160b29f05854c7" },
{ $set: { "subcategories.$[subcategory].videos.$[video].views": 4 }},
{ arrayFilters: [ { "subcategory._id": "5bbc762fb6160b29f05854c8" }, { "video._id": "5bc08818ed05cf1c5e01103c" } ] })

You set json object with only view key, thats why all other data expect "view" are deleted, in this case you need to use positional operator twice, like this
{
$set : {
"subcategories.$.videos.$.views": "4"
}
But MongoDb has limitation on positional operator.
The positional operator can be used only once in a query.
Here is the issue link https://jira.mongodb.org/browse/SERVER-831
I suggest two solution, but highly recommend to use first one
Create another Video Collection, and save video _id(pointer) into your array, than you can easily make any update query you want
Fetch your data, then do some changes, after that update whole data you fetched

Related

Mongoose how to find specific value

After a lot of reading, I am stuck.
the code I posted here, is the implementation of a store database I am trying to make.
in every store, we have some fields. I am interested in doing something with the items array, that contains JSON variables.
I want to filter the items through three filters, firstly by the store ID, secondly by the category ID, and the last filter will be the semi category ID.
I want to send the data from the front end, meaning I supply STOREID, the CategoryID, and the SemiCategoryID.
after receiving the data at the back end side, I am expecting to receive only the relevant items according to the data supplied by the front end.
{
"_id": {
"$oid": "5a1844b5685cb50a38adf5bb" --> **ID of the STORE**
},
"name": "ACE",
"user_id": "59e4c41105d1f6227c1771ea",
"imageURL": "none",
"rating": 5,
"items": [
{
"name": "NirCohen",
"categoryID": "5a0c2d292235680012bd12c9",
"semiCatID": "5a0c2d5a2235680012bd12ca",
"_id": {
"$oid": "5a1958181cd8a208882a80f9"
}
},
{
"name": "he",
"categoryID": "5a0c2d292235680012bd12c9",
"semiCatID": "5a0c2d5a2235680012bd12ca",
"_id": {
"$oid": "5a1973c40e561e08b8aaf2b2"
}
},
{
"name": "a",
"categoryID": "5a0c2d292235680012bd12c9",
"semiCatID": "5a0c2d5a2235680012bd12ca",
"_id": {
"$oid": "5a197439bc1310314c4c583b"
}
},
{
"name": "aaa",
"categoryID": "5a0c2d292235680012bd12c9",
"semiCatID": "5a0c2d5a2235680012bd12ca",
"_id": {
"$oid": "5a197474558a921bb043317b"
}
},
],
"__v": 9
}
and I want the Backend to return the filtered items according to the query.
The problem is, I am not managing to get the CORRECT query.
your help will be much appreciated,
thank you in advance.
If I understand you correctly, you are doing something like this:
Store.find({}).then(..);
If you only want to find the stores where categoryID is equal to the variable myCategory, you could filter them out by using:
Store.find({semiCatID: myCategory}).then(..);
Please let me know if this is not what you are after, then we can keep trying to figure this out together.
EDIT: So you are sending the variables StoreID, CategoryID and SemiCategoryID from the frontend. Receive them in the backend, and want to filter your database collection matching all three fields?
If so.. then I think all you have to do is change your current query:
store.findOne({ _id: req.body.userID }, (err, store) => { console.log(store); });
To something like:
store.findOne({
_id: req.body.userID,
storeID: req.body.StoreID,
categoryID: req.body.CategoryID,
semiCategoryID: req.body.SemiCategoryID
}, (err, store) => { console.log(store); });
This way, the objects you get back from mongo must match all four criterias given from the frontend.
As far as I Understood your question here is my answer to it you can use findById
Store.findById({//store id}).then(..);
or
Store.findOne({_id:ObjectID(storeID)}).then(..);

Searching many parameters in a Cloudant noSQL DB

I am using a Cloudant database on Bluemix to storage products in a Node.js server. These products will be searched by categories. To find a product that has only one category, would not be a problem because a search is made by comparing the string that is sent as a search parameter with the category string that is saved in the database. The problem occurs when a product has two or more categories. At the time of making the comparison of string to string, it would never coincide.
The products can have as many categories as they need.
Any ideas?
if i am understanding your question correctly, you may want to store category as an array of strings and index each element in the array. you can then search products against a single or multiple categories.
for example, given the following documents:
doc 1
{
"name": "product1",
"category: ["abc"]
}
doc 2
{
"name": "product2",
"category: ["abc", "def"]
}
you can set up a search index similar to:
function (doc) {
if (doc.category) {
for (var i in doc.category) {
index("category", doc.category[i], {"store": true, "facet": true})
}
}
}
then you may run queries like such:
.../{dbname}/_design/{designdocname}/_search/{searchindexname}?q=category:abc
which would return both product1 and product2
or:
.../{dbname}/_design/{designdocname}/_search/{searchindexname}?q=category:abc+AND+category:def
which would return only product2.
additional info: https://developer.ibm.com/clouddataservices/cloudant-search/
You should store one or more categories in array format in Cloudant database in "category" parameter
{
"_id": "producto1",
"category: ["TV", "cameras", "computers"]
}
Then you should create a search index
function (doc) {
if (doc.category) {
for (var i in doc.category) {
index("category", doc.category[i], {"store": true, "facet": true})
}
}
}
Now you can query the documents from Cloudant Query
{
"selector": {
"_id": {
"$gt": 0
},
"category": {
"$all": [
"TV",
"cameras"
]
}
},
"fields": [
"_id",
"_rev"
],
"sort": [
{
"_id": "asc"
}
]
}
Or you can use
https://{url_database}/{name_database}/_design/{name_design}/_search/{name_search}?q=category:TV

MongoDB: Query model and check if document contains object or not, then mark / group result

I have a Model called Post, witch contains an property array with user-ids for users that have liked this post.
Now, i need to query the post model, and mark the returned results with likedBySelf true/false for use in by client - is this possible?
I dont have to store the likedBySelf property in the database, just modify the results to have that property.
A temporary solution i found was to do 2 queries, one that finds the posts that is liked by user x, and the ones that have not been liked by user x, and en map (setting likedBySelf true/false) and combine the 2 arrays and return the combined array. But this gives some limitations to other query functions such as limit and skip.
So now my queries looks like this:
var notLikedByQuery = Post.find({likedBy: {$ne: req.body.user._id}})
var likedByQuery = Post.find({likedBy: req.body.user._id})
(I'm using the Mongoose lib)
PS. A typical post can look like this (JSON):
{
"_id": {
"$oid": "55fc463c83b2d2501f563544"
},
"__t": "Post",
"groupId": {
"$oid": "55fc463c83b2d2501f563545"
},
"inactiveAfter": {
"$date": "2015-09-25T17:13:32.426Z"
},
"imageUrl": "https://hootappprodstorage.blob.core.windows.net/devphotos/55fc463b83b2d2501f563543.jpeg",
"createdBy": {
"$oid": "55c49e2d40b3b5b80cbe9a03"
},
"inactive": false,
"recentComments": [],
"likes": 8,
"likedBy": [
{
"$oid": "558b2ce70553f7e807f636c7"
},
{
"$oid": "559e8573ed7c830c0a677c36"
},
{
"$oid": "559e85bced7c830c0a677c43"
},
{
"$oid": "559e854bed7c830c0a677c32"
},
{
"$oid": "559e85abed7c830c0a677c40"
},
{
"$oid": "55911104be2f86e81d0fb573"
},
{
"$oid": "559e858fed7c830c0a677c3b"
},
{
"$oid": "559e8586ed7c830c0a677c3a"
}
],
"location": {
"type": "Point",
"coordinates": [
10.01941398718396,
60.96738099591897
]
},
"updatedAt": {
"$date": "2015-09-22T08:45:41.480Z"
},
"createdAt": {
"$date": "2015-09-18T17:13:32.426Z"
},
"__v": 8
}
#tskippe you can use a method like following to process whether the post is liked by the user himself and call the function anywhere you want.
var processIsLiked = function(postId, userId, doc, next){
var q = Post.find({post_id: postId});
q.lean().exec(function(err,res){
if(err) return utils.handleErr(err, res);
else {
if(_.find(doc.post.likedBy,userId)){ //if LikedBy array contains the user
doc.post.isLiked = true;
} else {
doc.post.isLiked = false;
}
});
next(doc);
}
});
}
Because you are using q.lean() you dont need to actually persist the data. You need to just process it , add isLiked field in the post and send back the response. **note that we are manuplating doc directly. Also you chan tweek it to accept doc containing array of posts and iterating it and attach an isLiked field to each post.
I found that MongoDB's aggregation with $project tequnique was my best bet. So i wrote up an aggregation like this.
Explanation:
Since i want to keep the entire document, but $project purpose is to modify the docs, thus you have to specify the properties you want to keep. A simple way of keeping all the properties is to use "$$ROOT".
So i define a $project, set all my original properties to doc: "$$ROOT", then create a new property "likedBySelf", which is marked true / false if a specified USERID is in the $likedBy set.
I think that this is more clean and simple, than querying every single model after a query to set a likedBySelf flag. It may not be faster, but its cleaner.
Model.aggregate([
{ $project: {
doc: "$$ROOT",
likedBySelf: {
$cond: {
"if": { "$setIsSubset": [
[USERID],
"$likedBy"
]},
"then": true,
"else": false
}
}
}}
]);

Add and remove from array in single query

I want to make a query in which i want to know either the user like or unlike my status, now i want to make it on single query so that I will not call the DB 2 times from my NODEJS server, do any have solution of my problem.
For Add We are using
collection.update({ _id: id },
{ $pull: {
'user_id': 'xxxx-xxxx-xxxx-xxxx' }
}
);
For Remove We are using
collection.update({ _id: id },
{ $push: {
'user_id': 'xxxx-xxxx-xxxx-xxxx' }
}
);
Now I want to use both of them in one query like if apply present in fruit array remove it if not add it.
MongoDB does not allow both a $pull and $push or any other operation to update the same "path" ( therefore single array ) in a single statement. This is mainly to do with the logic handling server side where the update operations are never considered to be ordered in a statement.
Example:
{
"responses": [
{ "user": "Tom", "status": "like" },
{ "user": "Sarah", "status": "unlike" }
]
}
Not that it would make much sense, but you cannot do this:
db.collection.update(
{},
{
"$pull": { "responses": { "user": "Tom", "status": "like" },
"$push": { "responses": { "user": "Tom", "status": "unlike" }
}
)
As the single operation here contains both $push and $pull on the "same path" as "responses". Regardless of you you contruct the statement, neither is required to execute in any order at all.
While we could "match" the position for "Tom" and change his "status" to "unlike" instead, a better model is to do this:
{
"likes": ["Tom"],
"unlikes": ["Sarah"],
"likesTotal": 1,
"unlikesTotal": 1,
"totalScore": 0
}
What this means if I want to change the "vote" for "Tom" then you make a construct like this, with the help of Bulk operations to enable a single request and response:
var bulk = db.collection.initializeOrderedBulkOp();
// Cast "Tom's" unlike where they had a "like" already
bulk.find({
"likes": "Tom",
"unlikes": { "$ne": "Tom" }
}).updateOne({
"$pull": { "likes": "Tom" },
"$push": { "unlikes": "Tom" },
"$inc": {
"likesTotal": -1,
"unlikesTotal": 1
}
]);
// Cast "Tom's" new vote where nothing was there at all
bulk.find({
"unlikes": { "$ne": "Tom" },
"likes": { "$ne": "Tom" }
}).updateOne({
"$push": { "unlikes": "Tom" },
"$inc": {
"unlikesTotal": 1,
"totalScore": -1
}
});
bulk.execute();
This produces a really nice pattern. Not only is each update operation here basically "atomic" in that by acting on separate document properties each modifier is allowed to execute without conflict. But also as a "Bulk" operation, the request for "both" update operations that meet all possible conditions here are sent in a single request and received in a single response.
Of course your "client" logic should also be aware of the current status for who has "liked/disliked" on a particular item, but enforcing this in the general API is good practice.
It keeps arrays in check, and also keeps useful counters in check for general data and general querying purposes, without the need to "calculate" lengths of arrays or matching types.

mongodb update push array

I have the following schema. I am using node.js with mongodb
attributes: {
type: { type: 'string' },
title: { type:'string' },
description: { type:'string' },
is_active: { type:'boolean',defaultsTo:true },
createdBy: { type:'json' },
attachments:{ type:'array' }
}
arr = [{
'id':attResult.id,
'subtype':type,
'title' : attResult.title,
'body' : attResult.body,
'filetype' : attResult.filetype
}];
I am trying to push a attachments into the 'attachments' array that will be unique to the document.
This is the my query.
books.update(
{ id: refid },
{ $push: { attachments: arr } }
).done(function (err, updElem) {
console.log("updElem" + JSON.stringify(updElem));
});
What is the problem in my query,no error but not updated attachments.
I want my result to be this:
{
"_id" : 5,
"attachments": [
{
"id": "xxxxxxx",
"subtype": "book",
"title": "xxxx",
"body": "xxxx" ,
"filetype" : "xxxxx"
},
{
"id": "xxxxxxx",
"subtype": "book",
"title": "xxxx",
"body": "xxxx",
"filetype": "xxxxx"
}
]
}
Someone who trying to push the element into an array is possible now, using the native mongodb library.
Considering the following mongodb collection object
{
"_id" : 5,
"attachments": [
{
"id": "xxxxxxx",
"subtype": "book",
"title": "xxxx",
"body": "xxxx" ,
"filetype" : "xxxxx"
},
{
"id": "xxxxxxx",
"subtype": "book",
"title": "xxxx",
"body": "xxxx",
"filetype": "xxxxx"
}
]
}
arr = [{
'id':'123456',
'subtype':'book',
'title' : 'c programing',
'body' :' complete tutorial for c',
'filetype' : '.pdf'
},
{
'id':'123457',
'subtype':'book',
'title' : 'Java programing',
'body' :' complete tutorial for Java',
'filetype' : '.pdf'
}
];
The following query can be used to push the array element to "attachments" at the end. $push or $addToSet can be used for this.
This will be inserting one object or element into attachments
db.collection('books').updateOne(
{ "_id": refid }, // query matching , refId should be "ObjectId" type
{ $push: { "attachments": arr[0] } } //single object will be pushed to attachemnts
).done(function (err, updElem) {
console.log("updElem" + JSON.stringify(updElem));
});
This will be inserting each object in the array into attachments
db.collection('books').updateOne(
{ "_id": refid }, // query matching , refId should be "ObjectId" type
{ $push: { "attachments":{$each: arr} } } // arr will be array of objects
).done(function (err, updElem) {
console.log("updElem" + JSON.stringify(updElem));
});
Looking at your question a little bit more I'm betting that you are actually using "sails" here even though your question is not tagged as such.
The issue here is that the waterline ODM/ORM has it's own ideas about what sort of operations are actually supported since it tries to be agnostic between working with SQL/NoSQL backends and sort of demands a certain may of doing things.
The result is that updates with $push are not really supported at present and you need more of a JavaScript manipulation affair. So in fact you need to manipulate this via a .findOne and .save() operation:
books.findOne(refid).exec(function(err,book) {
book.attachments.push( arr[0] );
book.save(function(err){
// something here
});
});
Part of that is "waterline" shorthand for what would otherwise be considered an interchangeable use of _id and id as terms, where just specifying the id value as a single argument implies that you are referring to the id value in your query selection.
So unless you replace the waterline ODM/ORM you are pretty much stuck with this AFAIK until there is a decision to maintain this logic in a way that is more consistent with the MongoDB API or otherwise allow access to the "raw" driver interface to perform these .update() operations.
For reference though, and has been alluded to, your general "shell" syntax or what would otherwise be supported in MongoDB specific drivers is like this with the deprecation of the $pushAll operator and the intention being to merge the functionality with the $push and $addToSet operators using the $each modifier:
db.collection.update(
{ "_id": ObjectId(refid) }, // should be important to "cast"
{
"$push": {
"attachments": {
"$each": arr
}
}
}
)
So that syntax would work where it applies, but for you I am thinking that in "sails" it will not.
That gives you some food for thought, and some insight into the correct way to do things.
You are trying to insert an array as an element into your array. You may want to look at $pushAll as a short term solution. This operator is deprecated however see here.
Alternatively you can simply iterate over your array, and each iteration push an element from your array into attachments (this is the recommended approach by Mongo devs).

Resources