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

サンプル リクエストにタイプミスはありますか。名前が「共通フィールド 2」であるフィールドを持つドキュメントを照会しているため、応答で「共通フィールド 1」が期待されています。

また、これを複雑にする代わりに、単純に集約パイプラインを使用して、次の方法で処理を進めることができます。

  1. fields 配列の最初の $unwind。
  2. 次に $match fields where fields.name = "common field 1" and type = "multifield".
  3. 次に、値の配列で $unwind を実行します。
  4. 最後に、fields.value.name = "subfield1" のフィールドを $match します。

このようなもの :

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 に答える