lookup with pipeline and geoIntersects that use let variable - mongodb

I am trying to lookup for neighborhoods which match my condition - the boundries polygon intersects with the post's coordinates but I am unable to do it - cant use the let in my pipeline $match
example of post entity:
{
_id: ObjectId,
...,
location: {
...,
coordinates: {
type: 'Point',
coordinates: [number, number]
}
}
};
example of neighborhood entity:
{
_id: ObjectId,
...,
boundries: {
type: 'Polygon',
coordinates: [ [ [number, number], [number, number], [number, number], [number, number], [number, number] ] ]
}
};
example of query I am trying to "fix":
db.posts.aggregate([
{ $match: { _id: ObjectId('5a562e62100338001218dffa') } },
{
$lookup: {
from: 'neighborhoods',
let: { postCoordinates: '$location.coordinates.coordinates' },
pipeline: [
{
$match: {
boundries: {
$geoIntersects: {
$geometry: {
type: 'Point',
coordinates: '$$postCoordinates'
}
}
}
}
}
],
as: 'neighborhoods'
}
}
]);

Unfortunately coordinates can't be populated from document field.
Geospatial are query expressions and $let variables are only permitted to use in $match with $expr variant for aggregation expressions in $lookup pipeline.
You have to perform the query in two steps.
First step to get the coordinates for matching record.
var result = db.posts.findOne({ _id: ObjectId('5a562e62100338001218dffa')},{ _id: 0, 'location':1});
Second step to look for point in the polygon.
db.neighborhoods.find(
{
"boundries": {
$geoIntersects: {
$geometry: {
type: "Point" ,
coordinates: result.location.coordinates.coordinates
}
}
}
}
)

Related

How do I reverse the coordinates with mongodb?

I have these coordinates in the wrong order I need to reverse them for all collections:
{
_id: ObjectId("638f9866d9014fabfc47275e"),
address: '801 Pine St, Anchorage, AK',
name: 'Russian Jack Skatepark',
location: { type: 'Point', coordinates: [ 61.214855, -149.793563 ] }
}
You can use $reverseArray as follow:
db.collection.updateMany({},
[
{
$set: {
"location.coordinates": {
$reverseArray: "$location.coordinates"
}
}
}
])
Playgrouund

How do I convert all geo locations in mongo db from strings to floats?

skatespot-dev> db.parks.findOne();
{
_id: ObjectId("638f9866d9014fabfc47275e"),
address: '801 Pine St, Anchorage, AK',
name: 'Russian Jack Skatepark',
location: { type: 'Point', coordinates: [ '61.214855', '-149.793563' ] }
}
I need to run a query to convert all coodinates to floats i guess.
You can convert them to double using update with pipeline, like this:
db.collection.updateMany({},
[
{
$set: {
"location.coordinates": [
{
$toDouble: {
$first: "$location.coordinates"
}
},
{
$toDouble: {
$last: "$location.coordinates"
}
}
]
}
}
])
See how it works on the playground example

Get only matched array object along with parent fields

I also checked the following question and tried various other things but
couldn't get it working
Retrieve only the queried element in an object array in MongoDB collection
I have the following document sample
{
_id: ObjectId("634b08f7eb5cb6af473e3ab2"),
name: 'India',
iso_code: 'IN',
states: [
{
name: 'Karnataka',
cities: [
{
name: 'Hubli Tabibland',
pincode: 580020,
location: { type: 'point', coordinates: [Array] }
},
{
name: 'Hubli Vinobanagar',
pincode: 580020,
location: { type: 'point', coordinates: [Array] }
},
{
name: 'Hubli Bengeri',
pincode: 580023,
location: { type: 'point', coordinates: [Array] }
},
{
name: 'Kusugal',
pincode: 580023,
location: { type: 'point', coordinates: [Array] }
}
]
}
]
}
I need only the following
{
_id: ObjectId("634b08f7eb5cb6af473e3ab2"),
name: 'India',
iso_code: 'IN',
states: [
{
name: 'Karnataka',
cities: [
{
name: 'Kusugal',
pincode: 580023,
location: { type: 'point', coordinates: [Array] }
}
]
}
]
}
Following is the query that I have tried so far but it returns all the cities
db.countries.find(
{
'states.cities': {
$elemMatch: {
'name' : 'Kusugal'
}
}
},
{
'_id': 1,
'name': 1,
'states.name': 1,
'states.cities.$' : 1
}
);
I was able to achieve it with the help of aggregation.
db.countries.aggregate([
{ $match: { "states.cities.name": /Kusugal/ } },
{ $unwind: "$states" },
{ $unwind: "$states.cities" },
{ $match: { "states.cities.name": /Kusugal/ } }
]);
1st line $match will query the records with cities with only Kusugal
2nd & 3rd line $unwind will create a separate specific collection of documents from the filtered records
3rd line $match will filter these records again based on the condition
In simple aggregation processes commands and sends to next command and returns as an single result.

