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
};