Query an array of linked documents in Mongo using aggregates - mongodb

I have the following collections.
interface PartnerOrganization extends Document {
retailLocations?: RetailLocations[];
}
interface RetailLocation extends Document {
location: {
type: string;
coordinates: [number];
};
}
RetailLocation has an appropriate index setup, and I can successfully run aggregate queries such as the following
const retailLocation = await RetailLocation.aggregate([
{
$geoNear: {
near: { type: 'Point', coordinates: [searchInput.longitude, searchInput.latitude] },
distanceField: 'dist.calculated',
maxDistance: Number(searchInput.radius),
includeLocs: 'dist.location',
spherical: true,
},
},
]);
I would like to be able to write an aggregate pipeline which uses a $geoNear aggregate function, against PartnerOrganizations.
The overall goal being that I can do a search for PartnerOrganizations that match certain $search (I am using the $search aggregate) criteria as well as location criteria.
The issue I am having is that $geoNear needs to be the first step in the aggregate pipeline, and as such retailLocations hasn't been populated at that stage. I have tested it out with $lookups, but doing anything before the $geoNear stage will cause a Mongo error similar to this -
Basically, my 'dream query' if you will, is something like this
const retailLocation = await PartnerOrganization.aggregate([
{
$lookup: {
from: 'retaillocations',
localField: 'retailLocations',
foreignField: '_id',
as: 'retailLocations',
},
},
{
$geoNear: {
key: 'retailLocations.location',
near: { type: 'Point', coordinates: [searchInput.longitude, searchInput.latitude] },
distanceField: 'dist.calculated',
maxDistance: Number(searchInput.radius),
includeLocs: 'dist.location',
spherical: true,
},
},
{
$search: {
index: 'Partner Organizations',
text: {
query: `*${searchInput.text}*`,
path: ['companyName', 'instagramBio', 'ogDescription'],
},
},
}
]);
But just not sure if this is possible, or if i have to do separate queries to make it work, then filter/map between the results. Just for claritys sake - I am using Linked Documents, not embedded - you can see what that looks like in Compass below.

Related

Does geoNear aggregation not work when the query field is the objectId?

My geonear code work except when I put query: {id: req.params.id}. It works for any other filter obj I put.
Does anyone know how to query for a certain document using the objectId while using geoNear?
I presently have what's below. Is there also any way to do the matching on the document first i.e {$match: {id: req.params.id}. I tried doing this but since $geoNear has to be the first step in the pipeline it didn't work out.
const distances = await User.aggregate([
{
$geoNear: {
near: {
type: "Point",
coordinates: [lng, lat],
},
// maxDistance: 13.3333 * 1000,
spherical: true,
distanceField: "distance",
distanceMultiplier: 0.001,
query: { _id: req.params.id },
},
},
{ $project: { id: 1, distance: 1 } },
]);
unable to use $match operator for mongodb/mongoose aggregation with ObjectId ^ This fixed it sorry!

What's the best way to manage large ObjectID arrays in mongoose / mongo

In this case:
const PostSchema = new mongoose.Schema({
"content": {
type: String,
required: true
},
"user": {
type: mongoose.Schema.Types.ObjectId,
required: true,
ref: "User"
},
"created": {
type: Date,
default: Date.now()
},
"comments": [{
type: mongoose.Schema.Types.ObjectID,
ref: 'Comment'
}]
})
I want to be able to get 10 comments at a time, but I see no way to do that without having to get all the comments every time.
You can use uncorrelated lookup to join collections and limit to 10. Here is an example, I used String for _id for easy understanding.
$lookup - there are two lookup, I used here uncorrelated lookup where you can do parallel aggregation in joining collection. $match helps to conditionally join documents. $expr is a must to use inside the $match when you use uncorrelated lookup. $limit helps to limit the documents. If you need you can add more stages to perform aggregation inside the pipeline
Here is the script
db.PostSchema.aggregate([
{
"$lookup": {
"from": "Comment",
let: {
cId: "$comments"
},
"pipeline": [
{
$match: {
$expr: {
_id: {
in: [
"$$cId"
]
}
}
}
},
{
$limit: 10
}
],
"as": "comments"
}
}
])
Working Mongo playground

Question for using $geoNear twice in mongodb

I want to make a mongodb query to search companies by applying following filter.
{
myLocation: [lng1, lat1], // That is to get distance from current location to companies on result.
region: [lng2, lat2], //That is to get companies within a radius in this region
radius: 10000,
tags: ["tag1", "tag2"],
sort: "asc" // to sort by nearest from current location.
}
I thought it needs to use $geoNear twice and made an aggregation like this.
{
$geoNear: {
near: {
type: "Point",
coordinates: myLocation
},
distanceField: 'distance1',
maxDistance: radius,
spherical: true,
},
$match: { tags: tags },
$geoNear: {
near: {
type: "Point",
coordinates: region
},
distanceField: 'distance2',
spherical: true,
}
}
but it occurs an error - $geoNear is only valid as the first stage in a pipeline.
Is there any way to make a query to work this filter.
Appreciate your help.

