How to mongo aggregate with lookup and geoIntersects - mongodb

I want to lookup which users are in a specific area, so, if i have these objects on users collection:
{
"_id" : ObjectId("6197a78308591026b742cbc7"),
"longitude" : -8.88180350512795,
"latitude" : 38.5628716186268
}
/* 2 */
{
"_id" : ObjectId("6199798916317b0c2dcab874"),
"longitude" : -9.15904389999993,
"latitude" : 38.7235087
}
/* 3 */
{
"_id" : ObjectId("6199798916317b0c2dcab874"),
"longitude" : -8.6923178,
"latitude" : 41.1846394
}
And i have other collection deliveryareas with this object:
{
"_id" : ObjectId("6197ebf6fdbd8f6b06c97c57"),
"area" : {
"type" : "Polygon",
"coordinates" : [
[
[
-9.0556241,
38.4989521
],
[
-9.0551516,
38.4964531
],
[
-9.0522346,
38.4950589
],
[
-9.0526648,
38.4940872
]
]
],
"_id" : ObjectId("6197ebf6fdbd8f6b06c97c58")
}
}
Making this query:
db.getCollection('users').aggregate([
{
$lookup: {
from: 'deliveryareas',
let: { longitude: '$longitude', latitude: '$latitude' },
pipeline: [
{ $match: {
area: {
$geoIntersects: {
$geometry: {
type: 'Point',
coordinates: [ '$$longitude', '$$latitude' ],
},
},
}
} },
],
as: 'inRegion',
},
},
])
if i run the query directly in the deliveryareas collection it works, but over the pipeline on lookup don't work and it's giving the error "Point must only contain numeric elements", can anyone tell me why?

Just one more thing, if i run first this:
db.getCollection('users').aggregate([
{ $unwind: '$addresses' },
{ $project: { coordinates: [ '$addresses.region.longitude', '$addresses.region.latitude' ] } },
])
I have this results:
/* 1 */
{
"_id" : ObjectId("6197a78308591026b742cbc7"),
"coordinates" : [
-8.88180350512795,
38.5628716186268
]
}
/* 2 */
{
"_id" : ObjectId("6199798916317b0c2dcab874"),
"coordinates" : [
-9.15904389999993,
38.7235087
]
}
/* 3 */
{
"_id" : ObjectId("6199798916317b0c2dcab874"),
"coordinates" : [
-8.6923178,
41.1846394
]
}
Then if i add the lookup:
db.getCollection('users').aggregate([
{ $unwind: '$addresses' },
{ $project: { coordinates: [ '$addresses.region.longitude', '$addresses.region.latitude' ] } },
{
$lookup: {
from: 'deliveryareas',
let: { userCoordinates: '$coordinates' },
pipeline: [
{ $match: {
area: {
$geoIntersects: {
$geometry: {
type: 'Point',
coordinates: '$$userCoordinates',
},
},
}
} },
],
as: 'inRegion',
},
},
])
i have the error: Point must be an array or object
but if i replace the variable $$userCoordinates by a fixed value, for example [ -8.88180350512795, 38.5628716186268 ] (that i have on the users) the query run with success.

Related

Dynamic fields array in to single object mongo db

