Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Select fields to return from $lookup

I have a piece of code to join collection A (sample) to collection B (locators). I've tried the $unwind, $group and $push syntax the only problem is that I can't return the field locator and record.


data = db.sample.aggregate([
{'$lookup': {
    'from': 'locators',
    'localField': "locator",
    'foreignField': "_id",
    'as': "metalocator"}}])

print(list(data))

Which returns

[
  {
    '_id': '599A65E1A80541BA',
    'locator': 'ABC',
    'record': 'Nicaragua',
    'metalocator': [{'_id': 'ABC', 'group': 'Location', 'section': 'Geo', 'cabinet': 'Country', 'record': 'string', 'status': 'integer'}]
  },
  { 
    '_id': '428E970995AE8C76',
    'locator': 'CDE',
    'record': 'Nigeria',
    'metalocator': [{'_id': 'CDE', 'group': 'Location', 'section': 'Geo', 'cabinet': 'Country', 'record': 'string', 'status': 'integer'}]
  }
]

attempt 1

data = db.sample.aggregate([
    {"$lookup": {"from": "locators",
                 "localField": "locator",
                 "foreignField": "_id",
                 "as": "metalocator"}},
    {"$unwind": '$metalocator'},
    {"$group": {"_id": "$_id",
                "metalocator": {"$push":  {
                    "section": "$metalocator.section",
                    "cabinet": "$metalocator.cabinet"}}}}
])
print(list(data))

Which returns:

[
  {
    '_id': '1835853D2982AAEF',
    'metalocator': [{'section': 'Geo', 'cabinet': 'Country'}]
  },
  {
    '_id': '428E970995AE8C76',
    'metalocator': [{'section': 'Geo', 'cabinet': 'Country'}]
  }
]

Expected result should be:

[
  {
    '_id': '1835853D2982AAEF',
    'locator': 'ABC',
    'record': 'Nicaragua',
    'metalocator': [{'section': 'Geo', 'cabinet': 'Country'}]
  },
  {
    '_id': '428E970995AE8C76',
    'locator': 'CDE',
    'record': 'Nigeria',
    'metalocator': [{'section': 'Geo', 'cabinet': 'Country'}]
  }
]
like image 621
Ninja Warrior 11 Avatar asked May 28 '18 08:05

Ninja Warrior 11


1 Answers

You want $map:

db.sample.aggregate([
  {'$lookup': {
    'from': 'locators',
    'localField': "locator",
    'foreignField': "_id",
    'as': "metalocator"
  }},
  { '$addFields': {
    'metalocator': {
      '$map': {
        'input': '$metalocator',
        'in': {
          'section': '$$this.section',
          'cabinet': '$$this.cabinet'
        }
      }
    }
  }}
 ])

That is what you use to "re-map" the contents of an array, which is really what you are asking. It's usage is much the same as the operator of much the same name in python as well as many other languages.

If you have MongoDB 3.6, you alternately can use a different $lookup syntax where you can actually "choose" which fields to return from there:

db.sample.aggregate([
  {'$lookup': {
    'from': 'locators',
    'let': { 'locator': '$locator' },
    'pipeline': [
      { '$match': {
        '$expr': { '$eq': [ '$_id', '$$locator' ] }
      }},
      { '$project': { 'section': 1, 'cabinet': 1, '_id': 0 } }
    ],
    'as': "metalocator"
  }}
])

That would actually be more efficient where you can do it, since the data is simply not even returned into the target array and you don't need to "re-map" the array in order to discard the other fields.

For the record, what you "missed" was the $first operator:

db.sample.aggregate([
  { "$lookup": {
    "from": "locators",
    "localField": "locator",
    "foreignField": "_id",
    "as": "metalocator"
  }},
  { "$unwind": '$metalocator'},
  { "$group": {
    "_id": "$_id",
    "locator": { "$first": "$locator" },
    "record": { "$first": "$record" },
    "metalocator": {
      "$push":  {
        "section": "$metalocator.section",
        "cabinet": "$metalocator.cabinet"
      }
    }
  }}
])

But using $unwind and $group is not necessary here as the other methods shown above are far more efficient.

like image 199
Neil Lunn Avatar answered Oct 19 '22 05:10

Neil Lunn