I currently have the below code setup to retrieve some data from a MongoDB database and I would like to make an IF function that runs on the documents received. I am trying to have an IF function that runs if the price field in the documents are 10% higher than event.payload.base_price otherwise move on, I am fairly new to coding and MongoDB so can't figure this one out as I am not sure how to write this function. Any help would be incredibly appreciated.
async function run() {
try {
const query = { contract: idSplit[1] , id: idSplit[2] };
const options = {
sort: { name: 1 },
projection: {_id: 0, slug: 1, contract: 1, id: 1, price: 1},
};
const cursor = listings.find(query, options);
await cursor.forEach(console.dir);
} finally {
await client.close();
}
}
run().catch(console.dir);
After you get the array In the cursor, you can use below if conditions in the forEach Loop:
cursor.forEach((element) =>
{
if (element.price > event.payload.base_price + event.payload.base_price * 0.1) {
console.log("Event is happening", element.price);
} else {
console.log("Event is not happening", element.price)
}
});
Related
I'm creating a like functionality for my app using MongoDB. I recently stumbled upon a problem.
My code for the liking:
async function like(articleId, userId) {
const db = await makeDb();
return new Promise((resolve, reject) => {
const result = db.collection(collectionName).findOneAndUpdate(
{ articleId: ObjectID(articleId) },
{
$setOnInsert: {
articleId: ObjectID(articleId),
creationDate: new Date()
},
$addToSet: { likes: ObjectID(userId) }
},
{ upsert: true }
);
resolve(result);
});
What it does:
If there is no document in the collection create one and fill in all the fields
If a document exists update only the likes array with a new userID
This works as expected and there are no problems, thanks to $addToSet I don't have any duplicates etc.
However after the above code gets executed and the result is returned I need to do some additional stuff and for it to work I need to know if Mongos $addToSet did change anything.
So in short:
if a new userId has been added to the likes array I want to do something
if the userId is already in the likes array and the $addToSet didnt change anything I don't want to take any action.
Is there a way to distinguish if the $addToSet did something?
The current console.log() of the result looks like this:
{
lastErrorObject: { n: 1, updatedExisting: true },
value: { _id: 5e2c47c57bb5183d80dce14f,
articleId: 5da4d1365217baf52fbcd76a,
creationDate: 2020-01-25T13:51:01.928Z,
likes: [ 5d750b677d8edfc08af8a527 ]
},
ok: 1
}
The one thing that comes to my mind (but is very bad performance-wise) is to compare the likes array before the update if it contains the userID with javascript includes method.
Any better ideas?
I found the solution!
99% of the credit goes to Neil Lunn for this answer:
https://stackoverflow.com/a/44994382/2175228
It brought me on the right track. I ditched the findOneAndUpdate method in favor of the updateOne method. The query parameters etc. everything stayed the same, only the method name changed and obviously the result type of this method.
The problem with the updateOne method is that it does not return the object that was updated (or the old one). But it does return the upsertedId which is the only thing I need in my situation.
So in the end what You get is a very long CommandResult object which starts with:
CommandResult {
result: { n: 1, nModified: 0, ok: 1 },
(...)
}
but when I looked very closely I noticed that the object has exactly those fields that I needed the whole time:
modifiedCount: 0,
upsertedId: null, // if object was inserted, this field will contain it's ID
upsertedCount: 0,
matchedCount: 1
So what You need to do is just take the parts that You need from the CommandResult object and just proceed ;)
My code after the changes:
async function like(articleId, userId) {
const db = await makeDb();
return new Promise((resolve, reject) => {
db.collection(collectionName)
.updateOne(
{ articleId: ObjectID(articleId) },
{
$setOnInsert: {
articleId: ObjectID(articleId),
creationDate: new Date()
},
$addToSet: { likes: ObjectID(userId) }
},
{ upsert: true }
)
.then(data => {
// wrap the content that You need
const result = {
upsertedId: data.upsertedId,
upsertedCount: data.upsertedCount,
modifiedCount: data.modifiedCount
};
resolve(result);
});
});
}
Hopefully this answer will help someone with a similar problem in the future :)
I'm using Apollo Client, and for fetching queries I'm using useQuery from the package #apollo/react-hooks.
I would like to accomplish the following:
List of Steps:
Step 1: Fetch a query stage
const GetStage = useQuery(confirmStageQuery, {
variables: {
input: {
id: getId.id
}
}
});
Step 2: Based on the response that we get from GetStage, we would like to switch between 2 separate queries
if (!GetStage.loading && GetStage.data.getGame.stage === "Created") {
Details = useQuery(Query1, {
variables: {
input: {
id: getId.id
}
}
});
} else if (!GetStage.loading && GetStage.data.getGame.stage === "Confirmed") {
Details = useQuery(Query2, {
variables: {
input: {
id: getId.id
}
}
});
}
Step 3: Also when the page loads every time, I'm re-fetching the data.
useEffect(() => {
//Fetch for Change in the Stage
GetStage.refetch();
//Fetch for Change in the Object
if (Details) {
Details.refetch();
if (Details.data) {
setDetails(Details.data.getGame);
}
}
});
Problem?
Rendered more hooks than during the previous render.
Details.data is undefined
So how can we call multiple async queries in Apollo Client?
As Philip said, you can't conditionally call hooks. However conditionally calling a query is very common, so Apollo allows you to skip it using the skip option:
const { loading, error, data: { forum } = {}, subscribeToMore } = useQuery(GET_FORUM, {
skip: !forumId,
fetchPolicy: 'cache-and-network',
variables: { id: forumId },
});
The hook is called, but the query isn't. That's a lot simpler and clearer than using a lazy query for your use case in my opinion.
The rules of hooks say you can't conditionally call hooks, whenever you find yourself in a situation where you want to use an if/else around a hook you're probably on the wrong track.
What you want to do here is to use a lazyQuery for everything that's "optional" or will be fetched later - or for queries that depend on the result of another query.
Here's a quick example (probably not complete enough to make your entire code work):
// This query is always called, use useQuery
const GetStage = useQuery(confirmStageQuery, {
variables: {
input: {
id: getId.id
}
}
});
const [fetchQuery1, { loading1, data1 }] = useLazyQuery(Query1);
const [fetchQuery2, { loading2, data2 }] = useLazyQuery(Query2);
// Use an effect to execute the second query when the data of the first one comes in
useEffect(() => {
if (!GetStage.loading && GetStage.data.getGame.stage === "Created") {
fetchQuery1({variables: {
input: {
id: getId.id
}
}})
} else if (!GetStage.loading && GetStage.data.getGame.stage === "Confirmed") {
fetchQuery2({variables: {
input: {
id: getId.id
}
}})
}
}, [GetState.data, GetStage.loading])
In the project that I am working on, built using nodejs & mongo, there is a function that takes in a query and returns set of data based on limit & offset provided to it. Along with this data the function returns a total count stating all the matched objects present in the database. Below is the function:
// options carry the limit & offset values
// mongoQuery carries a mongo matching query
function findMany(query, options, collectionId) {
const cursor = getCursorForCollection(collectionId).find(query, options);
return Promise.all([findManyQuery(cursor), countMany(cursor)]);
}
Now the problem with this is sometime when I give a large limit size I get an error saying:
Uncaught exception: TypeError: Cannot read property '_killCursor' of undefined
At first I thought I might have to increase the pool size in order to fix this issue but after digging around a little bit more I was able to find out that the above code is resulting in a race condition. When I changed the code to:
function findMany(query, options, collectionId) {
const cursor = getCursorForCollection(collectionId).find(query, options);
return findManyQuery(cursor).then((dataSet) => {
return countMany(cursor).then((count)=> {
return Promise.resolve([dataSet, count]);
});
);
}
Everything started working perfectly fine. Now, from what I understand with regard to Promise.all was that it takes an array of promises and resolves them one after the other. If the promises are executed one after the other how can the Promise.all code result in race condition and the chaining of the promises don't result in that.
I am not able to wrap my head around it. Why is this happening?
Since I have very little information to work with, I made an assumption of what you want to achieve and came up with the following using Promise.all() just to demonstrate how you should use Promise.all (which will resolve the array of promises passed to it in no particular order. For this reason, there must be no dependency in any Promise on the order of execution of the Promises. Read more about it here).
// A simple function to sumulate findManyQuery for demo purposes
function findManyQuery(cursors) {
return new Promise((resolve, reject) => {
// Do your checks and run your code (for example)
if (cursors) {
resolve({ dataset: cursors });
} else {
reject({ error: 'No cursor in findManyQuery function' });
}
});
}
// A simple function to sumulate countMany for demo purposes
function countMany(cursors) {
return new Promise((resolve, reject) => {
// Do your checks and run your code (for example)
if (cursors) {
resolve({ count: cursors.length });
} else {
reject({ error: 'No cursor in countMany' });
}
});
}
// A simple function to sumulate getCursorForCollection for demo purposes
function getCursorForCollection(collectionId) {
/*
Simulating the returned cursor using an array of objects
and the Array filter function
*/
return [{
id: 1,
language: 'Javascript',
collectionId: 99
}, {
id: 2,
language: 'Dart',
collectionId: 100
},
{
id: 3,
language: 'Go',
collectionId: 100
}, {
id: 4,
language: 'Swift',
collectionId: 99
}, {
id: 5,
language: 'Kotlin',
collectionId: 101
},
{
id: 6,
language: 'Python',
collectionId: 100
}].filter((row) => row.collectionId === collectionId)
}
function findMany(query = { id: 1 }, options = [], collectionId = 0) {
/*
First I create a function to simulate the assumed use of
query and options parameters just for demo purposes
*/
const filterFunction = function (collectionDocument) {
return collectionDocument.collectionId === query.id && options.indexOf(collectionDocument.language) !== -1;
};
/*
Since I am working with arrays, I replaced find function
with filter function just for demo purposes
*/
const cursors = getCursorForCollection(collectionId).filter(filterFunction);
/*
Using Promise.all([]). NOTE: You should pass the result of the
findManyQuery() to countMany() if you want to get the total
count of the resulting dataset
*/
return Promise.all([findManyQuery(cursors), countMany(cursors)]);
}
// Consuming the findMany function with test parameters
const query = { id: 100 };
const collectionId = 100;
const options = ['Javascript', 'Python', 'Go'];
findMany(query, options, collectionId).then(result => {
console.log(result); // Result would be [ { dataset: [ [Object], [Object] ] }, { count: 2 } ]
}).catch((error) => {
console.log(error);
});
There are ways to write this function in a "pure" way for scalability and testing.
So here's your concern:
In the project that I am working on, built using nodejs & mongo, there is a function that takes in a query and returns set of data based on limit & offset provided to it. Along with this data the function returns a total count stating all the matched objects present in the database.
Note: You'll need to take care of edge case.
const Model = require('path/to/model');
function findManyUsingPromise(model, query = {}, offset = 0, limit = 10) {
return new Promise((resolve, reject) => {
model.find(query, (error, data) => {
if(error) {
reject(error);
}
resolve({
data,
total: data.length || 0
});
}).skip(offset).limit(limit);
});
}
// Call function
findManyUsingPromise(Model, {}, 0, 40).then((result) => {
// Do something with result {data: [object array], total: value }
}).catch((err) => {
// Do something with the error
});
I'm using jquery ui's sortable function (source) to re-arrange elements. I've built custom callbacks to create an list of those elements. So when i move an element, all elements is given a new position id. It could look like this:
[{
id_of_element_in_database: 12,
new_position: 0
}, {
id_of_element_in_database: 16,
new_position: 1
}, {
id_of_element_in_database: 14,
new_position: 2
}]
I'm sending this list to my back-end by doing a simple Ajax post
$.post('/position', { data: list });
Route
router.post('/position', (req, res) => {
console.log(req.body.data); // This prints the array of objects above.
});
Schema
mongoose.Schema({
id: Number,
position: Number,
...
});
Now I can't figure out how to change the position of all documents effectively. Creating a crappy loop of the array and doing multiple database-requests can't be the best approach.
I've tried that here and this feels so wrong.
for (let i in req.body.data) {
collection.update({ id: req.body.data[i].id }, { position: req.body.data[i].position });
There must be something else i can do to achieve this. I've tried google without any luck.
You could try the bulkWrite API to carry out the updates in a better way without multiple requests to the server:
var callback = function(err, r){
console.log(r.matchedCount);
console.log(r.modifiedCount);
}
// Initialise the bulk operations array
var ops = req.body.data.map(function (item) {
return {
"updateOne": {
"filter": {
"id": parseInt(item.id),
"position": { "$ne": parseInt(item.position) }
},
"update": { "$set": { "position": parseInt(item.position) } }
}
}
});
// Get the underlying collection via the native node.js driver collection object
Model.collection.bulkWrite(ops, callback);
Here's how I did it with Node.js:
var forEach = require('async-foreach').forEach;
var bulk = Things.collection.initializeOrderedBulkOp();
Things.find({}).lean().execAsync(execSettings).then(function(resp){
forEach(resp, function(template, index, arr) {
var done = this.async();
bulk.find({'_id': template._id}).update({$set: {_sid: generateShortId()}});
bulk.execute(function (error) {
done();
});
}, function(notAborted, arr){
res.json({done: true, arr: arr.length});
});
});
I have used following code snippets to get followers of a particular user which are stored in mongodb
Saved user json like below
{ "_id":{
"$oid":"5440f606255cd4740e88ed21" }, "username":"test2", "email":"test2%40gmail.com", "name":"Test2", "version":null, "password":"2ea01e7a318f15378641f47469613ce6817cc91a", "gender":null, "dob":null, "profile_image":"1413543430090.jpg", "status":"1", "role_id":"2", "posts":[
], "followers":[
{
"_id":{
"$oid":"5440fb8fad74e87c0fdf22e5"
},
"user_id":"5440f0c3ae7a24ac0e47ca9e",
"unfollow":"0",
"created":{
"$date":"2014-10-17T11:20:47.107Z"
}
},
{
"_id":{
"$oid":"5440fc1e51a684e00b72db8a"
},
"user_id":"5440f9790dd5d4a40b6cec18",
"unfollow":0,
"created":{
"$date":"2014-10-17T11:23:10.645Z"
}
} ] }
to get all followers of a user i have used following code snippet. In this code followers get using foreach loop.
Main issue is that find query within foreach loop is not working because of a asynchronous callback function
db.collection('users').findOne({'_id': new mongo.ObjectID('5440f606255cd4740e88ed21'), "followers.unfollow": '0'}, {followers: 1, _id: 1}, function(err, user) {
var followers = user.followers;
var finalarray = [];
followers.forEach(function(follower, index) {
db.collection('users').find({"user._id": new mongo.ObjectID(follower._id)}, {_id: 1, username: 1, profile_picture: 1},function(err, users) {
if (user) {
var temp_follower = [];
temp_follower = {'follower_id': user._id, 'username': user.username,'profile_picture': user.profile_picture};
finalarray.push(temp_follower);
}
});
});
});
res.json({'replyCode': "success", 'replyMsg': "followers get successfully", 'followers': finalarray});
Finally in response JSON i have not find any follower user detail
Help me to get rid of this issue.
Thanks
Dinesh Prajapati
You can use the $in operator instead of looping. Here is a sample code skeleton
db.collection('users').findOne({'_id': new mongo.ObjectID('5440f606255cd4740e88ed21'), "followers.unfollow": '0'}, {followers: 1, _id: 1}, function(err, user) {
db.collection('users').find({"user._id":{$in: user.followers }, {_id: 1, username: 1, profile_picture: 1},function(err, users) {
res.json({'replyCode': "success", 'replyMsg': "followers get successfully", 'followers': users});
}
});
});
Note that to perform sql-join like operation you may have to use mapReduce kind of thing. You can refer this link.