mongodb - use geoNear without omitting non 2dsphere indexed rows - mongodb

When using geoNear, all rows which do not have location (not a part of 2dsphere index) are omitted.
How to use geoNear to get nearest results but also show other rows that do not have location? The rows which do not have a location should be ranked lower in the results.
.collection('users')
.aggregate([
{
$geoNear: {
near: { type: "Point", coordinates: [userLocArray[0], userLocArray[1]]},
distanceField: "dist.calculated",
spherical: true,
//maxDistance: matchDistance,
}
},
Example documents (In the example below, the users xyz and mmm are omitted by geoNear because they do not have location field. I just wanted them to be ranked lowest but not completely omitted.
db={
users: [
{
_id: "abc",
name: "abc",
group: 1,
location: {
type: "Point",
coordinates: [
53.23,
67.12
]
},
calculatedDist: 112
},
{
_id: "xyz",
name: "xyyy",
group: 1,
calculatedDist: 13
},
{
_id: "123",
name: "yyy",
group: 1,
location: {
type: "Point",
coordinates: [
54.23,
67.12
]
},
calculatedDist: 13
},
{
_id: "rrr",
name: "tttt",
group: 1,
location: {
type: "Point",
coordinates: [
51.23,
64.12
]
},
calculatedDist: 14
},
{
_id: "mmm",
name: "mmmm",
group: 1,
calculatedDist: 14
},
]
}

You can simply add another $match stage at the end of pipeline to choose those documents without location
db.users.aggregate([
{
"$geoNear": {
"near": {
"type": "Point",
"coordinates": [
53.23,
67.12
]
},
"distanceField": "d"
}
},
{
"$unionWith": {
"coll": "users",
"pipeline": [
{
"$match": {
"location": null
}
}
]
}
}
])
Yes, you are right that Mongo Playground cannot demonstrate 2d sphere index behaviour. But I put it here nevertheless.

Related

mongodb - How to get sorted list of mutual likes from a collection?

I have two collections:
users - All the user info
'partnership` - Users can partner/follow each other.
Goal: To get all partnerships sorted by last partnered date.
When do users become partners: Assuming user_1 liked user_2 AND they become partners only when user_2 also liked user_1 (mutual like)
What is partnered date: The date/time when second like (mutual like) happened.
I started a mongo playground but please ignore my query -
Mongo Playground
Here is my sample data
db={
partnership: [
{
_id: "xyz_rrr",
updated: "2022-10-23T12:35:24.772+00:00",
users: [
"xyz",
"rrr"
]
},
{
_id: "rrr_eee",
updated: "2022-12-23T12:35:24.772+00:00",
users: [
"rrr",
"eee"
]
},
{
_id: "eee_rrr",
updated: "2023-01-21T12:35:24.772+00:00",
users: [
"eee",
"rrr"
]
},
{
_id: "mmm_rrr",
updated: "2023-02-19T12:35:24.772+00:00",
users: [
"mmm",
"rrr"
]
},
{
_id: "rrr_mmm",
updated: "2023-02-21T12:35:24.772+00:00",
users: [
"rrr",
"mmm"
]
},
],
users: [
{
_id: "abc",
name: "abc",
group: 1,
location: {
type: "Point",
coordinates: [
53.23,
67.12
]
},
calculatedDist: 112
},
{
_id: "xyz",
name: "xyyy",
group: 1,
location: {
type: "Point",
coordinates: [
54.23,
67.12
]
},
calculatedDist: 13
},
{
_id: "123",
name: "yyy",
group: 1,
location: {
type: "Point",
coordinates: [
54.23,
67.12
]
},
calculatedDist: 13
},
{
_id: "rrr",
name: "rrrrrrr",
group: 1,
location: {
type: "Point",
coordinates: [
51.23,
64.12
]
},
calculatedDist: 14
},
{
_id: "mmm",
name: "mmmm",
group: 1,
location: {
type: "Point",
coordinates: [
51.23,
64.12
]
},
calculatedDist: 14
},
{
_id: "eee",
name: "eeeee",
group: 1,
location: {
type: "Point",
coordinates: [
55.23,
62.12
]
},
calculatedDist: 143
}
],
}
Expected result
{
partneredUsers:
{ firstUser :
{
_id: "mmm",
name: "mmmm",
},
},
secondUser :
{
_id: "rrr",
name: "rrrrrrr",
},
},
partneredDate: "2023-02-21T12:35:24.772+00:00",
},
{
partneredUsers:
{ firstUser :
{
_id: "rrr",
name: "rrrrrrr",
},
},
secondUser :
{
_id: "eee",
name: "eeeee",
}
},
partneredDate: "2023-01-23T12:35:24.772+00:00",
}
}
Starting from partnership collection, create a partition key by $sortArray and $concat to identify mutually liked relationship. (They will share the same key). Use the partition key in $setWindowFields to compute count and rank.
count: pair of users mutually like each other when count > 1
rank: sort by updated: -1; the latest like will have rank: 1
Finally $lookup from users to get the details of users and $sort by partneredDate.
db.partnership.aggregate([
{
$set: {
partitionKey: {
"$reduce": {
"input": {
$sortArray: {
input: "$users",
sortBy: 1
}
},
"initialValue": "",
"in": {
"$concat": [
"$$value",
"$$this"
]
}
}
}
}
},
{
"$setWindowFields": {
"partitionBy": "$partitionKey",
"sortBy": {
"updated": -1
},
"output": {
"count": {
$sum: 1
},
rank: {
$rank: {}
}
}
}
},
{
$match: {
count: {
$gt: 1
},
rank: 1
}
},
{
"$lookup": {
"from": "users",
"localField": "users",
"foreignField": "_id",
"as": "userLookup"
}
},
{
"$project": {
_id: 0,
partneredUsers: {
firstUser: {
$first: "$userLookup"
},
secondUser: {
$last: "$userLookup"
}
},
partneredDate: "$updated"
}
},
{
$sort: {
partneredDate: -1
}
}
])
Mongo Playground