I am having mongo collection like below,
{
"_id" : ObjectId("62aeb8301ed12a14a8873df1"),
"Fields" : [
{
"FieldId" : "e8efd0b0-9d10-4584-bb11-5b24f189c03b",
"Value" : [
"test_123"
]
},
{
"FieldId" : "fa6745c2-b259-4a3b-8c6f-19eb78fbbbf5",
"Value" : [
"123"
]
},
{
"FieldId" : "2a1be5d0-8fb6-4b06-a253-55337bfe4bcd",
"Value" : []
},
{
"FieldId" : "eed12747-0923-4290-b09c-5a05107f5609",
"Value" : [
"234234234"
]
},
{
"FieldId" : "fe41d8fb-fa18-4fe5-b047-854403aa4d84",
"Value" : [
"Irrelevan"
]
},
{
"FieldId" : "93e46476-bf2e-44eb-ac73-134403220e9e",
"Value" : [
"test"
]
},
{
"FieldId" : "db434aca-8df3-4caf-bdd7-3ec23252c2c8",
"Value" : [
"2019-06-16T18:30:00.000Z"
]
},
{
"FieldId" : "00df903f-5d59-41c1-a3df-60eeafb77d10",
"Value" : [
"tewt"
]
},
{
"FieldId" : "e97d0386-cd42-6277-1207-e674c3268cec",
"Value" : [
"1"
]
},
{
"FieldId" : "35e55d27-7d2c-467d-8a88-09ad6c9f5631",
"Value" : [
"10"
]
}
]
}
This is all dynamic form fields.
So I want to query and get result like to below object,
{
"_id" : ObjectId("62aeb8301ed12a14a8873df1"),
"e8efd0b0-9d10-4584-bb11-5b24f189c03b": ["test_123"],
"fa6745c2-b259-4a3b-8c6f-19eb78fbbbf5": ["123"],
"2a1be5d0-8fb6-4b06-a253-55337bfe4bcd": [],
"eed12747-0923-4290-b09c-5a05107f5609": ["234234234"],
"fe41d8fb-fa18-4fe5-b047-854403aa4d84": ["Irrelevan"],
"93e46476-bf2e-44eb-ac73-134403220e9e":["test"],
"db434aca-8df3-4caf-bdd7-3ec23252c2c8":["2019-06-16T18:30:00.000Z"],
"00df903f-5d59-41c1-a3df-60eeafb77d10":["1"]
}
I want final output like this combination of fields Fields.FieldID should be key and Fields.Value should be value here.
Please try to help to me to form the object like above.
Thanks in advance!
You can restructure your objects using $arrayToObject, then using that value to as a new root $replaceRoot like so:
db.collection.aggregate([
{
$match: {
// your query here
}
},
{
$project: {
newRoot: {
"$arrayToObject": {
$map: {
input: "$Fields",
in: {
k: "$$this.FieldId",
v: "$$this.Value"
}
}
}
}
}
},
{
"$replaceRoot": {
"newRoot": {
"$mergeObjects": [
"$newRoot",
{
_id: "$_id"
}
]
}
}
}
])
Mongo Playground
I try this and get result like you want
db.collection.aggregate([{
$replaceWith: {
$mergeObjects: [
{
_id: "$_id"
},
{
$arrayToObject: { $zip: {inputs: ["$Fields.FieldId","$Fields.Value"]}}
}
]
}
}])
playground

MongoDB: Matching points from one collection with polygons from another

