【问题标题】:$unwind empty array$unwind 空数组
【发布时间】:2012-12-15 18:37:08
【问题描述】:

我有一个用户集合,其中每个文档具有以下结构:

{
  "_id": "<id>",
  "login": "xxx",
  "solved": [
    {
      "problem": "<problemID>",
      "points": 10
    },
    ...
  ]
}

solved 字段可以为空或包含任意多个子文档。我的目标是获取用户列表以及总分(points 的总和),其中尚未解决任何问题的用户将获得总分 0。这是否可以通过单个查询来做到这一点(理想情况下使用聚合框架)?

我试图在聚合框架中使用以下查询:

{ "$group": {
  "_id": "$_id",
  "login": { "$first": "$login" },
  "solved": { "$addToSet": { "points": 0 } }
} }
{ "$unwind": "$solved" }
{ "$group": {
  "_id": "$_id",
  "login": { "$first": "$login" },
  "solved": { "$sum": "$solved.points" }
} }

但是我收到以下错误:

exception: The top-level _id field is the only field currently supported for exclusion

提前谢谢你

【问题讨论】:

  • 当您说“已解决可能为空”时,它是存在且是一个空数组,还是在没有解决问题的情况下不存在?
  • 另外,$first login 得到第一个值(按数组的顺序)——这是你想要的,还是你想要的 $min?如果没有解决问题,login是否也可以为null或丢失?

标签: mongodb aggregation-framework


【解决方案1】:

在 MongoDB 3.2 和更新版本中,$unwind 运算符现在有一些选项,特别是 preserveNullAndEmptyArrays 选项可以解决这个问题。

如果此选项设置为 true,并且路径为 null、缺失或为空数组,则 $unwind 输出文档。如果为 false,$unwind 在路径为 null、缺失或空数组时不输出文档。在您的情况下,将其设置为 true:

db.collection.aggregate([
    { "$unwind": {
        "path": "$solved",
        "preserveNullAndEmptyArrays": true
    } },
    { "$group": {
        "_id": "$_id",
        "login": { "$first": "$login" },
        "solved": { "$sum": "$solved.points" }
    } }
])

【讨论】:

  • 好问题和好答案。你拯救了我的一天
【解决方案2】:

这是解决方案 - 它假定“已解决”字段不存在、等于 null 或已解决一系列问题和分数。它不处理的情况是“解决”为空数组 - 尽管这将是您可以添加的简单额外调整。

project = {$project : {
        "s" : {
            "$ifNull" : [
                "$solved",
                [
                    {
                        "points" : 0
                    }
                ]
            ]
        },
        "login" : 1
    }
};
unwind={$unwind:"$s"};
group= { "$group" : {
        "_id" : "$_id",
        "login" : {
            "$first" : "$login"
        },
        "score" : {
            "$sum" : "$s.points"
        }
    }
}

db.students.aggregate( [ project, unwind, group ] );

【讨论】:

  • 你好 @AsyaKamsky ,我已经在 MongoDB 3.0.3 上尝试过你的代码,但是 $ifNull 不能完全正常工作?我也对docs.mongodb.org/manual/reference/operator/aggregation/ifNull/… 上的这个问题一无所知。
  • 相反,我使用了's': { $cond: [{$eq: [{$size: '$solved'}, 0] }, [ { point: 0 } ], '$solved'] }
    ,并在 Jira 上进行了报告。
  • $size 在编写此答案时不存在,因此我无法在解决方案中使用它。下一个版本(3.2)也将具有 $isArray 运算符。我不知道您所说的 $ifNull 不能“完全”工作是什么意思 - 所以无法对此发表评论。
  • 我的意思是,我认为 $ifNull 会像文档 { $ifNull: [ &lt;expression&gt;, &lt;replacement-expression-if-null&gt; ] } 一样工作。但是,它并没有像上面的代码那样替换表达式。你能告诉我是否有替代的运营商适合我吗? (因为不再有$size 运算符)
  • 我已经通过使用 's': { $cond: [{$eq: ['$solved', []] }, [ { point: 0 } ], '$solved'] } 解决了。还是谢谢...
【解决方案3】:

$lookup 然后 $unwind 在查找数组中,这可能是空的


  let posts = await Post.aggregate<ActivityDoc>([
    {
      $match: {
        _id: new mongoose.Types.ObjectId(req.params.id),
      },
    },
    {
      $lookup: {
        from: 'users',
        localField: 'user',
        foreignField: '_id',
        as: 'user',
      },
    },
    {
      $unwind: '$user',
    },
    {
      $unwind: {
        path: '$user.follower',
        preserveNullAndEmptyArrays: true,
      },
    },
    {
      $match: {
        $or: [
          {
            $and: [
              {
                'privacy.mode': {
                  $eq: PrivacyMode.EveryOne,
                },
              },
            ],
          },
          {
            $and: [
              {
                'privacy.mode': {
                  $eq: PrivacyMode.MyCircle,
                },
              },
              {
                'user.follower.id': {
                  $eq: req.currentUser?.id,
                },
              },
            ],
          },
        ],
      },
    },
  ]);

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-02-19
    • 2017-10-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-05-20
    • 1970-01-01
    • 2018-04-12
    相关资源
    最近更新 更多