how can i search database table with latitude and longitude using sequelize - node.js

I have a store table that has a column lat_long which stores each store latitude/longitude as geometry point in sequelize. I want to be searching for stores based on proximity of the latitude/longitude from the request body from the front end. Have tried to exploit many related topics(answer) concerning this in Stackoverflow but what have learned is that the query can be written like this.
const location = sequelize.literal(`ST_GeomFromText('POINT(${lng} ${lat})', 4326)`);
User.findAll({
attributes: [[sequelize.fn('ST_Distance_Sphere', sequelize.literal('geolocation'), location),'distance']],
order: 'distance',
limit: 10,
logging: console.log
})
.then(function(instance){
console.log(instance);
})
Have tried it and it works fine but the challenge am having is that; in this example and almost all the sample I saw sofar they made use of the attribute which return only distance but I want to return all the fields in the table. Then I tried removing the attribute then the response I got was not searched based on the geometry(distance) and I also tried to use where but am getting an error.
Shown below is my latest query which is returning error please how can I go about this? thank you.
export const getNearestStoreWithCategory = async (lat, long) => {
try {
const location = sequelize.literal(`ST_GeomFromText('POINT(${lat} ${long})')`);
const distance = sequelize.fn('ST_Distance_Sphere', sequelize.literal('lat_long'), location)
const storeArr = await Store.findAll({
order: distance,
where: sequelize.where(distance),
limit: 20
})
return storeArr
} catch (error) {
return error
}
}

From the doc at https://sequelize.org/v4/manual/tutorial/querying.html, I'm guessing what you are missing in the sample code you had is including the distance to the fields to be returned as the response from the db rather that only computing the distance and returning;
const location = sequelize.literal(`ST_GeomFromText('POINT(${lng} ${lat})', 4326)`);
User.findAll({
attributes: {
include: [[sequelize.fn('ST_Distance_Sphere', sequelize.literal('geolocation'), location),'distance']]
},
order: 'distance',
limit: 10,
logging: console.log
})
.then(function(instance){
console.log(instance);
})
This should give a query like;
SELECT id, OTHER_FIELDS_OF_THIS_TABLE, ST_Distance_Sphere(geolocation) AS distance ...
Now, this is untested code, but you could give it a try and comment if it helps or not.

Related

Firestore promise wait for product details before returning products

I know similar questions like this have been asked 1000 times but for the life of me I am struggling with something I feel is quite simple.
We have 2 tables, one called order_lines the other called order_lines_meta, I need to first query order_lines and for each line get the order_lines_meta and return that
I have tried a lot of variations, here is where I am at and stuck, I need it to wait for the order_lines_meta to come back because otherwise I get blank metaData as the data comes after nodejs has already outputted the order_lines
At the end an object that contains order info, line items of objects and within line items a meta data object
Appreciate the help, I just can't seem to wrap my brain on this one , and I am certainly open to other ways of doing this as well
Using nodejs, express, typescript, firestore
const orderNumber = req.query.orderNumber as string;
const customerName = req.query.customerName as string;
const orderDate = req.query.orderDate as string;
const pickListObj = {
orderNumber: orderNumber,
customerName: customerName,
orderDate: orderDate,
line_items: <any>[],
};
db.collection('order_lines').where('number', '==', orderNumber).get().then((snap) => {
const promises = <any>[];
snap.forEach(async (order: any) => {
// get meta data
const metaDataObj = <any>[];
const productName = order.data().name;
const productQty = order.data().quantity;
promises.push(db.collection('worder_line_meta').where('lineId', '==', order.data().lineId).get().then((doc: any) => {
if (doc.display_value != '') {
const meta = [{display_key: doc.data().display_key, display_value: doc.data().display_value}];
metaDataObj.push(meta);
}
}));
});
return Promise.all(promises);
}).then(() => {
pickListObj.line_items.push({name: productName, quantity: productQty, meta_data: metaDataObj});
});
Move the push statement from the last .then inside the previous .then:
promises.push(db.collection('worder_line_meta')...then((doc: any) => {
if (doc.display_value != '') {
...
}
pickListObj.line_items.push({name: productName,
quantity: productQty,
meta_data: metaDataObj});
}));
In the last .then, you will then find the complete pickListObj.
However, I wonder whether it might be simpler and faster to join the two database collections right on the database and retrieve everything with one db.collection operation.

