MongoDB geoNear on Group Results/ Distinct field of Latest records

89 Views Asked by At

Following are the fields with some sample data of my collection for geolocation of all the users on every 30 seconds.

Data Collection Documents

[{
  "_id": id1,
  "purpose": "tracking",
  "user": User1,
  "geoLocation": {
    "type": "Point",
    "coordinates": [73.11892896428128, 22.31833064059432]
  },
  "createdAt": ISODate("2023-05-08T10:08:15.049Z"),
  "updatedAt": ISODate("2023-05-08T10:08:15.049Z")
},
{
  "_id": id2,
  "purpose": "tracking",
  "user": User1,
  "geoLocation": {
    "type": "Point",
    "coordinates": [73.09304942391326, 22.321831847174654]
  },
  "createdAt": ISODate("2023-05-08T10:09:43.828Z"),
  "updatedAt": ISODate("2023-05-08T10:09:43.828Z")
},
{
  "_id": id3,
  "purpose": "tracking",
  "user": User2,
  "geoLocation": {
    "type": "Point",
    "coordinates": [73.14348330367717, 22.280082608116277]
  },
  "createdAt": ISODate("2023-05-08T10:12:30.670Z"),
  "updatedAt": ISODate("2023-05-08T10:12:30.670Z")
},
{
  "_id": id4,
  "purpose": "tracking",
  "user": User2,
  "geoLocation": {
    "type": "Point",
    "coordinates": [73.1542041657593, 22.27487612032671]
  },
  "createdAt": ISODate("2023-05-08T10:13:10.224Z"),
  "updatedAt": ISODate("2023-05-08T10:13:10.224Z")
}
]

As per the above data there are 2 users with 2 locations with different timinigs(Old, New) Now, I am trying to fetch the records which are near to specific point with users latest locations(New) and ignore the old or previous records of the same users. Here is the query which I am using. Please Check Data Query Section

Now it is returning the following 2 objects. Please check the Current Result Section

But the result should be like following. Please check the Expected Result Section

Now following is the distance of each location from the DB Origin Coordinates Distance Data

id1 - 3123
id2 - 5864
id3 - 3528
id4 - 2267

But as per the current result, Query is fetching the result of id1 for the User1 because it matches the maxDistance option of geoNear but the result should be fetched of id2 as it is the latest result. It is showing fine for User2 because both the location satisfies the maxDistance and latest result is being retrieved.

I have already checked the related questions for this almost similar issue but the issue was not checked with the same query. I am attaching related question as well.

MongoDB geoNear with Distinct and Sorted values

Can anyone guide me what should be the solution in this case? I hope my question is quite clear for this issue.

Thanks in advance and Any help is really appreciated.

Data Query

DBModel.aggregate([
      {
        $geoNear: {
          near: { type: "Point", coordinates: [73.1528821581882, 22.29557552397352] },
          key: "geoLocation",
          distanceField: "distanceCalculated",
          minDistance: 0,
          maxDistance: 3000,
          query: { purpose: "tracking" },
          spherical: true,
        },
      },
      {
        $sort: {
          user: 1,
          createdAt: -1,
        },
      },
      {
        $group: {
          _id: "$user",
          geoLocation: {
            $first: "$geoLocation",
          },
          createdAt: {
            $first: "$createdAt",
          },
        },
      },
    ])

(Current Result)

[{
  "_id": id1,
  "purpose": "tracking",
  "user": User1,
  "geoLocation": {
    "type": "Point",
    "coordinates": [73.11892896428128, 22.31833064059432]
  },
  "createdAt": ISODate("2023-05-08T10:08:15.049Z"),
  "updatedAt": ISODate("2023-05-08T10:08:15.049Z"),
  "distanceCalculated": 3123 
},
{
  "_id": id4,
  "purpose": "tracking",
  "user": User2,
  "geoLocation": {
    "type": "Point",
    "coordinates": [73.1542041657593, 22.27487612032671]
  },
  "createdAt": ISODate("2023-05-08T10:13:10.224Z"),
  "updatedAt": ISODate("2023-05-08T10:13:10.224Z"),
  "distanceCalculated": 2267 
}
]

Expected Result

[
{
  "_id": id4,
  "purpose": "tracking",
  "user": User2,
  "geoLocation": {
    "type": "Point",
    "coordinates": [73.1542041657593, 22.27487612032671]
  },
  "createdAt": ISODate("2023-05-08T10:13:10.224Z"),
  "updatedAt": ISODate("2023-05-08T10:13:10.224Z"),
  "distanceCalculated": 2267 
}
]
1

There are 1 best solutions below

1
nimrod serok On

If you want to get only the users that their latest location is in a specific range from a point, you have several ways of doing it, which you can choose from according to your data. Ideally, you would group by user and keep only the latest and then run $geonear for this users, but this can't be done, as $geonear can only be the first step of the aggregation. Two other options are:

  1. Run $geonear without the maxDistance limit, but keep the distance on the document, then group and keep only users where their last location document is according to the limit. This will work on a small data set, or if most of your documents are in range anyway, but for a large data set, where only a small portion of the documents are in range you can do:
  2. Run $geonear with the maxDistance limit, so you'll keep only a small portion of your documents. Then group by user and keep only the latest (from the documents in range). Next use $lookup with pipeline to find for each user in range if they have another, more updated, document that should filter them out.

In both cases you should probably use the query part of $geonear to filter only documents with relevant updatedAt, for example, last 24 hours...

The second option will look like:

DBModel.aggregate([
  {$geoNear: {
      near: { type: "Point", coordinates: [73.1528821581882, 22.29557552397352] },
      key: "geoLocation",
      distanceField: "distanceCalculated",
      maxDistance: range,
      query: { purpose: "tracking", updatedAt: {$gt: ISODate("2023-05-08T00:00:00.000Z")}},
          spherical: true,
  }},
  {$sort: {user: 1, createdAt: -1}},
  {$group: {_id: "$user", data: {$first: '$$ROOT'}}},
  {$lookup: {
      from: 'userLocations',
      localField: '_id',
      foreignField: 'user',
      pipeline: [{$sort: {updatedAt: -1}}, {$limit: 1},{$project: {_id: 1}}],
          as: 'newest'
  }},
  {$match: {$expr: {$eq: ['$data._id', {$first: '$newest._id'}]}}},
  {$replaceRoot: {newRoot: '$data'}}
])