MongoDB 中的子查询
Sub-query in MongoDB
我在 MongoDB 中有两个集合,一个包含用户,一个包含操作。用户大致如下:
{_id: ObjectId("xxxxx"), country: "UK",...}
以及
等行为
{_id: ObjectId("yyyyy"), createdAt: ISODate(), user: ObjectId("xxxxx"),...}
我正在尝试计算按国家/地区划分的事件和不同用户。前半部分工作正常,但是当我尝试添加子查询以拉取国家/地区时,我只得到 country
的空值
db.events.aggregate({
$match: {
createdAt: { $gte: ISODate("2013-01-01T00:00:00Z") },
user: { $exists: true }
}
},
{
$group: {
_id: {
year: { $year: "$createdAt" },
user_obj: "$user"
},
count: { $sum: 1 }
}
},
{
$group: {
_id: {
year: "$_id.year",
country: db.users.findOne({
_id: { $eq: "$_id.user_obj" },
country: { $exists: true }
}).country
},
total: { $sum: "$count" },
distinct: { $sum: 1 }
}
})
这里没有加入,只有我们熊
所以MongoDB"does not do joins"。例如,您可能在 shell 中尝试过类似的操作:
db.events.find().forEach(function(event) {
event.user = db.user.findOne({ "_id": eventUser });
printjson(event)
})
但这并不像您认为的那样。它实际上完全按照它的样子执行,运行 对 "user" 集合进行查询,查询从 "events" 集合返回的每个项目,"to and from" [=62] =] 并且不在服务器上 运行。
出于同样的原因,您在聚合管道中的 'embedded' 语句不能那样工作。与上面不同的是,"whole pipeline" 逻辑在执行之前被发送到服务器。所以如果你对'select"UK"用户做了这样的事情:
db.events.aggregate([
{ "$match": {
"user": {
"$in": db.users.distinct("_id",{ "country": "UK" })
}
}}
])
然后 .distinct()
查询实际上是在 "client" 而不是服务器上进行评估的,因此在聚合管道中对任何文档值都不可用。所以 .distinct()
运行 首先是 returns 数组作为参数,然后整个管道被发送到服务器。那就是执行顺序。
更正
您至少需要某种程度的反规范化才能使您想要 运行 工作的那种查询。所以你一般有两个选择:
将整个用户对象数据嵌入到事件数据中。
至少在事件数据中嵌入 "some" 用户对象数据。在这种情况下 "country" 因为您将要使用它。
那么如果你遵循 "second" 的情况,至少 "extend" 你现有的数据会像这样包含 "country" 一点:
{
"_id": ObjectId("yyyyy"),
"createdAt": ISODate(),
"user": {
"_id": ObjectId("xxxxx"),
"country": "UK"
}
}
那么"aggregation"过程就变得简单了:
db.events.aggregate([
{ "$match": {
"createdAt": { "$gte": ISODate("2013-01-01T00:00:00Z") },
"user": { "$exists": true }
}},
{ "$group": {
"_id": {
"year": { "$year": "$createdAt" },
"user_id": "$user._id"
"country": "$user.country"
},
"count": { "$sum": 1 }
}},
{ "$group": {
"_id": "$_id.country",
"total": { "$sum": "$count" },
"distinct": { "$sum": 1 }
}}
])
我们不正常
修复您的数据以将其所需的信息包含在我们 "do not do joins" 的单个集合中是一个相对简单的过程。只是上面原始查询示例的变体:
var bulk = db.events.intitializeUnorderedBulkOp(),
count = 0;
db.users.find().forEach(function(user) {
// update multiple events for user
bulk.find({ "user": user._id }).update({
"$set": { "user": { "_id": user._id, "country": user.country } }
});
count++;
// Send batch every 1000
if ( count % 1000 == 0 ) {
bulk.execute();
bulk = db.events.intitializeUnorderedBulkOp();
}
});
// Clear any queued
if ( count % 1000 != 0 )
bulk.execute();
这就是全部内容。对 MongoDB 服务器的个人查询得到 "one collection" 和 "one collection only" 来处理。即使是如上所示的神奇 "Bulk Operations" 仍然只能在单个集合上 "batched"。
如果您想做 "aggregate on related properties" 之类的事情,那么您 "must" 将这些属性包含在您要为其聚合数据的集合中。将数据放在不同的集合中是完全可以接受的,例如 "users" 通常会附加更多信息,而不仅仅是和“_id”和 "country".
但这里的重点是,如果您需要 "country" 来分析 "user" 的 "event" 数据,那么也将其包含在数据中。最有效的服务器连接是 "pre-join",这通常是这里实践中的理论。
我在 MongoDB 中有两个集合,一个包含用户,一个包含操作。用户大致如下:
{_id: ObjectId("xxxxx"), country: "UK",...}
以及
等行为{_id: ObjectId("yyyyy"), createdAt: ISODate(), user: ObjectId("xxxxx"),...}
我正在尝试计算按国家/地区划分的事件和不同用户。前半部分工作正常,但是当我尝试添加子查询以拉取国家/地区时,我只得到 country
的空值db.events.aggregate({
$match: {
createdAt: { $gte: ISODate("2013-01-01T00:00:00Z") },
user: { $exists: true }
}
},
{
$group: {
_id: {
year: { $year: "$createdAt" },
user_obj: "$user"
},
count: { $sum: 1 }
}
},
{
$group: {
_id: {
year: "$_id.year",
country: db.users.findOne({
_id: { $eq: "$_id.user_obj" },
country: { $exists: true }
}).country
},
total: { $sum: "$count" },
distinct: { $sum: 1 }
}
})
这里没有加入,只有我们熊
所以MongoDB"does not do joins"。例如,您可能在 shell 中尝试过类似的操作:
db.events.find().forEach(function(event) {
event.user = db.user.findOne({ "_id": eventUser });
printjson(event)
})
但这并不像您认为的那样。它实际上完全按照它的样子执行,运行 对 "user" 集合进行查询,查询从 "events" 集合返回的每个项目,"to and from" [=62] =] 并且不在服务器上 运行。
出于同样的原因,您在聚合管道中的 'embedded' 语句不能那样工作。与上面不同的是,"whole pipeline" 逻辑在执行之前被发送到服务器。所以如果你对'select"UK"用户做了这样的事情:
db.events.aggregate([
{ "$match": {
"user": {
"$in": db.users.distinct("_id",{ "country": "UK" })
}
}}
])
然后 .distinct()
查询实际上是在 "client" 而不是服务器上进行评估的,因此在聚合管道中对任何文档值都不可用。所以 .distinct()
运行 首先是 returns 数组作为参数,然后整个管道被发送到服务器。那就是执行顺序。
更正
您至少需要某种程度的反规范化才能使您想要 运行 工作的那种查询。所以你一般有两个选择:
将整个用户对象数据嵌入到事件数据中。
至少在事件数据中嵌入 "some" 用户对象数据。在这种情况下 "country" 因为您将要使用它。
那么如果你遵循 "second" 的情况,至少 "extend" 你现有的数据会像这样包含 "country" 一点:
{
"_id": ObjectId("yyyyy"),
"createdAt": ISODate(),
"user": {
"_id": ObjectId("xxxxx"),
"country": "UK"
}
}
那么"aggregation"过程就变得简单了:
db.events.aggregate([
{ "$match": {
"createdAt": { "$gte": ISODate("2013-01-01T00:00:00Z") },
"user": { "$exists": true }
}},
{ "$group": {
"_id": {
"year": { "$year": "$createdAt" },
"user_id": "$user._id"
"country": "$user.country"
},
"count": { "$sum": 1 }
}},
{ "$group": {
"_id": "$_id.country",
"total": { "$sum": "$count" },
"distinct": { "$sum": 1 }
}}
])
我们不正常
修复您的数据以将其所需的信息包含在我们 "do not do joins" 的单个集合中是一个相对简单的过程。只是上面原始查询示例的变体:
var bulk = db.events.intitializeUnorderedBulkOp(),
count = 0;
db.users.find().forEach(function(user) {
// update multiple events for user
bulk.find({ "user": user._id }).update({
"$set": { "user": { "_id": user._id, "country": user.country } }
});
count++;
// Send batch every 1000
if ( count % 1000 == 0 ) {
bulk.execute();
bulk = db.events.intitializeUnorderedBulkOp();
}
});
// Clear any queued
if ( count % 1000 != 0 )
bulk.execute();
这就是全部内容。对 MongoDB 服务器的个人查询得到 "one collection" 和 "one collection only" 来处理。即使是如上所示的神奇 "Bulk Operations" 仍然只能在单个集合上 "batched"。
如果您想做 "aggregate on related properties" 之类的事情,那么您 "must" 将这些属性包含在您要为其聚合数据的集合中。将数据放在不同的集合中是完全可以接受的,例如 "users" 通常会附加更多信息,而不仅仅是和“_id”和 "country".
但这里的重点是,如果您需要 "country" 来分析 "user" 的 "event" 数据,那么也将其包含在数据中。最有效的服务器连接是 "pre-join",这通常是这里实践中的理论。