Change order of parameters in mongoose - node.js

I have following structure in my model :
geoLocation: {
latitude: {
type: Number
},
longitude: {
type: Number
}
}
I want to change the order to :
geoLocation: {
longitude: {
type: Number
},
latitude: {
type: Number
}
}
The problem is I have existing database in which data has been stored as :
"geoLocation" : {
"latitude" : 23.0720184,
"longitude" : 72.54213399999999
}
And I want to change order and want to save them as :
"geoLocation" : {
"longitude" : 72.54213399999999,
"latitude" : 23.0720184
}
I am using $centerSphere query of mongodb and it requires longitude as first argument. I tried to change order in model but it is not affecting the database.
How can I do that?

There is one way of doing it is by using aggregation. Please look for other better options too.
Using $project and $out we can simply put the longitude at first, the mongodb code snippet is shown below
db.collection_name.aggregate([{
$project:{
_id:1,
"project all other fields as shown for _id":1,
// Changing part
"geoLocation.longitude":"$geoLocation.longitude",
"geoLocation.latitude":"geoLocation.latitude"
}},
// Overwriting the same collection with latitude and longitude changed
{$out: "collection_name"}]);

Related

add field to all documents in mongodb collection

i am working on node js project, i have a mongodb collection of orders in which i want to add a field to every document , the problem is that i want to pass a function as the value of this new field and the arguments of the function are two other fields of the document , this is an example that will make you understand .
my collection :
[
{
_id:"eyxwapfhiezfe664ec",
orderPrice : 20,
createdAt : 2021-01-15T17:16:25.844Z
endedAt : 2021-01-15T17:20:25.844Z
}
{
_id:"eyxwlcfeojrfeoc",
orderPrice : 50,
createdAt : 2021-01-15T17:16:25.844Z
endedAt : 2021-01-15T17:20:25.844Z
}
{
_id:"eyxwapfhiseflflpsssc",
orderPrice : 20,
createdAt : 2021-01-15T17:16:25.844Z
endedAt : 2021-01-15T17:20:25.844Z
}
the field i want to add is completedTime which is the difference between createdAt and endedAt i have a function differenceBetweenDates which takes two dates as argument createdAt and ended at but i don't know how to pass this function to $set in update .
So to be more clear i want to add this field to every document :
CompletedTime: differenceBetweenDates(createdAt, endedAt)
thank you .
If you are using MongoDB 4.2, you can pass aggregation expressions as part of the update.
db.collection.updateMany({},[{ $set:{ CompletedTime: {$subtract:["$endedAt", "$createdAt" ]}}}])
In MongoDB 4.4 you might also use an aggregation pipeline with a $merge stage to merge the result set back into the original collection:
db.collection.aggregate([
{ $set: { CompletedTime: {$subtract:["$endedAt", "$createdAt" ]}}},
{ $merge: "collection" }
])

How to find text from populated model in mongoose?

We have two models:
User
const userSchema = new mongoose.Schema({
name: {
type: String
},
email: {
type: String
},
vehicleId: {
type: mongoose.Schema.Types.ObjectId,
ref: 'vehicle'
}
})
Vehicle
const vehicleSchema = new mongoose.Schema({
name: {
type: String
},
color: {
type: String
}
})
Now, we have to find user input from user's ( name, email ) & also from vehicle's ( name, color )
- If user search "pink" then we have to find that if any user has with name, email or them vehicle include "pink" keyword or not.
- If string match from reference model then we have to return whole record.
Can you please help us, how to achieve this?
You can first run a query on a Vehicle model to fetch all vehicles that have name pink
let vehicleIds=await Model.Vehicle.aggregate([
{$match:{$or:[
{name:{$regex:"pink",$options:"i"}},
{color:{$regex:"pink",$options:"i"}
]}},
{$group:{_id:null,ids:{$push:"$_id"}}}
])
here if vehicleIds is not empty, then vehicles[0].ids will have vehcileIds that have pink in name or color
let vehcileIds=[]
if(vehicles.length) vehcileIds=vehicles[0].ids
after above query runs run another query in the user model
let users= await Models.User.find({$or:[
{name:{$regex:"pink",$options:"i"}},
{email:{$regex:"pink",$options:"i"}
{vehicleId:{$in:}}
]})
I think, the best thing you can do is, Text Search.
ANSWER 1:
1.1. Step one
You have to create text index on your collection to perform text search.
create text index with
db.user.createIndex({"name":"text", "email": "text"})
db.vehicle.createIndex({"name": "text", "color": "text"})
we have to create text index on both collection as we want to perform text search on both collections.
*
Note:
as we are creating a text index on "name", "email" from User & "name", "color" from Vehicle, you can only perform text search on this four fields in respective collections.
you can assign weights to fields so that you can sort your result according to text score. (you must do this while creating index).
*
1.2. Step two
(i am assuming you are using Javascript and mongoose here.)
db.collection("users").aggregate({$match:{$text: {$search: "pink"}}},
(err, userData: any)=>{
if(err){
return err;
}
if(userdata.length){
let vehicleIds = userData.map((user) => mongoose.Types.ObjectId(user.vehicleId));
db.collection("vehicles").aggregate({$match:{$text:{$search:"pink", _id: {$in: vehicleIds}}}}, (err, vehicleData)=>{
//this will be your vehicle details
})
})
}else{
console.log("no Data found");
}
*
The problem with this approach is you have to fire two queries because of the restrictions in Text Search and extra overhead of text index on collections.
Good Thing is you get a lot of ways to perform a search, you can sort results according to relevance with text score, it is fast than the regular expression and you get better results, you get stemming, you get stop words (please ref links that are given).
*
ANSWER 2:
you can use regular expression
db.collection("users").aggregate({$match:{$or:[{name:{$regex: /pink/}}, {email:{$regex: /pink/}}]}},
{$lookup:{from:"vehicles", localField:"vehicleId", foreignFild:"_id", as:"vehicleDetail"}},
{$unwind:{path:"$vehicleDetail"}},
{$match:{$or:[{name:{$regex:/pink/}},{color:{$regex:/pink/}}]}}
(err, data)=>{
})
ANSWER 3:
If you dont want to prefer above options, you can fire normal query too
db.collection("users").aggregate({$match:{$or:[{name:{$regex: /pink/}}, {email:{$regex: /pink/}}]}},
{$lookup:{from:"vehicles", localField:"vehicleId", foreignFild:"_id", as:"vehicleDetail"}},
{$unwind:{path:"$vehicleDetail"}},
{$match:{$or:[{name:{$regex:/pink/}},{color:{$regex:/pink/}}]}}
(err, data)=>{
})
Hope this helps. :-)
Correct me if I am wrong anywhere. :-)
Below steps you should follow:
Find user by name and email.
Populate vehicle
Again match with the color.
db.getCollection('User').aggregate([
{ $match: { email: "h#gmail.com", name: "Hardik"}},
{ $lookup: {from: 'Vehicle', localField: 'vehicleId', foreignField: '_id', as: 'vehicle'} },
{ $match: { "vehicle.color": {$regex: '.*'+ "Pink" +'.*', $options: 'si'}}},
])
Data:
User
{
"_id" : ObjectId("5d51bd5ef5fc3d6486b40ffb"),
"email" : "h#gmail.com",
"name" : "Hardik",
"vehicleId" : ObjectId("5d539786f5fc3d6486b4252b")
}
Vehicle
{
"_id" : ObjectId("5d539786f5fc3d6486b4252b"),
"name" : "Bike",
"color" : "Pink"
}
Output:
{
"_id" : ObjectId("5d51bd5ef5fc3d6486b40ffb"),
"email" : "h#gmail.com",
"name" : "Hardik",
"vehicleId" : ObjectId("5d539786f5fc3d6486b4252b"),
"vehicle" : [
{
"_id" : ObjectId("5d539786f5fc3d6486b4252b"),
"name" : "Bike",
"color" : "Pink"
}
]
}
Hope this helps!

complicated mongoose pull list of data from api and insert into mongodb if it doesn't already exist

I am connecting to the Yelp API using the RapidAPI module in Nodejs. I am able to request a token, connect, and request data, retrieve that data, and insert the relevant information for each result it into mongodb. Here's where it gets complicated...
Let's say I make a Yelp API request and search for bars. I get a list of bars and insert them into the database. Let's say one of these in the list is "Joe's Bar & Grill". One of the fields in my mongodb is "type" and it's an array. So now, this particular document will look something like this:
{
id: 'joes-bar-and-grill',
name: 'Joe\'s Bar & Grill',
type: ['bar']
}
But then I run another request on the Yelp API on "restaurants", and in this list "Joe's Bar & Grill" shows up again. Instead of inserting a new duplicate document into mongodb, I'd like the existing document to end up looking like this:
{
id: 'joes-bar-and-grill',
name: 'Joe\'s Bar & Grill',
type: ['bar', 'restaurant']
}
In addition to this, let's say I run another request again for "bars", and "Joe's Bar & Grill" comes up again. I don't want it to automatically insert "bar" into the type array again, if "bar" already exists in its array.
I've tried findOneAndUpdate with upsert: true and a $push of new data into the array, but I cannot get it to work at all. Does anyone have any ideas?
You can use findOneAndUpdate, combined with $addToSet (to make sure that an entry in the array only exists once) and $each (to allow passing arrays to $addToSet):
Bar.findOneAndUpdate({ id : 'joes-bar-and-grill' }, {
id : 'joes-bar-and-grill',
name : 'Joe\'s Bar & Grill',
$addToSet : { type : { $each : [ 'restaurant' ] } }
}, { upsert : true })
EDIT: now that you posted your entire code, the problem becomes more obvious.
For one, I'm not sure if the third and fourth arguments that you're passing to Location.update() make sense. As far as I know, the third should be an option object, and the fourth an async function.
Secondly, it looks like you're just ignoring any update errors.
And lastly, this isn't going to work:
for (var i = 0; i < payload.businesses.length; i++) { Location.update(...) }
Because Location.update() is asynchronous, the i variable will get clobbered (you should browse around on SO to find the explanation for that; for example, see this question).
You're going to need a library that will provide you with better async support, and preferably one that will also help limiting the number of update queries.
Once such library is async, and using it, your code would become something like this:
const async = require('async');
...
async.eachLimit(payload.businesses, 5, function(business, callback) {
Location.update({ yelpID : business.id }, {
name : business.name,
latitude : business.location.latitude,
longitude : business.location.longitude,
address1 : business.location.address1,
address2 : business.location.address2,
address3 : business.location.address3,
city : business.location.city,
state : business.location.state,
zip_code : business.location.zip_code,
country : business.location.country,
timezone : 'CST'
$addToSet : { type : 'bar' }
}, { upsert : true }, callback);
}, function(err) {
if (err) {
console.error(err);
} else {
console.log('All documents inserted');
}
});
You may use $addToSet operator
The $addToSet operator adds a value to an array unless the value is
already present, in which case $addToSet does nothing to that array.
$addToSet only ensures that there are no duplicate items added to the
set and does not affect existing duplicate elements. $addToSet does
not guarantee a particular ordering of elements in the modified set.
If the field is absent in the document to update, $addToSet creates
the array field with the specified value as its element.
If the field is not an array, the operation will fail.
The below solution assumes that on each update, you receive a single type and not an array. If the input document is an array itself, you may use robertklep's solution with $each operator
db.mycoll.update(
{ "id" : "joes-bar-and-grill" },
{
$set:{
name : 'Joe\'s Bar & Grill',
},
$addToSet : { type : 'restaurant' }
},
true, false);
I have also used $set operator.
The $set operator replaces the value of a field with the specified
value.
The $set operator expression has the following form:
{ $set: { field1: value1, ... } }
Here is the mongo shell output to explain it further :
> db.mycoll.find({ "id" : "joes-bar-and-grill" });
// NO RESULT
> db.mycoll.update(
... { "id" : "joes-bar-and-grill" },
... {
... $set:{
... name : 'Joe\'s Bar & Grill',
... },
... $addToSet : { type : 'restaurant' }
... },
... true, false);
WriteResult({
"nMatched" : 0,
"nUpserted" : 1,
"nModified" : 0,
"_id" : ObjectId("58e719b4d543c5e30d615d59")
})
// INSERTED A NEW DOCUMENT AS IT DOES NOT EXIST
> db.mycoll.find({ "id" : "joes-bar-and-grill" }); // FINDING THE OBJECT
{ "_id" : ObjectId("58e719b4d543c5e30d615d59"), "id" : "joes-bar-and-grill", "name" : "Joe's Bar & Grill", "type" : [ "restaurant" ] }
> db.mycoll.update(
... { "id" : "joes-bar-and-grill" },
... {
... $set:{
... name : 'Joe\'s Bar & Grill',
... },
... $addToSet : { type : 'bar' }
... },
... true, false);
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
// UPDATING THE DOCUMENT WITH NEW TYPE : "bar"
> db.mycoll.findOne({ "id" : "joes-bar-and-grill" });
{
"_id" : ObjectId("58e719b4d543c5e30d615d59"),
"id" : "joes-bar-and-grill",
"name" : "Joe's Bar & Grill",
"type" : [
"restaurant",
"bar"
]
}

Using geoJSON in mongoose schema and use it in a query

I'm new to mongoDB and the usage of geoJSON within it.
I have the following schema I created using the mongoose module:
var mongoose = require('mongoose');
var hikeSchema = mongoose.Schema({
name: String,
area: String,
length: Number,
time: String,
Difficulty: { type : Number, min : 0 , max :5 },
start_coord: {
lon: Number,
lat: Number
},
rank_count: Number,
avg_overall_rating: { type : Number, min : 0 , max :5 },
completed_count: Number,
description: String,
ranking_pages: [
mongoose.Schema.Types.ObjectId
]
});
module.exports = mongoose.model('Hike', hikeSchema);
I used a simple Number type to represent to longitude and latitude inside a sub object within the schema.
I don't know if this is the way to go about it. I want it to be saved as a geoJSON object and then run queries on this object.
I recommand you to see the mongoDB part for geolocation datas : http://docs.mongodb.org/manual/core/2d/
To store longitude, latitude, MongoDB take you the correct data structure to save items :
To store location data as legacy coordinate pairs, use an array or an embedded document.
When possible, use the array format:
loc : [ < longitude > , < latitude > ]
Consider the embedded document form:
loc : { lng : , lat : }
Arrays are preferred as certain languages do not guarantee associative map ordering.
For all points, if you use longitude and latitude, store coordinates in longitude, latitude order.
On your shema, change
start_coord: {
lon: Number,
lat: Number
},
By :
start_coord: {
lng: Number,
lat: Number
},
Then you should add a 2dindex for your field start_coords to use all mongoDB geospatial queries :
db.collection.ensureIndex( { start_coord : "2d" } , { min : <l ower bound > , max : < upper bound > } )
EDIT It's important to set min and max if your longitude and latitude is not a real lng/lat (for example if you could use 2dindexes in a orthonormal map)
Now you can use all geospatial query operators : http://docs.mongodb.org/manual/reference/operator/query-geospatial/
You can also do it this way:
start_coord: {
type: [Number], // [<longitude>, <latitude>]
index: '2d' // create the geospatial index
}
Check this tutorial: How to use geospatial indexing in mongoDb

Can't get to work $match & $group MongoDB Aggregation

This is my code:
this.aggregate(
{
$match: {
measurer_code : parseInt(_measurer_code),
user_code :parseInt(_user_code),
time : {$gte : iDate, $lte : eDate}
}
},
{
$group:{
sum: {$sum: "$value"},
cant: {$sum: 1}
}
},cb);
The collection has the following structure:
user_code : Number,
measurer_code : Number,
value : Number,
time : Date
Returns undefined. But if i run only $match or only $group returns documents.
Someone can help me?
Thanks!
When you group you have to specify how you want to group the data.
If you want count and sum across all the documents, you have to add "_id":null or "_id":1 (any constant) in the $group document. If you want to group by field "foo" you have to add "_id":"$foo" to the document.

Resources