skip to Main Content

I need to project phone numbers and emails from array and show them as Document Fields.
Something like this:

{
Name: "Mac",
Emails: [
{address: '[email protected]', title: '', isMain: true},
{address: '[email protected]', title: '', isMain: false}
],
Phones: [
{number: '6465468794984', title: '', isMain: true},
{number: '6465468794986', title: '', isMain: false}
]
}

I need it this way:

{
 Name: "Mac",
 Email1: '[email protected]',
 Email2: '[email protected]',
 Phone1: '6465468794984',
 Phone2: '6465468794986
}

How can I do that?
Thanks.

2

Answers


  1. Modify key dynamically according to the size of the array not seems to be the best efficient query… I suggests to do it in your code, running this simple query you can loop and get Emails[index] and Phones[index].

    By the way, using only MongoDB I think you can try this one (maybe it is possible to improve):

    The trick here is to do a "loop" using $map to iterate over the Emails or Phones size.

    • So the map wil be from 0 to emails size, which is input: {$range: [0, { $size: "$Emails" }]}.
    • Every step will be the "index", setting as as: "index".
    • And for every step we want to create and object with structure {"k":"EmailX", "v":"[email protected]}. And that’s why we set k value as $concat the "Email" text and index + 1 using $add (i.e. Email1, Email2, … EmailN).
    • Then v value is simply the elements at that index. So at this point you have [{"k":"Email1", "v": "[email protected]"}, {"k": "Email2", "v": "[email protected]"}, { ... }]
    • And this is done for Emails and Phones and also is into $concatArrays stage to get in a single array all k and v pairs for emails and phones.
    • Next stage is to use $arrayToObject to transform the pairs k and v to and object like {"Email1": "[email protected]"}.
    • And last part is using $replaceRoot to get calues at top level, merging the Name value and the result value which is all emails and phones.
    db.collection.aggregate([
      {
        $project: {
          Name: 1,
          result: {
            "$concatArrays": [
              {
                $map: {
                  input: {$range: [0, { $size: "$Emails" }]},
                  as: "index",
                  in: {
                    k: {$concat: ["Email", { $toString: { $add: ["$$index",1]}}]},
                    v: {$arrayElemAt: ["$Emails.address","$$index"]}
                  }
                }
              },
              {
                $map: {
                  input: {$range: [0, {$size: "$Phones"}]},
                  as: "index",
                  in: {
                    k: { $concat: ["Phone", {$toString: { $add: [ "$$index", 1]}}]},
                    v: { $arrayElemAt: [ "$Phones.number", "$$index" ] }
                  }
                }
              }
            ]
          }
        }
      },
      {
        $project: {
          "_id": 0,
          "Name": 1,
          "result": {
            $map: {
              input: "$result",
              as: "item",
              in: {
                $arrayToObject: [
                  [
                    {
                      k: "$$item.k",
                      v: "$$item.v"
                    }
                  ]
                ]
              }
            }
          }
        }
      },
      {
        $replaceRoot: {
          newRoot: {
            $mergeObjects: [
              {
                "Name": "$Name"
              },
              {
                $mergeObjects: "$result"
              }
            ]
          }
        }
      }
    ])
    

    Example here

    P.S. I’ve tried to be as clear as possible explaining the query. Also I think it is a better option do the loop into the code not getting the computed result from DB. But that’s a dev decision so maybe it’s ok for you doing in this way.

    Login or Signup to reply.
  2. Another option is to use $reduce:

    db.collection.aggregate([
      {$project: {
          Name: 1,
          result: {$concatArrays: [
              {$reduce: {
                  input: "$Emails",
                  initialValue: [],
                  in: {$concatArrays: [
                      "$$value",
                      [{
                          k: {$concat: [
                              "Email",
                              {$toString: {$add: [{$size: "$$value"}, 1]}}
                          ]},
                          v: "$$this.address"
                      }]
                  ]}
              }},
              {$reduce: {
                  input: "$Phones",
                  initialValue: [],
                  in: {$concatArrays: [
                      "$$value",
                      [{
                          k: {$concat: [
                              "Phone",
                              {$toString: {$add: [{$size: "$$value"}, 1]}}
                          ]},
                          v: "$$this.number"
                      }]
                  ]}
              }}
          ]}
      }},
      {$replaceRoot: {newRoot: {$mergeObjects: [
              {Name: "$Name"},
              {$mergeObjects: {$arrayToObject: "$result"}}
      ]}}}
    ])
    

    See how it works on the playground example

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search