Node.js 将$lookup与条件联接一起使用
如果我有以下文件 用户Node.js 将$lookup与条件联接一起使用,node.js,mongodb,aggregation-framework,Node.js,Mongodb,Aggregation Framework,如果我有以下文件 用户 { uuid: string, isActive: boolean, lastLogin: datetime, createdOn: datetime } 项目 { id: string, users: [ { uuid: string, otherInfo: ... }, {... more users} ] } 我
{
uuid: string,
isActive: boolean,
lastLogin: datetime,
createdOn: datetime
}
项目
{
id: string,
users: [
{
uuid: string,
otherInfo: ...
},
{... more users}
]
}
我想选择自2周以来未登录且处于非活动状态的所有用户,或者自5周以来没有项目的所有用户
现在,这两周很好,但我似乎不知道如何做“5周没有项目”部分
我想出了下面这样的方法,但最后一部分不起作用,因为$exists
显然不是顶级操作符
有人做过这样的事吗?
谢谢
不确定您最初认为需要的原因,但实际上:
const getResults = () => {
const now = Date.now();
const twoWeeksAgo = new Date(now - (1000 * 60 * 60 * 24 * 7 * 2 ));
const fiveWeeksAgo = new Date(now - (1000 * 60 * 60 * 24 * 7 * 5 ));
// as long a mongoDriverCollectionReference points to a "Collection" object
// for the "users" collection
return mongoDriverCollectionReference.aggregate([
// No $expr, since you can actually use an index. $expr cannot do that
{ "$match": {
"$or": [
// Active and "logged in"/created in the last 2 weeks
{
"isActive": true,
"$or": [
{ "lastLogin": { "$gte": twoWeeksAgo } },
{ "createdOn": { "$gte": twoWeeksAgo } }
]
},
// Also want those who...
// Not Active and "logged in"/created in the last 5 weeks
// we'll "tag" them later
{
"isActive": false,
"$or": [
{ "lastLogin": { "$gte": fiveWeeksAgo } },
{ "createdOn": { "$gte": fiveWeeksAgo } }
]
}
]
}},
// Now we do the "conditional" stuff, just to return a matching result or not
{ "$lookup": {
"from": _.get(Config, `env.collection.projects`), // there are a lot cleaner ways to register models than this
"let": {
"uuid": {
"$cond": {
"if": "$isActive", // this is boolean afterall
"then": null, // don't really want to match
"else": "$uuid" // Okay to match the 5 week results
}
}
},
"pipeline": [
// Nothing complex here as null will return nothing. Just do $in for the array
{ "$match": { "$in": [ "$$uuid", "$users.uuid" ] } },
// Don't really need the detail, so just reduce any matches to one result of [null]
{ "$group": { "_id": null } }
],
"as": "projects"
}},
// Now test if the $lookup returned something where it mattered
{ "$match": {
"$or": [
{ "active": true }, // remember we selected the active ones already
{
"projects.0": { "$exists": false } // So now we only need to know the "inactive" returned no array result.
}
]
}}
]).toArray(); // returns a Promise
};
这非常简单,因为通过计算得到的表达式实际上非常糟糕,并且不是第一个管道阶段所需要的。另外,“不是您所需要的”,因为createdOn
和lastLogin
确实不应该被合并到一个数组中,该数组只是一个和条件,您所描述的逻辑实际上意味着或。所以这里的天气很好
关于分离的是活动的
的真/假
的条件也是如此。再说一遍,不是“两周”就是“五周”。这当然不需要,因为标准不等式范围匹配工作良好,并且使用“索引”
然后你真的只想做let
for中的“有条件的”事情,而不是你的“它存在吗”思维。您真正需要知道的(因为日期的范围选择实际上已经完成)是active
现在是true
还是false
。如果处于活动状态
(根据您的逻辑,您不关心项目),只需将管道阶段中使用的$$uuid
设置为null
值,使其不匹配,并返回一个空数组。如果false
(也已经与前面的日期条件匹配),则使用实际值和“join”(当然,在有项目的情况下)
然后只需让用户保持活动状态,然后只测试活动状态下剩余的false
值,看看数组中的项目是否实际返回了任何内容。如果没有,那么他们就没有项目
这里可能需要注意的是,由于users
是projects
集合中的一个“数组”,因此您使用该数组中的单个值作为条件
请注意,为了简洁起见,我们可以在内部管道中使用,只返回一个结果,而不返回实际匹配项目的许多匹配结果。您不关心内容或“计数”,只关心one被返回或什么都不返回。再次遵循所提出的逻辑
这将获得您想要的结果,并且它以一种高效的方式实现,并且在可用的情况下实际使用索引
而且return await
肯定不会做您认为它会做的事情,事实上这是一条ESLint警告消息(我建议您在项目中启用ESLint),因为这不是一件明智的事情。它实际上没有什么作用,因为您无论如何都需要wait getResults()
(根据示例命名),因为wait
关键字不是“魔法”,而是一种更漂亮的书写方式then()
。除了希望更容易理解之外,一旦您理解了async/await
在语法上的真正含义,也就是说。先生,您是一个救生员!非常感谢您,也感谢您提供的扩展信息。我将开始调整我的查询与你的命题肯定!再次感谢你!
const getResults = () => {
const now = Date.now();
const twoWeeksAgo = new Date(now - (1000 * 60 * 60 * 24 * 7 * 2 ));
const fiveWeeksAgo = new Date(now - (1000 * 60 * 60 * 24 * 7 * 5 ));
// as long a mongoDriverCollectionReference points to a "Collection" object
// for the "users" collection
return mongoDriverCollectionReference.aggregate([
// No $expr, since you can actually use an index. $expr cannot do that
{ "$match": {
"$or": [
// Active and "logged in"/created in the last 2 weeks
{
"isActive": true,
"$or": [
{ "lastLogin": { "$gte": twoWeeksAgo } },
{ "createdOn": { "$gte": twoWeeksAgo } }
]
},
// Also want those who...
// Not Active and "logged in"/created in the last 5 weeks
// we'll "tag" them later
{
"isActive": false,
"$or": [
{ "lastLogin": { "$gte": fiveWeeksAgo } },
{ "createdOn": { "$gte": fiveWeeksAgo } }
]
}
]
}},
// Now we do the "conditional" stuff, just to return a matching result or not
{ "$lookup": {
"from": _.get(Config, `env.collection.projects`), // there are a lot cleaner ways to register models than this
"let": {
"uuid": {
"$cond": {
"if": "$isActive", // this is boolean afterall
"then": null, // don't really want to match
"else": "$uuid" // Okay to match the 5 week results
}
}
},
"pipeline": [
// Nothing complex here as null will return nothing. Just do $in for the array
{ "$match": { "$in": [ "$$uuid", "$users.uuid" ] } },
// Don't really need the detail, so just reduce any matches to one result of [null]
{ "$group": { "_id": null } }
],
"as": "projects"
}},
// Now test if the $lookup returned something where it mattered
{ "$match": {
"$or": [
{ "active": true }, // remember we selected the active ones already
{
"projects.0": { "$exists": false } // So now we only need to know the "inactive" returned no array result.
}
]
}}
]).toArray(); // returns a Promise
};