只要我有以下文件
用户
{
uuid: string,
isActive: boolean,
lastLogin: datetime,
createdOn: datetime
}
项目
{
id: string,
users: [
{
uuid: string,
otherInfo: ...
},
{... more users}
]
}
我想选择所有自2个星期以来未登录且处于非活动状态或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();
答案 0 :(得分:1)
不确定为什么您以为$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
的{{1}}的条件分离中也是如此。还是 “两个星期” OR “五个星期”。而且,由于标准不等式范围匹配可以正常工作并且使用“索引”,因此这当然不需要$expr
。
然后,您真的只想在$lookup
的true/false
中执行“有条件的” 而不是您的“是否存在” 思维。您真正需要知道的(由于实际上已经完成了日期范围的选择)是let
现在是active
还是true
。只需将false
的值设为active
,就可以将$$uuid
放在null
的位置(根据您的逻辑,您不必关心项目)并且$match
返回一个空数组。在false
(也已经与之前的日期条件匹配)的地方,您使用实际值和“ join”(当然有项目)。
然后,只需保留active
个用户,然后仅测试false
的其余active
个值,看看是否{ {3}}实际上返回了任何内容。如果没有,那么他们就是没有项目。
这里可能要注意的是,由于"projects"
是users
集合内的“数组”,因此您需要对数组的单个值的$lookup
条件使用$lookup
请注意,为了简洁起见,我们可以在内部管道中使用$in
来返回一个结果,而不是实际匹配项目的许多匹配项。您不在乎内容或“计数” ,而只是在返回了一个或什么都没有。再次遵循提出的逻辑。
这可以为您带来理想的结果,并且以一种高效的方式进行,并且实际上在可用的地方使用索引。
当然projects
并不会按照您的想法去做,实际上这是一条ESLint警告消息(我建议您在项目中启用ESLint),因为这不是一件明智的事情。它实际上并没有执行任何操作,因为您仍然需要return await
(按照示例命名),因为await getResults()
关键字不是“ magic” 而是一个更漂亮的书写方式await
。希望您会更容易理解,但一旦您了解then()
的语法含义,就可以了。