我们收集如下。每个文档代表一个驱动程序的行程,loc属性包含way-points,time属性包含对应于way-points的时间。例如,在旅行A中,驱动程序将在tripA.loc.coordinates[0]
tripA.time[0]
{
tripId : "Trip A",
time : [
"2015-03-08T04:47:43.589Z",
"2015-03-08T04:48:43.589Z",
"2015-03-08T04:49:43.589Z",
"2015-03-08T04:50:43.589Z",
],
loc: {
type: "MultiPoint",
coordinates: [
[ -73.9580, 40.8003 ],
[ -73.9498, 40.7968 ],
[ -73.9737, 40.7648 ],
[ -73.9814, 40.7681 ]
]
}
}
{
tripId : "Trip B",
time : [
"2015-03-08T04:47:43.589Z",
"2015-03-08T04:48:43.589Z",
"2015-03-08T04:49:43.589Z",
"2015-03-08T04:50:43.589Z",
],
loc: {
type: "MultiPoint",
coordinates: [
[ -72.9580, 41.8003 ],
[ -72.9498, 41.7968 ],
[ -72.9737, 41.7648 ],
[ -72.9814, 41.7681 ]
]
}
}
我们想查询从(1km)位置附近开始的旅行" [long1,lat1]"在时间t(+ -10分钟)附近,以[long2,lat2]结束。
是否有简单有效的方法为MongoDB或Elasticsearch制定上述查询?
如果可以,请提供查询。在MongoDB或Elasticsearch中。 (MongoDB更喜欢)
答案 0 :(得分:1)
这确实是从评论开始的,但显然已经很久了。因此,这是对限制和方法的长期解释。
你要求在这里实现的底线实际上是一个"联合查询"通常定义为两个单独的查询,其中最终结果是" set intersection"每个结果。更简单的说,所选择的"旅行"来自你的"起源"查询匹配您的"目的地中的结果"查询。
在一般的数据库术语中,我们指的是" union"作为"加入"或至少选择一套标准"和"选择另一个必须满足共同的基本分组标识符。
MongoDB中的基点我认为也适用于弹性搜索索引,因为数据存储机制都不支持" join"以任何方式来自直接的单一查询。
此处有另一个MongoDB原则,考虑到您的建议或现有建模,即使使用" array"中指定的项目也是如此。条款,没有办法实现"和"在坐标上进行地理空间搜索的条件,也考虑到您选择建模为GeoJSON" MultiPoint"查询不能选择"该对象的哪个元素匹配"最近的"至。因此"所有点"在考虑"最接近的匹配"时会考虑。
你的意图很清楚。所以我们可以看到"起源"在两个数组"两个数组"在你的文档结构中作为"第一个"每个数组中的元素。代表性数据是"位置"和"时间"对于每个进步的航点#34;在"旅行"。自然地以你的"目的地结束"在每个数组的结尾元素处,当然考虑数据点是"配对"。
我认为逻辑是认为这是存储事物的好方法,但它不遵循您在此处提到的任何存储解决方案的允许查询模式。
正如我已经提到的,这确实是一个"联盟"在意图中,当我看到导致设计的想法时,最好存储这样的东西:
{
"tripId" : "Trip A",
"time" : ISODate("2015-03-08T04:47:43.589Z"),
"loc": {
"type": "Point",
"coordinates": [ -73.9580, 40.8003 ]
},
"seq": 0
},
{
"tripId" : "Trip A",
"time" : ISODate("2015-03-08T04:48:43.589Z"),
"loc": {
"type": "Point",
"coordinates": [ -73.9498, 40.7968 ]
},
"seq": 1
},
{
"tripId" : "Trip A",
"time" : ISODate("2015-03-08T04:49:43.589Z"),
"loc": {
"type": "Point",
"coordinates": [ -73.9737, 40.7648 ]
},
"seq": 2
},
{
"tripId" : "Trip A",
"time" : ISODate("2015-03-08T04:50:43.589Z"),
"loc": {
"type": "Point",
"coordinates": [ -73.9814, 40.7681 ]
},
"seq": 3,
"isEnd": true
}
在示例中,我只是将这些文档插入名为" geojunk"的集合中,然后为" loc"发布2dsphere索引。字段:
db.geojunk.ensureIndex({ "loc": "2dsphere" })
然后用" 2"完成此处理。 .aggregate()
次查询。 .aggregate()
的原因是因为您希望匹配"首先"文件"每次旅行"在每种情况下。这表示查询找到的每个行程的最近航点。那么基本上你想要"合并"这些结果成为某种" hash"结构由" tripId"。
结束逻辑说,如果一个"起源"和目的地"匹配给定"旅行"的查询条件,那么这是整个查询的有效结果。
我在这里给出的代码是一个任意的nodejs
实现。主要是因为它是表示在" parallel"中发出查询的良好基础。为了获得最佳性能,我也选择使用nedb
作为" hash"的示例。多一点" Mongolike"语法:
var async = require('async'),
MongoClient = require("mongodb").MongoClient;
DataStore = require('nedb');
// Common stream upsert handler
function streamProcess(stream,store,callback) {
stream.on("data",function(data) {
// Clean "_id" to keep nedb happy
data.trip = data._id;
delete data._id;
// Upsert to store
store.update(
{ "trip": data.trip },
{
"$push": {
"time": data.time,
"loc": data.loc
}
},
{ "upsert": true },
function(err,num) {
if (err) callback(err);
}
);
});
stream.on("err",callback)
stream.on("end",callback);
}
MongoClient.connect('mongodb://localhost/test',function(err,db) {
if (err) throw err;
db.collection('geojunk',function(err,collection) {
if (err) throw err;
var store = new DataStore();
// Parallel execution
async.parallel(
[
// Match origin trips
function(callback) {
var stream = collection.aggregate(
[
{ "$geoNear": {
"near": {
"type": "Point",
"coordinates": [ -73.9580, 40.8003 ],
},
"query": {
"time": {
"$gte": new Date("2015-03-08T04:40:00.000Z"),
"$lte": new Date("2015-03-08T04:50:00.000Z")
},
"seq": 0
},
"maxDistance": 1000,
"distanceField": "distance",
"spherical": true
}},
{ "$group": {
"_id": "$tripId",
"time": { "$first": "$time" },
"loc": { "$first": "$loc" }
}}
],
{ "cursor": { "batchSize": 1 } }
);
streamProcess(stream,store,callback);
},
// Match destination trips
function(callback) {
var stream = collection.aggregate(
[
{ "$geoNear": {
"near": {
"type": "Point",
"coordinates": [ -73.9814, 40.7681 ]
},
"query": { "isEnd": true },
"maxDistance": 1000,
"distanceField": "distance",
"spherical": true
}},
{ "$group": {
"_id": "$tripId",
"time": { "$first": "$time" },
"loc": { "$first": "$loc" }
}}
],
{ "cursor": { "batchSize": 25 } }
);
streamProcess(stream,store,callback);
}
],
function(err) {
if (err) throw err;
// Just documents that matched origin and destination
store.find({ "loc": { "$size": 2 }},{ "_id": 0 },function(err,result) {
if (err) throw err;
console.log( JSON.stringify( result, undefined, 2 ) );
db.close();
});
}
);
});
});
在我列出的样本数据上,这将返回:
[
{
"trip": "Trip A",
"time": [
"2015-03-08T04:47:43.589Z",
"2015-03-08T04:50:43.589Z"
],
"loc": [
{
"type": "Point",
"coordinates": [
-73.958,
40.8003
]
},
{
"type": "Point",
"coordinates": [
-73.9814,
40.7681
]
}
]
}
]
因此它找到了最接近被查询位置的起点和终点,也是一个"起源"在所需的时间内和定义为目的地的东西,即" isEnd"。
因此$geoNear
操作与返回的结果匹配是最接近点和其他条件的文档。 $group
阶段是必需的,因为同一行程中的其他文件可以"可能"符合条件,所以它只是一种确定方式。 $first
运算符确保已经排序"排序"每个"旅行"结果将只包含一个结果。如果你真的"肯定"在条件不会发生的情况下,您可以在聚合之外使用标准$nearSphere
查询。所以我在这里安全地玩。
有一点需要注意,即使包含在" nedb"虽然它确实支持将输出转储到磁盘,但数据仍然在内存中累积。如果你期望大的结果,那么而不是这种类型的"哈希表"实现时,您需要以类似于另一个mongodb集合的方式输出,并从那里检索匹配的结果。
但这并没有改变整体逻辑,而另一个原因是使用" nedb"证明,因为你会" upsert"以相同的方式对结果集合中的文档进行处理。