0

I have the following structure for a collection in 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", "", "", ""]}
        ], ... }
   ]
}

and I am trying to implement projection in my API: for example, if the user requests

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

The result should be

{
   '_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]},
        ], ... }
   ]
}

Since the 'fields' names are not actual keys, I cannot use mongo projection, say

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

So I must instead search within the array for the fields whose "name" property matches my projection parameter. I achieved that for the first level array with aggregation and $redact, as suggested in this answer 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"
       }
      }}])

However, I cannot use $redact to select subfields from the inner arrays in multivalued fields. The $or parameters would have to be something like

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

which means a first level field with the same name of the subfield specified would also pass.

After upgrading MongoDB to 3.2, I attempted the $filter solution in this answer https://stackoverflow.com/a/12241930/5418731, which also works fine for the first level array:

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

but I couldn't find a way to use it "nested" and filter the second level array items. Adding {$eq: ['$$field.value.name', 'subfield1']} doesn't work.

Last, I tried the $map solution presented here 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
                    ]
                }
            }
        }
    }}
])

But this one won't work because the "value" property of each "fields" item is not necessarily an array, and when it isn't the whole query fails.

I'm about to give up and mask the results in JS. Is there a good solution for that with Mongo?

4

1 回答 1

0

您的示例请求中是否有错字。当您查询字段名称为“common field 2”但您期望在响应中出现“common field 1”的文档时。

此外,您可以简单地使用聚合管道并按以下方式进行操作,而不是让这变得如此复杂:

  1. 首先 $unwind 在 fields 数组上。
  2. 然后 $match fields where fields.name = "common field 1" and type = "multifield"。
  3. 然后 $unwind 在值数组上。
  4. 最后 $match fields.value.name = "subfield1" 的字段

像这样的东西:

db.points.aggregate([
    { $unwind: "$fields" },
    { $match: { "fields.name": "common field 1", "fields.type": "multifield" } },
    { $unwind: "$fields.value" },
    { $match: { "fields.value.name": "subfield1" } }
]);
于 2016-02-17T17:06:32.540 回答