只要我有以下文件
用户
{
uuid: string,
isActive: boolean,
lastLogin: datetime,
createdOn: datetime
}
项目
{
id: string,
users: [
{
uuid: string,
otherInfo: ...
},
{... more users}
]
}
我想选择所有自 2 周以来未登录且处于非事件状态或自 5 周以来没有项目的用户。
现在,这两周工作得很好,但我似乎不知道如何完成“5 周,没有项目”部分
我想出了类似下面的东西,但最后一部分不起作用,因为 $exists
显然不是顶级运算符。
有人做过这样的事吗? 谢谢!
return await this.collection
.aggregate([
{
$match: {
$and: [
{
$expr: {
$allElementsTrue: {
$map: {
input: [`$lastLogin`, `$createdOn`],
in: { $lt: [`$$this`, twoWeeksAgo] }
}
}
}
},
{
$or: [
{
isActive: false
},
{
$and: [
{
$expr: {
$allElementsTrue: {
$map: {
input: [`$lastLogin`, `$createdOn`],
in: { $lt: [`$$this`, fiveWeeksAgo] }
}
}
}
},
{
//No projects exists on this user
$exists: {
$lookup: {
from: _.get(Config, `env.collection.projects`),
let: {
currentUser: `$$ROOT`
},
pipeline: [
{
$project: {
_id: 0,
users: {
$filter: {
input: `$users`,
as: `user`,
cond: {
$eq: [`$$user.uuid`, `$currentUser.uuid`]
}
}
}
}
}
]
}
}
}
]
}
]
}
]
}
}
])
.toArray();
最佳答案
不确定你为什么这么想 $expr
最初需要 $match
,但实际上:
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
};
这非常简单,就像通过 $expr
计算表达式一样实际上非常糟糕,并不是您在第一个管道阶段想要的。另外“不是您需要的”,因为 createdOn
和 lastLogin
确实不应该合并到 $allElementsTrue
的数组中。这只是一个AND条件,您所描述的逻辑实际上意味着OR。所以$or
这里做得很好。
$or
也是如此关于 isActive
或 true/false
的条件分离。同样是“两周”或“五周”。而这当然不需要$expr
因为标准不等式范围匹配工作正常,并且使用“索引”。
那么您实际上只想在 $lookup
的 let
中执行“条件” 操作而不是你的“它存在吗”的想法。您真正需要知道的(因为日期范围选择实际上已经完成)是 active
现在是 true
还是 false
。在它的事件
(根据您的逻辑,您不关心项目)的情况下,只需在$match
中使用$$uuid
即可。管道暂存一个 null
值,因此它不会与 $lookup
匹配。返回一个空数组。如果 false
(也已经匹配之前的日期条件),那么您使用实际值并“加入”(当然有项目)。
然后,只需保持活跃
用户,然后仅测试活跃
的剩余false
值来查看是否$lookup
中的 "projects"
数组实际上返回了任何东西。如果没有,那么他们就没有项目。
这里可能应该注意的是,由于 users
是 projects
集合中的一个“数组”,因此您使用 $in
对于 $match
针对数组的单个值的条件。
请注意,为了简洁起见,我们可以使用 $group
在内部管道中仅返回一个结果,而不是可能返回与实际匹配项目的多个匹配项。您不关心内容或“计数”,而只关心是否返回一个或什么也没有。再次遵循所提出的逻辑。
这将为您提供所需的结果,并且它以一种高效的方式实现,并且实际上使用可用的索引。
另外,return wait
肯定不会做您认为它会做的事情,事实上,这是一条 ESLint 警告消息(我建议您在项目中启用 ESLint),因为这不是明智之举。它实际上什么也没做,因为无论如何你都需要 await getResults()
(按照示例命名),因为 await
关键字不是“魔术” 但只是 then()
的一种更漂亮的编写方式。一旦您理解了 async/await
在语法上的真正含义,希望更容易理解。
关于node.js - 将 $lookup 与条件连接一起使用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55759773/