Deleting an item with condition in MongoDB?

I want to remove a product from the Cart by checking its quantity. Its quantity should be decremented by 1 unless it reaches zero, and after that, it should pull out from the product array of the Cart.
here is my Logic : (I want to perform the pull and decrement operation inside the single query. But I m stuck on how to perform these two operations together by a simple condition in MongoDb)
const cart = await Cart.findOneAndUpdate({"products.productId": req.body.productId}, {$inc: {"products.$.quantity": -1}}, {new: true})
await Cart.update({"products.productId": req.body.productId}, {$pull: {quantity: 0}})
here is the model for clarification:
import mongoose from 'mongoose';
const cartSchema = new mongoose.Schema({
userId: {
type: String,
required: true,
},
products: [
{
productId: {
type: String,
},
quantity: {
type: Number,
default: 1
}
}
]
}, {timestamps: true});
const Cart = new mongoose.model('Cart', cartSchema);
export default Cart;
Thanks :)
There is no straight way to do this in single regular update query.
To improve your approach you can try this,
first query to check productId and quantity should greater than 1
const cart = await Cart.updateOne(
{
products: {
$elemMatch: {
productId: req.body.productId,
quantity: { $gt: 1 }
}
}
},
{ $inc: { "products.$.quantity": -1 } }
);
Playground
second query if the first query's result is nModified is 0 then pull the product, by checking condition productId and quantity equal-to 1
if (cart.nModified === 0) {
await Cart.updateOne(
{
products: {
$elemMatch: {
productId: req.body.productId,
quantity: { $eq: 1 }
}
}
},
{ $pull: { products: { productId: req.body.productId } } }
)
}
Playground
If you really want to do using single query you can try update with aggregation pipeline starting from MongoDB 4.2,
$map to iterate loop of products array and check condition, if the productId matches then increment/decrement quantity by $add operator otherwise return current quantity
$filter to iterate loop of above result and check condition if productId and quantity is not zero
await Cart.updateOne(
{ "products.productId": req.body.productId },
[{
$set: {
products: {
$filter: {
input: {
$map: {
input: "$products",
in: {
productId: "$$this.productId",
quantity: {
$cond: [
{ $eq: ["$$this.productId", req.body.productId] },
{ $add: ["$$this.quantity", -1] },
"$$this.quantity"
]
}
}
}
},
cond: {
$and: [
{ $eq: ["$$this.productId", req.body.productId] },
{ $ne: ["$$this.quantity", 0] }
]
}
}
}
}
}
])
Playground

In MongoDB, how do I perform a $geoWithin query as part of an aggregation pipeline that utilizes $lookup?

I'm trying to use an aggregation pipeline to determine whether a specific territory has specific territories that match $geoWithin. Each territory has a geojson geometry object that is indexed appropriately. I'm not sure I am using let properly in this pipeline or if there is a way to coerce MongoDB into recognizing the parameters as appropriate geojson. I get the following error with the pipeline below:
unknown GeoJSON type: { type: "$$type", coordinates: "$$coords" }
const findTerritoriesWithin = (_id: string) => [
{
'$match': {
'_id': new ObjectId(_id)
}
}, {
'$lookup': {
'from': 'territories',
'let': {
'type': '$geometry.type',
'coords': '$geometry.coordinates'
},
'pipeline': [
{
'$match': {
'geometry': {
'$geoWithin': {
'$geometry': {
'type': '$$type',
'coordinates': '$$coords'
}
}
}
}
}
],
'as': 'matchingTerritories'
}
}
];
const collection = db.collection('territories');
const results = await collection.aggregate(findTerritoriesWithin('5ef2408033a243ced1f02d1e'))
I also tried
{
from: 'territories',
let: {
geometry: '$geometry'
},
pipeline: [
{
$match: {
'geometry': {
$geoWithin: {
$geometry: '$$geometry'
}
}
}
}
],
as: 'matching_territories'
}
but that yields: unknown geo specifier: $geometry: "$$geometry"
MongoDB version: 4.2.2.
Thanks in advance.