mongoose .find with pagination returning 0 elements

I'm trying to get pagination working properly, but for some reason, mongoose is returning zero items.
Here's the relevant code in nodejs:
if (req.query.name) {
match.name = req.query.name;
}
// initial pageSize is 2
// initial page is 1
pageSize = parseInt(req.query.pagesize);
page = parseInt(req.query.page);
const foods = await Food.find({ match })
.skip(pageSize * (page - 1))
.limit(pageSize);
res.send({
foods,
});
Now, I've used this same technique using .populate with a different route and have gotten the result I'm looking for like so:
await req.user
.populate({
path: 'pets',
options: {
limit: req.query.pagesize,
skip: req.query.pagesize * (req.query.page - 1),
},
})
.execPopulate();
I can't figure out what's wrong. There's 20 items in the database. If I remove the .limit and .skip and just have the .find, I retrieve all the items.
It seems like you are pasting in the wrong value to .find()
Shouldn't you place .find({name: req.query.name}) or .find(match) in your case??
Now it says ({match: {name: req.query.name}}) wich doesn't work I believe

Optional parameters on sequelize query

Good morning.
I'm quite new to NodeJS / sequelize world and I'm currently facing a problem while trying to display a dashboard on screen.
This dashboard has three filters: two dates (period), client name, and employee name. The user can select none, one, two, or all the filters and my database needs to work accordingly.
That being said, my problem is with Sequelize because I don't know how to treat this problem of parameters not being "always" there.
I've seen this question:
Sequelize optional where clause parameters?
but this answer doesn't work anymore. I also tried another way of building the where clause, but I failed on it as well (mainly due to sequelize operators).
The last thing I tried was to make a single query with all parameters included but try to find some value (or flag) that would make sequelize ignore the parameter, for the case when the parameter was no there*, but it looks like Sequelize doesn't have anything like that.
* I've read a question here that has an answer saying that {} would do the trick but I tried that as well but didn't work.
In summary: I need to make a query that can "change" over time, for example:
Foo.findAll({
where: {
id : 1,
}
});
Foo.findAll({
where: {
id {
[Op.in] : [1,2,3,4,5]
},
name: "palmeiira",
}
});
Do you know a way of doing it without the need of using a lot if / switch statements?
I'm currently using Sequelize v. 5.5.1.
Update
I tried doing as suggested by #Anatoly and created a function to build the parameters. It was something like that. (I tried a "smaller" version just to test)
async function test() {
const where = {};
where[Op.and] = [];
where[Op.eq].push({
id: {
[Op.in]: [1,2,3]
}
});
return where;
}
I setted the return value to a const:
const query = await test()
And tried console.log(query)
The result was: { [Symbol(and)]: [ { id: [Object] } ] }, which made me believe that the problem was parsing the Op part so i tried using 'Op.and' and 'Op.in' to avoid that and it solved this problem, but led to another on sequelize that said Invalid value
Do you have any idea where is my error ?
P.S.: #Anatoly very nice idea you gave me on original answer. Thank you very much.
If these three conditions should work together then you can use Op.and with an array of conditions:
const where = {}
if (datesFilter || clientNameFilter || employeenameFilter) {
where[Op.and] = []
if (datesFilter) {
where[Op.and].push({
dateField: {
[Op.between]: [datesFilter.start, datesFilter.finish]
}
})
}
if (clientNameFilter) {
where[Op.and].push({
name: {
[Op.iLike]: `%${clientNameFilter.value}%`
}
})
}
if (employeenameFilter) {
where[Op.and].push({
employeeName: {
[Op.iLike]: `%${employeenameFilter.value}%`
}
})
}
}
const dashboardItems = await DashboardItem.findAll({ where }, {
// some options here
})
If the conditions should work as alternatives then just replace Op.and with Op.or

Increment a column with the default value as null