I'm trying to match points in one collection with regions stored in another collection.
Here are examples of documents.
Points:
{
"_id" : ObjectId("5e36d904618c0ea59f1eb04f"),
"gps" : { "lat" : 50.073288, "lon" : 14.43979 },
"timeAdded" : ISODate("2020-02-02T15:13:22.096Z")
}
Regions:
{
"_id" : ObjectId("5e49a469afae4a11c4ff3cf7"),
"type" : "Feature",
"geometry" : {
"type" : "Polygon",
"coordinates" : [
[
[ -748397.88, -1049211.61 ],
[ -748402.77, -1049212.2 ],
...
[ -748410.41, -1049213.11 ],
[ -748403.05, -1049070.62 ]
]
]
},
"properties" : {
"Name" : "Region 1"
}
}
And the query I'm trying to construct is something like this:
db.points.aggregate([
{$project: {
coordinates: ["$gps.lon", "$gps.lat"]
}},
{$lookup: {
from: "regions", pipeline: [
{$match: {
coordinates: {
$geoWithin: {
$geometry: {
type: "Polygon",
coordinates: "$geometry.coordinates"
}
}
}
}}
],
as: "district"
}}
])
I'm getting an error:
assert: command failed: {
"ok" : 0,
"errmsg" : "Polygon coordinates must be an array",
"code" : 2,
"codeName" : "BadValue"
} : aggregate failed
I've noticed the structure of $geoWithin document is same as structure of one I have for each region. So I tried such query:
db.points.aggregate([
{$project: {
coordinates: ["$gps.lon", "$gps.lat"]
}},
{$lookup: {
from: "regions", pipeline: [
{$match: {
coordinates: {
$geoWithin: "$geometry.coordinates"
}
}}
],
as: "district"
}}
])
The error was same.
I looked up for geoqueries but surprisingly all found mentions had static region document instead of one taken from a collection. So I'm wondering - is it ever possible to map points with regions having that both document collections aren't static and taken from DB?
Unfortunately not possible
You could perform query below if $geometry could deal with MongoDB Aggregation Expressions.
db.points.aggregate([
{
$lookup: {
from: "regions",
let: {
coordinates: [
"$gps.lon",
"$gps.lat"
]
},
pipeline: [
{
$addFields: {
coordinates: "$$coordinates"
}
},
{
$match: {
coordinates: {
$geoWithin: {
$geometry: {
type: "Polygon",
coordinates: "$geometry.coordinates"
}
}
}
}
}
],
as: "district"
}
}
])

How to use $geoNear after $lookup

Hi i have the following problem:
I would like to use $geoNear (to count distance between two points) but after $loopback (and on collection that i joined).
This is the model for companyBases collection (i would like to join it):
{
"_id" : ObjectId("5d7cfe13f42e7345d967b378"),
"location" : {
"type" : "Point",
"coordinates" : [
20.633856,
49.761268
]
},
"vehicles" : [
{
"_id" : ObjectId("5d7cfe13f42e7345d967b340"),
...other fields that doesn't matter
}
]
}
This is vehicle collection:
{
"_id" : ObjectId("5d7cfe13f42e7345d967b340"),
...other fields that doesn't matter
}
I would like to join companyBase collection in aggregation on vehicles collection:
db.vehicles.aggregate([
{
$lookup: {
from: "companybases",
let: {
vehicleId: "$_id"
},
pipeline: [
{
$match: {
$expr: { $in: ["$$vehicleId", "$vehicles._id"] }
}
}
],
as: "companyBases"
}
},
{
$unwind: "$companyBases"
},
{
$geoNear: {
near: {
type: "Point",
coordinates: [50.02485, 20.0008]
},
distanceField: "distance",
spherical: true
}
}
]);
But it returns me:
{
"message" : "$geoNear is only valid as the first stage in a pipeline.",
"operationTime" : "Timestamp(1568472833, 1)",
"ok" : 0,
"code" : 40602,
"codeName" : "Location40602",
"$clusterTime" : {
"clusterTime" : "Timestamp(1568472833, 1)",
"signature" : {
"hash" : "AAAAAAAAAAAAAAAAAAAAAAAAAAA=",
"keyId" : "0"
}
},
"name" : "MongoError"
}
When i am doing the same pipeline on companybases collection it returns me documents with counted distance:
db.companybases.aggregate([
{
$geoNear: {
near: {
type: "Point",
coordinates: [50.02485, 20.0008]
},
distanceField: "distance",
spherical: true
}
}
]);
And result:
{
"_id" : ObjectId("5d7cfe13f42e7345d967b378"),
"location" : {
"type" : "Point",
"coordinates" : [
20.633856,
49.761268
]
},
"vehicles" : [
{
...some fields
},
],
...some fields
"distance" : 4209673.447019393
}
I realize that the error may be because of missing index on vehicles collection. So is there any way to calculate distance with $geoNear with $lookup ? Or maybe it's impossible and i have to do on my own ?
Simple solutions (you can put $geoNear in $lookup pipeline):
db.vehicles.aggregate([
{
$lookup: {
from: "companybases",
let: {
vehicleId: "$_id"
},
pipeline: [
{
$geoNear: {
near: {
type: "Point",
coordinates: [50.02485, 20.0008]
},
distanceField: "distance",
spherical: true
}
},
{
$match: {
$expr: { $in: ["$$vehicleId", "$vehicles._id"] }
}
}
],
as: "companyBases"
}
},
{
$unwind: "$companyBases"
}
]);
But that strongly impressed the performance (it takes at least 5 seconds), becuase $geoNear is used before match.
your error shows "message" : "$geoNear is only valid as the first stage in a pipeline.",
it is clear that you can't put '$geoNear' after $lookup
so You can only use $geoNear as the first stage of a pipeline.
just to exchange you agg order
use $geoNear at first then with $lookup

