I have the following data in nedb.
["UserId":"1446943507761","UserName":"xxx","link":"xxx.html","taskDone":"false","id":14,"_id":"fdaaTWSxloQZdYlT"]
["UserId":"1446943507761","UserName":"xxx","link":"xxx.html","taskDone":"false","id":1,"_id":"fzh2cedAXxT76GwB"]
["UserId":"1446943507761","UserName":"xxx","link":"xxx.html","taskDone":"false","id":0,"_id":"k4loE7XR5gioQk54"]
I am trying to update row with id 0 and set the value of taskDone to true. I use the following query to set the value to true
db.taskmap.update({ _id: "k4loE7XR5gioQk54", UserName:"xxx" }, { $set: { taskDone: "true"} }, function (err, numReplaced) {
console.log("replaced---->" + numReplaced);
});
It updates the value but it updates as a new row. It basically inserts a new row with same values except for the taskdone value as true. It does not delete the existing data. Hence in the final data table after update i get tow rows for id 0 with all values same except for the taskDone. I am not sure if i am doing anything wrong. It will be helpful if anybody can tell me a correct way of updating the value.
You should call db.loadDatabase(); again at the end of db.update(); to see, that no second row with the same _id: appears instead the specific doc gets directly updated.
Edit:
It appears that sometimes when you do db.update() the document that should be updated appears twice instead on the database. It happened to me when I was updating an entry in a list of servers, multiple entries with the modified values were appearing on the database. So to avoid this simply do the following. (I took the same code that was suggested as the answer)
db.update(
{ _id: "k4loE7XR5gioQk54", UserName:"xxx" },
{ $set: { taskDone: "true"} },
{},// this argument was missing
function (err, numReplaced) {
console.log("replaced---->" + numReplaced);
db.loadDatabase();
}
);
Doing this prevents this from happening. I tested it multiple times and the issue disappeared.
update wants four arguments
var Datastore = require('nedb');
var db = new Datastore();
db.insert(
[
{
"UserId":"1446943507761",
"UserName":"xxx",
"link":"xxx.html",
"taskDone":"false",
"id":14,
"_id":"fdaaTWSxloQZdYlT"
},
{
"UserId":"1446943507761",
"UserName":"xxx",
"link":"xxx.html",
"taskDone":"false",
"id":1,
"_id":"fzh2cedAXxT76GwB"
},
{
"UserId":"1446943507761",
"UserName":"xxx",
"link":"xxx.html",
"taskDone":"false",
"id":0,
"_id":"k4loE7XR5gioQk54"
}],
function (err, newDocs) {
// empty here
}
);
db.update(
{ _id: "k4loE7XR5gioQk54", UserName:"xxx" },
{ $set: { taskDone: "true"} },
{},// this argument was missing
function (err, numReplaced) {
console.log("replaced---->" + numReplaced);
}
);
// should give the correct result now
db.find({}).exec(function (err, docs) {console.log(docs);});
Related
lets suppose I have a model with a field called draftFields. It is an object(but it can be an array).
I will create a PUT request to add data into draftFields. My question is: can I add data to draftFields and preserve the previous vale?
Lets say that I have added the first data to draftFields. E.g:
draftFields = {
someRandomValue: 'hi'
}
and after that I'm going to make another PUT request and it should look like this:
draftFields = {
someRandomValue: "hi",
anotherRandomValue: "hey"
}
How can I do that? Everytime I updated my draftFields obj it will remove the previous value. I had to save it in my frontend state to be able to save the previous value. Is there any workaround or method to preserve the values from the backend?
This is my code atm:
app.put('/api/save-draft/:id', function (req, res) {
User.findOneAndUpdate(
{ _id: req.params.id },
{ $set: { draftFields: req.body.draftFields } },
{ new: true },
(err, doc) => {
if (err) {
console.log('Something wrong when updating data!');
res.status(400).send('Error');
}
res.status(200).send('All good!');
console.log(doc);
},
);
});
I'm using Javascript(ReactJS) and NodeJS if this is relevant.
I can use the $push method and change from object to array.
https://docs.mongodb.com/manual/reference/operator/update/push/
I have failed searching, sorry.
Mongo creates a new document if it doesn't already exist (must have the id of 1). And if it already exists, it should increment all values by 1. But the problem here is that it adds another document every time, instead of updating the already existing one.
const query = {
id: 1,
customer: {
worth: 0
}
},
update = { id: 1, $inc:
{
'customer.worth': 1
}
},
options = { upsert: true };
Model.findOneAndUpdate(query, update, options)
.catch(err => console.log(err));
How can I just increment the value of 1 if document already exists, instead of adding another document every time? I only want to have 1 document at any given time.
your query seems ok and inserts during update caused by upsert flag
Optional. If set to true, creates a new document when no document
matches the query criteria. The default value is false, which does not
insert a new document when no match is found.
read documentation
the following query worked for me
db.getCollection('test').findOneAndUpdate({
"id" : 1,
"customer" : {
"worth" : 0
}
}, {
$inc: {
'customer.worth': 1
}
})
I am trying to follow the example of cursor-based paginating with React Apollo (https://www.apollographql.com/docs/react/data/pagination/#cursor-based) but am struggling with how my component that rendered the original data gets the new (appended) data.
This is how we get the original data and pass it to the component:
const { data: { comments, cursor }, loading, fetchMore } = useQuery(
MORE_COMMENTS_QUERY
);
<Comments
entries={comments || []}
onLoadMore={...}
/>
What I'm unsure of is how the fetchMore function works.
onLoadMore={() =>
fetchMore({
query: MORE_COMMENTS_QUERY,
variables: { cursor: cursor },
updateQuery: (previousResult, { fetchMoreResult }) => {
const previousEntry = previousResult.entry;
const newComments = fetchMoreResult.moreComments.comments;
const newCursor = fetchMoreResult.moreComments.cursor;
return {
// By returning `cursor` here, we update the `fetchMore` function
// to the new cursor.
cursor: newCursor,
entry: {
// Put the new comments in the front of the list
comments: [...newComments, ...previousEntry.comments]
},
__typename: previousEntry.__typename
};
}
})
}
From what I understand, yes, once my component will cal this onLoadMore function (using a button's onClick for example), it will fetch the data based on a new cursor.
My question is this. I'm sorry if this is too simple and I'm not understanding something basic.
How does the component get the new data?
I know the data is there, because I console logged the newComments (in my case, it wasn't newComments, but you get the idea.) And I saw the new data! But those new comments, how are they returned to the component that needs the data? And if I click the button again, it is still stuck on the same cursor as before.
What am I missing here?
In the updateQuery function lets you modify (override) the result for the current query. At the same time your component is subscribed to the query and will get the new result. Let's play this through:
Your component is rendered for the first time, component will subscribe to the query and receive the current result of the query from the cache if there is any. If not the query starts fetching from the GraphQL server and your component gets notified about the loading state.
If the query was fetched your component will get the data once the result came in. It now shows the first x results. In the cache an entry for your query field is created. This might look something like this:
{
"Query": {
"cursor": "cursor1",
"entry": { "comments": [{ ... }, { ... }] }
}
}
// normalised
{
"Query": {
"cursor": "cursor1",
"entry": Ref("Entry:1"),
}
"Entry:1": {
comments: [Ref("Comment:1"), Ref("Comment:2")],
},
"Comment:1": { ... },
"Comment:2": { ... }
}
User clicks on load more and your query is fetched again but with the cursor value. The cursor tells the API from which entry it should start returning values. In our example after Comment with id 2.
Query result comes in and you use the updateQuery function to manually update the result of the query in the cache. The idea here is that we want to merge the old result (list) with the new result list. We already fetched 2 comments and now we want to add the two new comments. You have to return a result that is the combined result from two queries. For this we need to update the cursor value (so that we can click "load more" again and also concat the lists of comments. The value is written to the cache and our normalised cache now looks like this:
{
"Query": {
"cursor": "cursor2",
"entry": { "comments": [{ ... }, { ... }, { ... }, { ... }] }
}
}
// normalised
{
"Query": {
"cursor": "cursor2",
"entry": Ref("Entry:1"),
}
"Entry:1": {
comments: [Ref("Comment:1"), Ref("Comment:2"), Ref("Comment:3"), Ref("Comment:4")],
},
"Comment:1": { ... },
"Comment:2": { ... },
"Comment:3": { ... },
"Comment:4": { ... }
}
Since your component is subscribed to the query it will get rerendered with the new query result from the cache! The data is displayed in the UI because we merged the query so that the component gets new data just as if the result had all four comments in the first place.
It depends on how you handle the offset. I'll try to simplify an example for you.
This is a simplified component that I use successfully:
const PlayerStats = () => {
const { data, loading, fetchMore } = useQuery(CUMULATIVE_STATS, {
variables: sortVars,
})
const players = data.GetCumulativeStats
const loadMore = () => {
fetchMore({
variables: { offset: players.length },
updateQuery: (prevResult, { fetchMoreResult }) => {
if (!fetchMoreResult) return prevResult
return {
...prevResult,
GetCumulativeStats: [
...prevResult.GetCumulativeStats,
...fetchMoreResult.GetCumulativeStats,
],
}
},
})
}
My CUMULATIVE_STATS query returns 50 rows by default. I pass the length of that result array to my fetchMore query as offset. So when I execute CUMULATIVE_STATS with fetchMore, the variables of the query are both sortVars and offset.
My resolver in the backend handles the offset so that if it is, for example, 50, it ignores the first 50 results of the query and returns the next 50 from there (ie. rows 51-100).
Then in the updateQuery I have two objects available: prevResult and fetchMoreResult. At this point I just combine them using spread operator. If no new results are returned, I return the previous results.
When I have fetched more once, the results of players.length becomes 100 instead of 50. And that is my new offset and new data will be queried the next time I call fetchMore.
I have a Documents in a Collection that have a field that is an Array (foo). This is an Array of other subdocuments. I want to set the same field (bar) for each subdocument in each document to the same value. This value comes from a checkbox.
So..my client-side code is something like
'click #checkAll'(e, template) {
const target = e.target;
const checked = $(target).prop('checked');
//Call Server Method to update list of Docs
const docIds = getIds();
Meteor.call('updateAllSubDocs', docIds, checked);
}
I tried using https://docs.mongodb.com/manual/reference/operator/update/positional-all/#positional-update-all
And came up with the following for my Server helper method.
'updateAllSubDocs'(ids, checked) {
Items.update({ _id: { $in: ids } }, { $set: { "foo.$[].bar": bar } },
{ multi: true }, function (err, result) {
if (err) {
throw new Meteor.Error('error updating');
}
});
}
But that throws an error 'foo.$[].bar is not allowed by the Schema'. Any ideas?
I'm using SimpleSchema for both the parent and subdocument
Thanks!
Try passing an option to bypass Simple Schema. It might be lacking support for this (somewhat) newer Mongo feature.
bypassCollection2
Example:
Items.update({ _id: { $in: ids } }, { $set: { "foo.$[].bar": bar } },
{ multi: true, bypassCollection2: true }, function (err, result) {
if (err) {
throw new Meteor.Error('error updating');
}
});
Old answer:
Since you say you need to make a unique update for each document it sounds like bulk updating is the way to go in this case. Here's an example of how to do this in Meteor.
if (docsToUpdate.length < 1) return
const bulk = MyCollection.rawCollection().initializeUnorderedBulkOp()
for (const myDoc of docsToUpdate) {
bulk.find({ _id: myDoc._id }).updateOne({ $set: update })
}
Promise.await(bulk.execute()) // or use regular await if you want...
Note we exit the function early if there's no docs because bulk.execute() throws an exception if there's no operations to process.
If your data have different data in the $set for each entry on array, I think you need a loop in server side.
Mongo has Bulk operations, but I don't know if you can call them using Collection.rawCollection().XXXXX
I've used rawCollection() to access aggregate and it works fine to me. Maybe work with bulk operations.
Following code gives me an exception in node js saying: "need to remove or update"
var args = {
query: { _id: _id },
update: { $set: data },
new: true,
remove: false
};
db.collection(COLLECTION.INVENTORY_LOCATION)
.findAndModify(args, function (err, results) {
if (err) {
return callback(err);
} else {
console.log(results);
callback(null, results);
}
});
Not able to figure out the issue as I have specified the update operation.
The syntax is different in the node driver than for the shell, which is the syntax you are using.
db.collection("collection_name").findAndModify(
{ _id: _id }, // query
[], // represents a sort order if multiple matches
{ $set: data }, // update statement
{ new: true }, // options - new to return the modified document
function(err,doc) {
}
);
There is a separate function for .findAndRemove()
As the documentation for the remove parameter of the findAndModify function states:
remove: <boolean>:
Must specify either the remove or the update field. Removes the
document specified in the query field. Set this to true to remove the
selected document . The default is false.
The default value is false so you don't have to provide it at all.
I believe the issue is that you are supplying both update and remove parameters. Try removing the remove parameter.