I need to increment a column with 1 on some occasions, but the default value of that column is null and not zero. How do I handle this case using sequelize? What method could be utilized?
I could do by checking the column for null in one query and updating it accordingly in the second query using sequelize but I am looking for something better. Could I handle this one call?
I'll confess that I'm not terribly experienced with sequelize, but in general you'll want to utilize IFNULL. Here's what the raw query might look like:
UPDATE SomeTable
SET some_column = IFNULL(some_column, 0) + 1
WHERE <some predicate>
Going back to sequelize, I imagine you're trying to use .increment(), but judging from the related source, it doesn't look like it accepts anything that will do the trick for you.
Browsing the docs, it looks like you might be able to get away with something like this:
SomeModel.update({
some_column: sequelize.literal('IFNULL(some_column, 0) + 1')
}, {
where: {...}
});
If that doesn't work, you're probably stuck with a raw query.
First you need to find the model instance and update via itself, or update directly via Sequelize Static Model API.
Then you'll check whether the updated field got nullable value or not ? If fails then do the manual update as JMar propose above
await model.transaction({isolationLevel: ISOLATION_LEVELS.SERIALIZABLE}, async (tx) => {
const user = await model.User.findOne({
where: {
username: 'username',
},
rejectOnEmpty: true,
transaction: tx,
});
const updatedRecord = await user.increment(['field_tag'], {
transaction: tx,
});
if (!updatedRecord.field_tag) {
/** Manual update & Convert nullable value into Integer !*/
await model.User.update({
field_tag: Sequelize.literal('IFNULL(field_tag, 0) + 1')
}, {
where: {
username: 'username',
},
transaction: tx,
});
}
});

How can I validate if gps coordinates given to a GET web service in node.js/mongodb are correct?

I have a webservice that queries the mongodb (with mongoose) with a given gps data, this is how it looks:
app.get('/queryUsers/:latitude,:longitude,:distance', function(req, res) {
var lat = req.params.latitude;
var long = req.params.longitude;
var distance = req.params.distance;
// Opens a generic Mongoose Query. Depending on the post body we will...
var query = HelpRequest.find({});
// ...include filter by Max Distance (converting miles to meters)
if (distance) {
// Using MongoDB's geospatial querying features. (Note how coordinates are set [long, lat]
query = query.where('location').near({
center: {type: 'Point', coordinates: [long, lat]},
// Converting meters to miles. Specifying spherical geometry (for globe)
maxDistance: distance * 1609.34, spherical: true
});
}
etc.
When I call it with a correct values, e.g.:
http://localhost:3000/queryUsers/48.77824506989009,19.80828625000005,1691.758035687216
then I'm getting the correct data. But when I call it for example with string:
http://localhost:3000/queryUsers/48.77824506989009,someString,1691.758035687216
then I'm getting this error:
{"stack":"Error\n at MongooseError.CastError
(/project/node_modules/mongoose/lib/error/cast.js:18:16)\n at SchemaArray.SchemaNumber.cast
(/project/node_modules/mongoose/lib/schema/number.js:219:9)\n at SchemaArray.castToNumber
(/project/node_modules/mongoose/lib/schema/array.js:227:38)\n at /project/node_modules/mongoose/lib/schema/array.js:237:29\n at Array.forEach (native)\n at castArraysOfNumbers
(/project/node_modules/mongoose/lib/schema/array.js:233:7)\n at cast$geometry (/project/node_modules/mongoose/lib/schema/array.js:260:7)\n at SchemaArray.cast$near
(/project/node_modules/mongoose/lib/schema/array.js:249:12)\n at SchemaArray.castForQuery
(/project/node_modules/mongoose/lib/schema/array.js:190:19)\n at module.exports
Now, since I'm new to writing such webservices - could you help me and tell me how should I modify my code to return a nicer message to the end user in case of giving the wrong data?
Maybe you can validate each of the numbers and return errors if they are not what you expect them to be, that way you can intercept the error before it happens in Mongoose which will be much faster since it doesn't hit the DB to do validation there.
Since lat and long are float numbers, you can validate if they are in fact, float numbers. The following link can help you with that.
How do I check that a number is float or integer?
To actually validate, you could use middleware or do it in the same method you're using but middleware is more "node.js".
app.get('/queryUsers/:latitude,:longitude,:distance', validateLocation, function(req, res) {
// data is correct here
});
and then create a new method to do the validation:
function validateLocation(req, res, next) {
var lat = req.params.latitude;
if(!isFloat(lat)) { return res.status(406).send("Please send a valid latitude"); }
// ... do the same for each variable. you can also use arrays to return multiple errors
else {
return next();
}
}

Resources