如何使用mongo省略嵌套文档中数组的元素?

时间:2016-02-17 16:00:49

标签: arrays mongodb subdocument

我对MongoDB中的集合有以下结构

{
   '_id': 45
   'tags': [ 'tag 1', 'tag 3' ]
   'active': true
   'fields': [
      { 'name': 'common field 1', 'type': 'text', 'value': 'some text', ... },
      { 'name': 'common field 2', ... },
      { 'name': 'multivalued field 1', 
        'type': 'multifield',
        'valueCount': 5,
        'value': [
            { 'name': 'subfield1', ..., 'value': [1, 2, 3, 4, 5]},
            { 'name': 'subfield2', ..., 'value': ["one", "two", "three", "four", "five"]},
            { 'name': 'subfield3', ..., 'value': ["here", "there", "", "", ""]}
        ], ... }
   ]
}

我正在尝试在我的API中实现投影:例如,如果用户请求

api/collection/?fields=id,fields{common field 2, multifield{subfield1}} 

结果应为

{
   '_id': 45
   'fields': [
      { 'name': 'common field 1', 'type': 'text', 'value': 'some text', ... },
      { 'name': 'multivalued field 1', 
        'type': 'multifield',
        'valueCount': 5,
        'value': [
            { 'name': 'subfield1', ..., 'value': [1, 2, 3, 4, 5]},
        ], ... }
   ]
}

由于'字段'名称不是实际键,我不能使用mongo投影,比如说

db.collection.find({},{_id: 1, tags: 1, fields.'common field 1': 1})

所以我必须在数组中搜索“name”属性与我的投影参数匹配的字段。我在第一级数组中实现了聚合和$ redact,如本回答https://stackoverflow.com/a/24032549/5418731

所示
db.points.aggregate([
      { $match: {}},
  {
        $project: {_id :1, fields: 1}
      },
      { $redact : {
       $cond: {
        if: { $or : [{ $not : "$name" }, { $eq: ["$name", "common field 1"] }]},
           then: "$$DESCEND",
           else: "$$PRUNE"
       }
      }}])

但是,我无法使用$ redact从多值字段中的内部数组中选择子字段。 $或参数必须类似于

[{ $not : "$name" }, { $eq: ["$name", "common field 1"] }, { $eq: ["$name", "subfield1"] }]

表示与指定子字段名称相同的第一级字段也将通过。

将MongoDB升级到3.2之后,我在这个答案https://stackoverflow.com/a/12241930/5418731中尝试了$ filter解决方案,这对第一级数组也适用:

db.points.aggregate([
    {$project: {
        fields: {$filter: {
            input: '$fields',
            as: 'field',
            cond: {$eq: ['$$field.name', 'multivalued field 1']}
        }}
    }}
])

但我找不到一种方法来“嵌套”使用它并过滤第二级数组项。添加{$eq: ['$$field.value.name', 'subfield1']}不起作用。

最后,我尝试了这里提供的$ map解决方案https://stackoverflow.com/a/24156418/5418731

db.points.aggregate([
    { "$project": {
        "_id": 1,
        "fields": {
            "$map": {
                "input": "$fields",
                "as": "f",
                "in": {
                    "$ifNull": [
                        { 
                            "name": "$multivalued field 1",
                            "type": "$multifield", //attempt to restrict search to fields with arrays as values
                            "value": {
                                "$map": {
                                    "input": "$$f.value",
                                    "as": "v",
                                    "in": {
                                        "$ifNull": [
                                            { "name": "$subfield1"},
                                            false
                                        ]
                                    }
                                }
                            }
                        },
                        false
                    ]
                }
            }
        }
    }}
])

但是这个不起作用,因为每个“fields”项的“value”属性不一定是数组,当它不是整个查询失败时。

我即将放弃并掩盖JS中的结果。有没有一个很好的解决方案与Mongo?

1 个答案:

答案 0 :(得分:0)

您的样本请求中是否存在拼写错误。当您查询文档时 名称为"普通字段2"但是你期待"共同领域1"在你的回复中。

此外,您只需使用聚合管道并按以下方式继续操作,而不是使其如此复杂:

  1. 首先在字段数组上展开$。
  2. 然后$匹配字段where fields.name =" common field 1"并输入=" multifield"。
  3. 然后在值数组上展开$。
  4. 最后$匹配fields.value.name =" subfield1"
  5. 的字段

    这样的事情:

    db.points.aggregate([
        { $unwind: "$fields" },
        { $match: { "fields.name": "common field 1", "fields.type": "multifield" } },
        { $unwind: "$fields.value" },
        { $match: { "fields.value.name": "subfield1" } }
    ]);