node.js - 将 $lookup 与条件连接一起使用

标签 node.js mongodb aggregation-framework

只要我有以下文件

用户

{
    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 计算表达式一样实际上非常糟糕,并不是您在第一个管道阶段想要的。另外“不是您需要的”,因为 createdOnlastLogin 确实不应该合并到 $allElementsTrue 的数组中。这只是一个AND条件,您所描述的逻辑实际上意味着OR。所以$or这里做得很好。

$or 也是如此关于 isActivetrue/false 的条件分离。同样是“两周”“五周”。而这当然不需要$expr因为标准不等式范围匹配工作正常,并且使用“索引”。

那么您实际上只想在 $lookuplet 中执行“条件” 操作而不是你的“它存在吗”的想法。您真正需要知道的(因为日期范围选择实际上已经完成)是 active 现在是 true 还是 false。在它的事件(根据您的逻辑,您不关心项目)的情况下,只需在$match中使用$$uuid即可。管道暂存一个 null 值,因此它不会与 $lookup 匹配。返回一个空数组。如果 false (也已经匹配之前的日期条件),那么您使用实际值并“加入”(当然有项目)。

然后,只需保持活跃用户,然后仅测试活跃的剩余false值来查看是否$lookup 中的 "projects" 数组实际上返回了任何东西。如果没有,那么他们就没有项目。

这里可能应该注意的是,由于 usersprojects 集合中的一个“数组”,因此您使用 $in对于 $match针对数组的单个值的条件。

请注意,为了简洁起见,我们可以使用 $group在内部管道中仅返回一个结果,而不是可能返回与实际匹配项目的多个匹配项。您不关心内容或“计数”,而只关心是否返回一个什么也没有。再次遵循所提出的逻辑。

这将为您提供所需的结果,并且它以一种高效的方式实现,并且实际上使用可用的索引。

另外,return wait 肯定不会做您认为它会做的事情,事实上,这是一条 ESLint 警告消息(我建议您在项目中启用 ESLint),因为这不是明智之举。它实际上什么也没做,因为无论如何你都需要 await getResults() (按照示例命名),因为 await 关键字不是“魔术” 但只是 then() 的一种更漂亮的编写方式。一旦您理解了 async/await 在语法上的真正含义,希望更容易理解。

关于node.js - 将 $lookup 与条件连接一起使用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55759773/

相关文章:

node.js - 将 Cloud9 IDE 部署到 Heroku?

node.js - 如何在 Mongoose 中设置数据库名称和集合名称?

MongoDB - 如何从嵌套数组中的元素中删除字段?

c# - MongoCollectionSettings.GuidRepresentation 已过时,有什么替代方法?

Mongodb聚合: Combine multiple keys into an array

node.js - 如何在gulp任务之间共享参数?

MongoDB:如何获取集合中最后更新文档的最后更新时间戳

node.js - mongodb 中 $nin 的非严格行为

mongodb - 删除除最新文档之外的所有重复项

node.js - Google 电子表格 api 请求的身份验证范围不足