10

I have 3 collections:

  1. positions:
+------------------+----------------------+-----------------------+
|    position_id   |    company_id        |       position_name   |
+------------------+----------------------+-----------------------+
|         1        |        1             |        position 1     |
+------------------+----------------------+-----------------------+
|         2        |        2             |        position 2     |
+------------------+----------------------+-----------------------+
  1. companies:
+------------------+----------------------+-----------------------+
|    company_id    |     industry_id      |       company_name    |
+------------------+----------------------+-----------------------+
|        1         |          1           |       company 1       |
+------------------+----------------------+-----------------------+
|        2         |          2           |       company 2       |
+-----------------------------------------------------------------+
  1. industries:
+------------------+----------------------+
|     industry_id  |       industry_name  |
+------------------+----------------------+
|          1       |      industry 1      |
+------------------+----------------------+
|          2       |      industry 2      |
+------------------+----------------------+

I need to return the following result in one API:

[{
  position_id: 1,
  position_name: 'position 1',
  company: {
    company_id: 1,
    company_name: 'company 1',
    industry: {
      industry_id: 1,
      industry_name: 'industry 1',
    }
  }
}, {
  position_id: 2,
  posiiton_name: 'position 2',
  company: {
    company_id: 2,
    company_name: 'company 2',
    industry: {
      industry_id: 2,
      industry_name: 'industry 2',
    }
  }
}]

So the code of the pipeline part I can think of is like the following:

const pipelines = [{
  $lookup: {
    from: 'companies',
    localField: 'company_id',
    foreignField: 'company_id',
    as: 'company',

    $lookup: {
      from: 'industries',
      localField: 'industry_id',
      foreignField: 'industry_id',
      as: 'industry'
    }
  }
}]

return positions.aggregate(pipelines);

But this would throw some errors. So what is the correct way to do the nested $lookup in mongodb search?

Thanks in advance!

Ashh
  • 44,693
  • 14
  • 105
  • 132
Jeff Tian
  • 5,210
  • 3
  • 51
  • 71

1 Answers1

17

$lookup 3.6 syntax allows you to join nested tables and $unwind to deconstructs an array field from the input documents to output a document for each element. Something like this

position.aggregate([
  { "$lookup": {
    "from": "companies",
    "let": { "companyId": "$company_id" },
    "pipeline": [
      { "$match": { "$expr": { "$eq": [ "$_id", "$$companyId" ] } } },
      { "$lookup": {
        "from": "industries",
        "let": { "industry_id": "$industry_id" },
        "pipeline": [
          { "$match": { "$expr": { "$eq": [ "$_id", "$$industry_id" ] } } }
        ],
        "as": "industry"
      }},
      { "$unwind": "$industry" }
    ],
    "as": "company"
  }},
  { "$unwind": "$company" }
])

With the 3.4 version

position.aggregate([
  { "$lookup": {
    "from": "companies",
    "localField": "company_id",
    "foreignField": "_id",
    "as": "companies"
  }},
  { "$unwind": "$companies" },
  { "$lookup": {
    "from": "industries",
    "localField": "companies.industry_id",
    "foreignField": "_id",
    "as": "companies.industry"
  }},
  { "$unwind": "$companies.industry" },
  { "$group": {
    "_id": "$_id",
    "companies": { "$push": "$companies" }
  }}
])
Ashh
  • 44,693
  • 14
  • 105
  • 132
  • 1
    Hi I tried your solution and it works great on my local machine! Thanks! But after the code merged to production, it claims that `MongoError: arguments to $lookup must be strings, let: { companyId: "$companyId" } is type object`. And then I found the server uses mongodb 3.4. How should I change the code to make it be mongodb 3.4 compatible? – Jeff Tian Oct 10 '18 at 02:52
  • 1
    It can be done with 3.4 version but it will be a very lengthy and expensive to the performance. So it would be better if you update your server version. – Ashh Oct 10 '18 at 04:09
  • Unfortunately I can't update the server version because it's a large system that I can not control, and the team won't risk the upgrading... – Jeff Tian Oct 10 '18 at 05:27
  • 2
    Updated my answer – Ashh Oct 10 '18 at 05:42
  • 1
    Cool, Thanks a ton! – Jeff Tian Oct 10 '18 at 06:23
  • The `3.4` version code looks so much prettier then `3.6`. Wouldn't you agree? – tedi Jul 22 '21 at 07:21
  • 1
    @Tadej But performance wise 3.6 is better – Ashh Jul 22 '21 at 09:53