Better way to query multiple fields in $geoNear than using $or?

I have a list of events. I would like to search both the title and description fields for a certain phrase and return events in a given radius matching the query.
I am currently using the $or operator, which seems to work. However, I am wondering if there is a better way of doing this? I've created a compound text index that uses both fields; I'm just not sure if it actually gets used.
let queryText = /someString/;
return db.collection('events').aggregate([
{
$geoNear: {
near: [lng, lat],
distanceField: 'distance',
maxDistance: radius,
query: {
$or: [
{title: queryText},
{description: queryText}
]
},
spherical: true
}
}
]).toArray();
You can include your query in another argument (remember that $geoNear always must be on top) like this
let queryText = /someString/;
return db.collection('events').aggregate([
{
$geoNear: {
near: [lng, lat],
distanceField: 'distance',
maxDistance: radius,
spherical: true
},
{
$match: {
$or: [
{title: queryText},
{description: queryText}
]
}
}
]).toArray();
All of the time I use this way, I have never try your way before.

MongoDB : near + lookup in aggregation query

I would like to know if this is even possible.
Actually my query is : a near query on Users table + a forEach loop of find queries on UserSports table where user._id = userId of UserSports (it's like a join done in a foreach) :
//geonear query
db.users.find({
geopoint_mongo:
{$near:
{$geometry: {type: "Point", coordinates: [userGeopoint.lng, userGeopoint.lat]},
$maxDistance: maxDistance * 1000
}
}
},
{_id: 1, geopoint: 1, url_avatar: 1, bool_coach: 1, bool_ambassador: 1, gender: 1, bool_handicap: 1}
).toArray(function (err, usersFound) {
if (usersFound && usersFound.length > 0) {
var count = 0;
var total = usersFound.length;
var users = [];
// start the loop
usersFound.forEach(function (user) {
// find with condition query
db.userSports.find({
userId: user._id,
sportId: {$in: sportIds}
},
{_id: 1, user_level: 1, sportId: 1}
).toArray(function (err, userSports) {
count++;
if (userSports && userSports.length > 0) {
user.userSports = userSports;
users.push(user);
}
if (count === total) {
console.log('Done');
}
});
});
}
});
I heard you can use $lookup and aggregate to do the join, because actually my queries take too much time - but I wasn't able to find any example of this.
At least, is there a solution to run this code faster?
You can use below aggregation query with $lookup stage in 3.4 version.
db.users.aggregate([
{"$match":{ // Match to find users in location
"geopoint_mongo":{
"$near":{
"$geometry":{"type":"Point","coordinates":[userGeopoint.lng, userGeopoint.lat]},
"$maxDistance":maxDistance*1000
}
}
}},
{"$project":{ // Keep fields from users collection
"geopoint":1,"url_avatar":1,"bool_coach":1,"bool_ambassador":1,"gender":1,"bool_handicap":1
}},
{"$lookup":{ // Join user with user sports collection
"from":"userSports",
"localField":"_id",
"foreignField":"userId",
"as":"sports"
}},
{"$unwind":"$sports"}, // $unwind + $match to get matching sports
{"$match":{"sports.sportId":{"$in":sportIds}}},
{"$addFields":{"sports._id":1,"sports.user_level":1,"sports.sportId":1}}, // Keep fields from user sports collection
{"$group":{
"_id":"$_id",
"geopoint":{"$first":"$geopoint"},// Keep fields from users collection
"bool_coach":{"$first":"$bool_coach"},
"bool_ambassador":{"$first":"$bool_ambassador"},
"gender":{"$first":"$gender"},
"bool_handicap":{"$first":"$bool_handicap"},
"sports":{"$push":"$sports"} // Collect matching sports
}}
])
In such a aggregation query we have to consider The aggregation pipeline. Bellow code will help you to identify syntax for this query
db.driversLocation.aggregate([
{
$geoNear: {
near: {type: "Point", coordinates:[parseFloat(req.query.latitude), parseFloat(req.query.longitude )]},
distanceField: "dist.calculated",
maxDistance:10000,
spherical: true
}
},
{
$lookup:
{
from: "drivers",
localField: "driverId",
foreignField: "oid",
as: "mas"
},
},{
$match:{
"mas.vehicle.bodyType": "lorry"
}
},
]