Conditional lookup for nested localfield

I want to get user's friends and their information. But only wanna join for users where "status" equals to 2.
Here is my object
{
"user_id" : ObjectId("5d2f574b1c27807fd7eb133d"),
"relationship" : [
{
"user_id" : ObjectId("5d2f57661c27807fd7eb133e"),
"status" : 2
},
{
"user_id" : ObjectId("5d2f57c01c27807fd7eb133f"),
"status" : 1
}
]
}
My query
db.getCollection('relationships').aggregate([
{$match: {
user_id: ObjectId("5d2f574b1c27807fd7eb133d")}
},
{ $lookup: {
from: "users",
as: "users",
let: { id: "$_id" },
pipeline: [
{ $match: {
$expr: { $and: [
{ $eq: [ "$relationship.user_id", "$$id" ] },
{ $eq: [ "$relationship.status", 2 ] }
] }
} }
],
} }
])
expected output
{
"user_id" : ObjectId("5d2f574b1c27807fd7eb133d"),
"friends" : [
{
"_id" : ObjectId("5d2f57661c27807fd7eb133e"),
"name" : "Mike"
}
]
}
What's the proper way to do this ?

How to reduce to single document in finall stage of mongodb aggregation?

Document -
Document consists of Population data, Literate data.
{
"_id" : NumberInt(0),
"area" : "India",
"population" : {
"density" : NumberInt(382),
"class" : [
{
"rural" : [
{
"male" : [
NumberInt(61285192),
NumberInt(427917052)
]
},
{
"female" : [
NumberInt(56300322),
NumberInt(405170610)
]
}
]
},
{
"urban" : [
{
"male" : [
NumberInt(21666943),
NumberInt(195807196)
]
},
{
"female" : [
NumberInt(19536830),
NumberInt(181298564)
]
}
]
}
]
},
"education" : {
"class" : [
{
"rural" : [
{
"male" : NumberInt(288047480)
},
{
"female" : NumberInt(204973398)
}
]
},
{
"urban" : [
{
"male" : NumberInt(204973398)
},
{
"female" : NumberInt(129276960)
}
]
}
]
}
}
Aggregation Query -
Trying to figure out the nodejs query which will help to create single document (JSON) containing information about id, area, density, male data and female data.
Below query is generating two documents. How to merge two documents into single document
router.get('/population/:id', function (req, res, next) {
var id = Number(req.params.id);
sCollection.aggregate(
{
$match: { _id: id }
},
{
$unwind: '$population.class'
},
{
$unwind: '$population.class.rural'
},
{
$project: { _id: '$_id', area: '$area', density: '$population.density', ruralMale: '$population.class.rural.male', ruralFemale: '$population.class.rural.female' }
},
).toArray(function (err, population) {
if (err) {
res.send('Error census-population not found ' + err);
} else {
res.json(population);
}
});
});
Attached image will help in better understanding of the question
you need to use $group like this:
[
{$match: { _id: 0 }},
{$unwind: '$population.class'},
{$unwind: '$population.class.rural'},
{$group:{_id:"$_id", area: {$first: "$area"}, density: {$first:'$population.density'}, ruralMale: {$first: '$population.class.rural.male'}, ruralFemale: {$last:'$population.class.rural.female' }}}
]