mongodb - How to sort by distance using geoNear in addition to looking up another collection

I have two functionalities working individually but want to combine them.
Functionality 1 - Sort users by their geoNear distance.
Functionality 2 - The users should not have already been liked by the
current user (look up partnership collection)
How to update this query to start from the user's collection so I can do geoNear?
The output in the below mongoplayground is correct except that the resulting users are not sorted by calculatedDist which is a field calculated by geoNear.
$geoNear: {
near: { type: "Point", coordinates: [x,y },
distanceField: "calculatedDist",
spherical: true
}
geoNear needs location which is only available in users collection hence I think below query needs to be modified to start in user's collection.
https://mongoplayground.net/p/7H_NxciKezB
db={
users: [
{
_id: "abc",
name: "abc",
group: 1,
location: {
type: "Point",
coordinates: [
54.23,
67.12
]
},
calculatedDist: 13
},
{
_id: "xyz",
name: "xyyy",
group: 1,
location: {
type: "Point",
coordinates: [
54.23,
67.12
]
},
calculatedDist: 11
},
{
_id: "123",
name: "yyy",
group: 1,
location: {
type: "Point",
coordinates: [
54.23,
67.12
]
},
calculatedDist: 2
},
{
_id: "rrr",
name: "tttt",
group: 1,
location: {
type: "Point",
coordinates: [
54.23,
67.12
]
},
calculatedDist: 11
},
{
_id: "eee",
name: "uuu",
group: 1,
location: {
type: "Point",
coordinates: [
54.23,
67.12
]
},
calculatedDist: 7
},
],
partnership: [
{
_id: "abc_123",
fromUser: "abc",
toUser: "123"
},
{
_id: "eee_rrr",
fromUser: "eee",
toUser: "rrr"
},
{
_id: "rrr_abc",
fromUser: "rrr",
toUser: "abc"
},
{
_id: "abc_rrr",
fromUser: "abc",
toUser: "rrr"
},
{
_id: "xyz_rrr",
fromUser: "xyz",
toUser: "rrr"
},
{
_id: "rrr_eee",
fromUser: "rrr",
toUser: "eee"
},
]
}
geoNear as far as I know has to be the first thing to be done so my query should start with the users collection. This breaks my partnership check because for that to work, I start at partnership collection.
In the playground above, the user eee has a lesser calculated distance as a result of geoNear but it shows after user abc.
Try this out:
db.partnership.aggregate([
// $geoNear
{
$match: {
$or: [
{
fromUser: "rrr"
},
{
toUser: "rrr"
}
]
}
},
{
$group: {
_id: 0,
from: {
$addToSet: "$fromUser"
},
to: {
$addToSet: "$toUser"
}
}
},
{
$project: {
_id: 0,
users: {
$filter: {
input: {
$setIntersection: [
"$from",
"$to"
]
},
cond: {
$ne: [
"$$this",
"rrr"
]
}
}
}
}
},
{
$lookup: {
from: "users",
let: {
userId: "$users"
},
pipeline: [
{
"$geoNear": {
"near": {
"type": "Point",
"coordinates": [
31.4998,
-61.4065
]
},
"distanceField": "calculatedDist",
"spherical": true
}
},
{
"$match": {
"$expr": {
"$in": [
"$_id",
"$$userId"
]
}
}
}
],
as: "users"
}
},
{
$project: {
users: 1,
count: {
$size: "$users"
}
}
}
])
Here, we use the pipelined form of lookup.
The lookup is on the user's collection, in which we specify a pipeline with the $geoNear stage as the first stage.
And finally filter out and only keep the users belonging to a partnership.
This is the playground link. Let me know if it works, on the playground I can't test it because $geoNear requires a 2d index.
While using calculatedDist, it looks like this:
db.partnership.aggregate([
// $geoNear
{
$match: {
$or: [
{
fromUser: "rrr"
},
{
toUser: "rrr"
}
]
}
},
{
$group: {
_id: 0,
from: {
$addToSet: "$fromUser"
},
to: {
$addToSet: "$toUser"
}
}
},
{
$project: {
_id: 0,
users: {
$filter: {
input: {
$setIntersection: [
"$from",
"$to"
]
},
cond: {
$ne: [
"$$this",
"rrr"
]
}
}
}
}
},
{
$lookup: {
from: "users",
let: {
userId: "$users"
},
pipeline: [
{
$sort: {
calculatedDist: 1
}
},
{
"$match": {
"$expr": {
"$in": [
"$_id",
"$$userId"
]
}
}
}
],
as: "users"
}
},
{
$project: {
users: 1,
count: {
$size: "$users"
}
}
}
])
Playground.

Mongodb - Add geoNear to an existing query to sort by distance

There are 3 collections for this application
users - all user details
partnership - user relation to each other
location - user geolocation
I have 1 and 2 working. The 3rd task is to sort the resuling users list by distance from a given coordinate.
Here is the mongo playground:
https://mongoplayground.net/p/XELySm8KGpM
db={
users: [
{
_id: "abc",
name: "abc",
group: 1
},
{
_id: "xyz",
name: "xyyy",
group: 1
},
{
_id: "123",
name: "yyy",
group: 1
},
{
_id: "rrr",
name: "tttt",
group: 1
},
{
_id: "eee",
name: "uuu",
group: 1
}
],
partnership: [
{
_id: "abc_123",
fromUser: "abc",
toUser: "123"
},
{
_id: "eee_rrr",
fromUser: "eee",
toUser: "rrr"
},
{
_id: "rrr_abc",
fromUser: "rrr",
toUser: "abc"
},
{
_id: "abc_rrr",
fromUser: "abc",
toUser: "rrr"
},
{
_id: "xyz_rrr",
fromUser: "xyz",
toUser: "rrr"
},
{
_id: "rrr_eee",
fromUser: "rrr",
toUser: "eee"
},
],
locations: [
{
_id: "123",
location: {
type: "Point",
coordinates: [
54.23,
67.12
]
}
},
{
_id: "rrr",
location: {
type: "Point",
coordinates: [
51.23,
64.12
]
}
},
{
_id: "eee",
location: {
type: "Point",
coordinates: [
55.23,
62.12
]
}
},
{
_id: "abc",
location: {
type: "Point",
coordinates: [
53.23,
67.12
]
}
},
]
}
The following query by itself works. How to integrate it to the query in the playground?
{
$geoNear: {
near: { type: "Point", coordinates: [ 41.99279 , -81.719296 ] },
distanceField: "dist.calculated",
spherical: true
}
},
$geoNear can only be the first step in an aggregation pipeline, so you need to first sort by distance and then do all the other things. This means your schema is not very efficient for this.
One option is:
db.locations.aggregate([
{$geoNear: {
near: { type: "Point", coordinates: [ 41.99279 , -81.719296 ] },
distanceField: "calculatedDist",
spherical: true
}
},
{$lookup: {
from: "partnership",
let: {user_id: "$_id"},
pipeline: [
{$match: {$expr: {
$or: [
{$and: [{$eq: ["$fromUser", "rrr"]}, {$eq: ["$toUser", "$$user_id"]}]},
{$and: [{$eq: ["$toUser", "rrr"]}, {$eq: ["$fromUser",
"$$user_id"]}]},
]
}}}
],
as: "valid"
}
},
{$match: {"valid.0": {$exists: true}}},
{$lookup: {
from: "users",
localField: "_id",
foreignField: "_id",
as: "user"
}},
{$project: {user: {$first: "$user"}, calculatedDist: 1}},
{$sort: {calculatedDist: 1}},
{$group: {_id: 0, users: {$push: "$user"}, count: {$sum: 1}}}
])
See how it works on the playground example

MongoDB + BBOX + Point = inside

I have the following document in my MongoDB collection, which I would like to be able to do a query that check if point that is provided by the user is inside a bbox array that is stored in the collection.
{
"type": "Feature",
"properties": {
"place_id": 298104298,
"osm_type": "relation",
"osm_id": 80500,
"display_name": "Australia",
"place_rank": 4,
"category": "boundary",
"type": "administrative",
"importance": 0.8521350639151115,
"address": {
"country": "Australia",
"country_code": "au"
}
},
"bbox": [
72.2461932,
-55.3228175,
168.2261259,
-9.0880125
]
}
What I would like to do is a geoIntersect or geoWithin query.
for example:
[{
$match: {
bbox: {
$nearSphere: {
$geometry: {
type: 'Point',
coordinates: [
-73.9667,
40.78
]
}
}
}
}
}]
I have also tried
[{
$project: {
geometry: 0
}
}, {
$match: {
bbox: {
$geoWithin: {
$box: [
[
-73.9667,
40.78
],
[
40.78,
-73.9667
]
]
}
}
}
}]
However that did return results but wrong results the geo location should return NULL as the location is Antarctic Ice shield, Antarctica - (-73.9667,40.78)

How to do faceted search queries with MongoDB

I'm making a search page where people can search for other users and filter the results by various categories (gender, industry, occupation, etc) and I want to be able to show a count next to each facet, like you see on ebay and lots of ecommerce sites.
I've got it semi-working, but the problem is that I'm doing the facet counts after the filtering has already taken place, so if the filter includes industry = "science" (for example), then I don't get a count of all the other industries, I only get the count of the users whose industry is "science".
How can I make sure I get the counts of all the industries if the search query includes filtering by industry?
{
$geoNear: {
near: {
type: "Point",
coordinates: [parseFloat(longitude), parseFloat(latitude)],
},
distanceField: "dist.calculated",
maxDistance: Number((maxDistance || 1500) * 1609.34),
spherical: true,
query: filters,
},
},
{
$project: {
name: 1,
location: 1,
gender: 1,
username: 1,
dist: 1,
area: 1,
industry: 1,
},
},
{
$facet: {
paginatedResults: [
{ $skip: parseInt(skip) },
{ $limit: parseInt(limit) },
],
totalCount: [
{
$count: "count",
},
],
industry: [
{ $match: { industry: { $exists: 1 } } },
{
$sortByCount: "$industry",
},
],
gender: [
{ $match: { gender: { $exists: 1 } } },
{
$sortByCount: "$gender",
},
],
},
},
]);```