У меня есть коллекция Item
, которая может содержать от тысячи до сотен тысяч документов.В этой коллекции я хочу выполнить геопространственные запросы.Используя Mongoose, есть два варианта - find()
и конвейер агрегации.Я показал мои реализации обоих ниже:
Модель Mongoose
Для начала, вот соответствующие свойства моей модели Mongoose:
// Define the schema
const itemSchema = new mongoose.Schema({
// Firebase UID (in addition to the Mongo ObjectID)
owner: {
type: String,
required: true,
ref: 'User'
},
// ... Some more fields
numberOfViews: {
type: Number,
required: true,
default: 0
},
numberOfLikes: {
type: Number,
required: true,
default: 0
},
location: {
type: {
type: 'String',
default: 'Point',
required: true
},
coordinates: {
type: [Number],
required: true,
},
}
}, {
timestamps: true
});
// 2dsphere index
itemSchema.index({ "location": "2dsphere" });
// Create the model
const Item = mongoose.model('Item', itemSchema);
Поиск запроса
// These variables are populated based on URL Query Parameters.
const match = {};
const sort = {};
// Query to make.
const query = {
location: {
$near: {
$maxDistance: parseInt(req.query.maxDistance),
$geometry: {
type: 'Point',
coordinates: [parseInt(req.query.lng), parseInt(req.query.lat)]
}
}
},
...match
};
// Pagination and Sorting
const options = {
limit: parseInt(req.query.limit),
skip: parseInt(req.query.skip),
sort
};
const items = await Item.find(query, undefined, options).lean().exec();
res.send(items);
Трубопровод агрегации
Предположим, что необходимо рассчитать расстояние:
// These variables are populated based on URL Query Parameters.
const query = {};
const sort = {};
const geoSpatialQuery = {
$geoNear: {
near: {
type: 'Point',
coordinates: [parseInt(req.query.lng), parseInt(req.query.lat)]
},
distanceField: "distance",
maxDistance: parseInt(req.query.maxDistance),
query,
spherical: true
}
};
const items = await Item.aggregate([
geoSpatialQuery,
{ $limit: parseInt(req.query.limit) },
{ $skip: parseInt(req.query.skip) },
{ $sort: { distance: -1, ...sort } }
]).exec();
res.send(items);
Правка - Пример Документально Исправлено
Вот пример документа со всемиего свойств из коллекции Item
:
{
"_id":"5cd08927c19d1dd118d39a2b",
"imagePaths":{
"standard":{
"images":[
"users/zbYmcwsGhcU3LwROLWa4eC0RRgG3/5cd08927c19d1dd118d39a2b/images/Image-aafe69c7-f93e-411e-b75d-319042068921-standard.jpg",
"users/zbYmcwsGhcU3LwROLWa4eC0RRgG3/5cd08927c19d1dd118d39a2b/images/Image-397c95c6-fb10-4005-b511-692f991341fb-standard.jpg",
"users/zbYmcwsGhcU3LwROLWa4eC0RRgG3/5cd08927c19d1dd118d39a2b/images/Image-e54db72e-7613-433d-8d9b-8d2347440204-standard.jpg",
"users/zbYmcwsGhcU3LwROLWa4eC0RRgG3/5cd08927c19d1dd118d39a2b/images/Image-c767f54f-7d1e-4737-b0e7-c02ee5d8f1cf-standard.jpg"
],
"profile":"users/zbYmcwsGhcU3LwROLWa4eC0RRgG3/5cd08927c19d1dd118d39a2b/images/Image-51318c32-38dc-44ac-aac3-c8cc46698cfa-standard-profile.jpg"
},
"thumbnail":"users/zbYmcwsGhcU3LwROLWa4eC0RRgG3/5cd08927c19d1dd118d39a2b/images/Image-51318c32-38dc-44ac-aac3-c8cc46698cfa-thumbnail.jpg",
"medium":"users/zbYmcwsGhcU3LwROLWa4eC0RRgG3/5cd08927c19d1dd118d39a2b/images/Image-51318c32-38dc-44ac-aac3-c8cc46698cfa-medium.jpg"
},
"location":{
"type":"Point",
"coordinates":[
-110.8571443,
35.4586858
]
},
"numberOfViews":0,
"numberOfLikes":0,
"monetarySellingAmount":9000,
"exchangeCategories":[
"Math"
],
"itemCategories":[
"Sports"
],
"title":"My title",
"itemDescription":"A description",
"exchangeRadius":10,
"owner":"zbYmcwsGhcU3LwROLWa4eC0RRgG3",
"reports":[],
"createdAt":"2019-05-06T19:21:13.217Z",
"updatedAt":"2019-05-06T19:21:13.217Z",
"__v":0
}
Вопросы
Исходя из вышеизложенного, я хотел задать несколько вопросов.
Есть ли разница в производительности между моими реализациями обычного запроса Mongoose и использованием конвейера агрегации?
Правильно ли сказатьчто near
и geoNear
очень похожи на nearSphere
при использовании индекса 2dsphere
с GeoJSON - разве что geoNear
предоставляет дополнительные данные и ограничение по умолчанию?То есть, несмотря на разные единицы измерения, оба запроса - концептуально - будут показывать соответствующие данные в пределах определенного радиуса от некоторого местоположения, несмотря на то, что поле называется radius
для nearSphere
и maxDistance
сnear
/ geoNear
.
В моем примере, приведенном выше, как можно снизить потерю производительности при использовании skip
, но при этом можно добиться разбиения на страницы как в запросах, так и в агрегации?
Функция find()
позволяет необязательный параметр определять, какие поля будут возвращены.Агрегационный конвейер занимает этап $project
, чтобы сделать то же самое.Есть ли определенный порядок, в котором $project
должен использоваться в конвейере для оптимизации скорости / эффективности, или это не имеет значения?
Я надеюсь, что этот стиль вопроса разрешен согласноПравила переполнения стека.Спасибо.