展开空数组

52

我有一个用户集合,每个文档的结构如下:

{
  "_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

提前感谢您


当你说“solved may be empty”时,它是一个空数组还是在没有解决问题的情况下不存在? - Asya Kamsky
另外,$first login会获取第一个值(按照数组顺序) - 这是您想要的吗?还是您想要 $min? 如果没有解决问题,登录是否可以为null或缺失? - Asya Kamsky
3个回答

152

从MongoDB 3.2版本开始,$unwind 操作符现在具有一些选项,其中特别是 preserveNullAndEmptyArrays 选项将解决此问题。

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

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

1
哇,经典的 if-true-work-much-better-than-the-unreasonable-default 标志 :/ - Boaz

8

这里是解决方案 - 它假设字段“solved”要么不存在,要么等于null,或者已经解决了一系列问题和得分。它无法处理的情况是“solved”为空数组 - 尽管这可以是您可以添加的简单附加调整。

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]);

翻译:使用聚合操作对db.students集合进行处理,包括投影、展开和分组。

你好 @AsyaKamsky,我已经尝试了你的MongoDB 3.0.3代码,但是$ifNull没有正常工作?而且我在 http://docs.mongodb.org/manual/reference/operator/aggregation/ifNull/#exp._S_ifNull 上也找不到关于这个问题的任何信息。 - efkan
1
相反,我使用了's': { $cond: [{$eq: [{$size: '$solved'}, 0] }, [ { point: 0 } ], '$solved'] }<br>并在Jira上报告了。 - efkan
当回答这个问题时,$size还不存在,所以我不能在解决方案中使用它。下一个版本(3.2)也将有$isArray运算符。我不知道你所说的$ifNull没有“完全”工作是什么意思,所以无法发表评论。 - Asya Kamsky
我的意思是,我以为$ifNull会像文档中所述一样工作:{ $ifNull: [ <expression>, <replacement-expression-if-null> ] }。然而它并没有像你上面的代码那样替换表达式。请问是否有其他操作符可供选择?(因为$size操作符已不再使用) - efkan
我已经通过使用's': { $cond: [{$eq: ['$solved', []] }, [ { point: 0 } ], '$solved'] }解决了。无论如何还是谢谢... - efkan
显示剩余2条评论

2

$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,
                },
              },
            ],
          },
        ],
      },
    },
  ]);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接