<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Krishna Bose</title>
    <description>The latest articles on DEV Community by Krishna Bose (@krishnabose02).</description>
    <link>https://dev.to/krishnabose02</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F566036%2Fa85f2c61-31de-4349-b69e-c66ab99e1cc4.jpeg</url>
      <title>DEV Community: Krishna Bose</title>
      <link>https://dev.to/krishnabose02</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/krishnabose02"/>
    <language>en</language>
    <item>
      <title>MongoDB lookup on an array of objects which contains (foreign) ObjectId as a key</title>
      <dc:creator>Krishna Bose</dc:creator>
      <pubDate>Sat, 05 Jun 2021 22:50:51 +0000</pubDate>
      <link>https://dev.to/krishnabose02/mongodb-lookup-on-an-array-of-objects-which-contains-foreign-objectid-as-a-key-37b9</link>
      <guid>https://dev.to/krishnabose02/mongodb-lookup-on-an-array-of-objects-which-contains-foreign-objectid-as-a-key-37b9</guid>
      <description>&lt;p&gt;If you have worked with MongoDB it's highly probable that you have used aggregation pipelines. And its a very common use-case that you need to connect documents from one collection to another, aka &lt;strong&gt;'The Join Query'&lt;/strong&gt; from our SQL days.&lt;/p&gt;

&lt;p&gt;Let's elaborate the problem statement first. We have 2 collections, &lt;code&gt;orders&lt;/code&gt; and &lt;code&gt;products&lt;/code&gt;. &lt;br&gt;
Every order has some user related information and a list of products which the user has ordered along with its quantity.&lt;/p&gt;

&lt;h4&gt;
  
  
  sample order object
&lt;/h4&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    ...
    _id: ObjectId(...),
    userId: ObjectId(...),
    createdAt: 1622928483,
    products: [
        {
            productId: ObjectId(...),
            quantity: 1,
        },
        {
            productId: ObjectId(...),
            quantity: 3,
        },
    ]
    ...
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h4&gt;
  
  
  sample product object
&lt;/h4&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
   ...
   _id: ObjectId(...),
   name: 'Macbook Pro M1',
   description: 'All systems pro.',
   price: 1200,
   ...
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h4&gt;
  
  
  The result we want from aggregation
&lt;/h4&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    ...
    _id: ObjectId(...),
    userId: ObjectId(...),
    createdAt: 1622928483,
    products: [
        {
            productId: ObjectId(...),
            quantity: 1,
             // this got added
            product: {
               ...
               _id: ObjectId(...),
               name: 'Macbook Pro M1',
               description: 'All systems pro.',
               price: 1200,
               ...
            }
        },
        {
            productId: ObjectId(...),
            quantity: 3,
             // this too
            product: {
               ...
               _id: ObjectId(...),
               name: 'iPhone 12',
               description: 'Blast past fast.',
               price: 900,
               ...
            }
        },
    ]
    ...
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Here you can see that we have inflated the product details inside the products array.&lt;br&gt;
At the time of writing this article, I couldn't find any sweet and easy way of achieving this, so I decided to dive in a little deeper.&lt;/p&gt;

&lt;p&gt;A point to note here, if the product array would have been something like &lt;code&gt;products: [ObjectId(...), ObjectId(...)]&lt;/code&gt; then it would be a breeze. But since we also need to accommodate &lt;code&gt;quantity&lt;/code&gt; alongside the productId, it's not quite feasible (at least with the current schema).&lt;/p&gt;

&lt;p&gt;So, let's get started  with the solution.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Split the array
&lt;/h3&gt;

&lt;p&gt;Use the &lt;code&gt;$unwind&lt;/code&gt; operator to split the products array so that each document formed will have only one product instead of the entire array.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    $unwind: {
        path: '$products'
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Now each of our unwound orders would looks something like this&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    ...
    _id: ObjectId(...),
    userId: ObjectId(...),
    createdAt: 1622928483,
    products: {
        productId: ObjectId(...),
        quantity: 1,
    }
    ...
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This looks simpler, but wait, where are the rest of the ordered products?&lt;br&gt;
It turns out, using &lt;code&gt;$unwind&lt;/code&gt; would split one document into multiple documents where all the data is identical except the one list on which &lt;code&gt;$unwind&lt;/code&gt; is called.&lt;br&gt;
Here, it will split our sample document into 2 documents like the following&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#doc 1
{
    ...
    _id: ObjectId(...),
    userId: ObjectId(...),
    createdAt: 1622928483,
    products: {
        productId: ObjectId(...),
        quantity: 1,
    }
    ...
}
#doc 2
{
    ...
    _id: ObjectId(...),
    userId: ObjectId(...),
    createdAt: 1622928483,
    products: {
        productId: ObjectId(...),
        quantity: 3,
    }
    ...
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;So, I hope it's clear that, if you have n orders with m products in each, it will generate a total &lt;code&gt;m*n&lt;/code&gt; documents.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Inflate product details
&lt;/h3&gt;

&lt;p&gt;This is the important step, fetching product details from another collection&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    $lookup: {
        from: 'products',
        localField: 'products.productId',
        foreignField: '_id',
        as: 'products.product'
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;this step inflates the data and makes it look something like the following&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    ...
    _id: ObjectId(...),
    userId: ObjectId(...),
    createdAt: 1622928483,
    products: {
        productId: ObjectId(...),
        quantity: 1,
        product: [
            {
               ...
               _id: ObjectId(...),
               name: 'Macbook Pro M1',
               description: 'All systems pro.',
               price: 1200,
               ...
            }
        ]
    }
    ...
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;If you notice one thing, the product is inflated, but its an array instead of an object. The reason being, mongodb expects that multiple documents in the &lt;code&gt;products&lt;/code&gt; table may match with this criteria, and hence by default puts all the docs in an array. In our case we know its going to be unique, so we can just unwind it out.&lt;/p&gt;

&lt;h4&gt;
  
  
  Step 2a: Unwind the product inflated
&lt;/h4&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    $unwind: {
        path: '$products.product'
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Cool! we got the data we needed. Now we can merge those docs up to make it into original.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Merging the docs
&lt;/h3&gt;

&lt;p&gt;the key for this step is &lt;code&gt;$group&lt;/code&gt;. we will push all the products into an array.&lt;/p&gt;

&lt;p&gt;But, here is a small catch!&lt;br&gt;
For grouping them together, you need to preserve all the other fields as well. like &lt;code&gt;userId&lt;/code&gt;, &lt;code&gt;createdAt&lt;/code&gt; and &lt;code&gt;_id&lt;/code&gt;.&lt;br&gt;
One way is, put everything in &lt;code&gt;_id&lt;/code&gt; of your group, something like&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    $group: {
        _id: {
            _id: '$_id',
            userId: '$userId',
            createdAt: '$createdAt'
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;BUT...&lt;br&gt;
there are 3 shortcomings&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Difficult to execute if docs have different keys.&lt;/li&gt;
&lt;li&gt;High chances of forgetting/mistyping any field&lt;/li&gt;
&lt;li&gt;If the schema is altered even slightly, you need to go through the pipeline and rewrite this part again.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;As a typical engineer who believes in writing 20 smart lines to avoid hardcoding 5 lines, I came up with a way to make this bit future proof.&lt;/p&gt;

&lt;p&gt;The key idea is, merge on the basis of &lt;code&gt;_id&lt;/code&gt; alone, and then lookup itself to get a fresh copy of the doc.&lt;/p&gt;

&lt;h4&gt;
  
  
  Step 3a: Join on basis of &lt;code&gt;_id&lt;/code&gt;
&lt;/h4&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    _id: '$_id',
    products: {
        $push: '$products'
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Here, I am pushing elements to &lt;code&gt;products&lt;/code&gt; after picking them from &lt;code&gt;products&lt;/code&gt;, it might sound confusing, but what it does is that, it takes all of the &lt;code&gt;products&lt;/code&gt; from existing docs, put them in an array and then call it products in the end.&lt;/p&gt;

&lt;h4&gt;
  
  
  Step 3b: Fetch a fresh copy of orders for regeneration
&lt;/h4&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    $lookup: {
        from: 'orders',
        localField: '_id',
        foreignField: '_id',
        as: 'orderDetails'
    },
    $unwind: {
        path: '$orderDetails'
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This is pretty straightforward, shouldn't need much explaining. At this point the doc looks something like this&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    _id: ObjectId(...),
    products: [
        {
            productId: ObjectId(...),
            quantity: 1,
            product: {
                ...
            }
        },
        ...
    ],
    orderDetails: {
        _id: ObjectId(...),
        userId: ObjectId(...),
        createdAt: 1622928483,
        products: [
            {
                // No product: {...} here, since its original doc
                productId: ObjectId(...),
                quantity: 1
            }
        ]
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;I know it looks weird, don't worry. We have some beautification left.&lt;/p&gt;

&lt;h4&gt;
  
  
  Step 3c: Beautify and reshape the entire doc
&lt;/h4&gt;

&lt;p&gt;All we need to do is put all the stuff in their proper places.&lt;br&gt;
We shall do it in 2 steps.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Put the &lt;code&gt;products: {}&lt;/code&gt; from root to &lt;code&gt;orderDetails.products&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Make the &lt;code&gt;orderDetails&lt;/code&gt; as the root doc&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Something like this&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    $addFields: {
        'orderDetails.products': '$products'
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;and then&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    $replaceRoot: {
        newRoot: '$orderDetails'
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This one takes the contents inside &lt;code&gt;orderDetails&lt;/code&gt; and make it as the root document.&lt;/p&gt;

&lt;p&gt;And here we have our required order object with inflated product details!&lt;/p&gt;

&lt;h4&gt;
  
  
  The complete aggregation looks something like this
&lt;/h4&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[
    {
        $unwind: {
            path: '$products'
        }
    },
    {
        $lookup: {
            from: 'products',
            localField: 'products.productId',
            foreignField: '_id',
            as: 'products.product'
        }
    },
    {
        $unwind: {
            path: '$products.product'
        }
    },
    {
        $group: {
            _id: '$_id',
            products: {
                $push: '$products'
            }
        }
    },
    {
        $lookup: {
            from: 'orders',
            localField: '_id',
            foreignField: '_id',
            as: 'orderDetails'
        }
    },
    {
        $unwind: {
            path: '$orderDetails'
        }
    },
    {
        $addFields: {
            'orderDetails.products': '$products'
        }
    },
    {
        $replaceRoot: {
            newRoot: '$orderDetails'
        }
    }
]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;So, here we have it!&lt;br&gt;
Inflating array of items using keys nested inside objects.&lt;/p&gt;

&lt;p&gt;If there is a better way, let me know. I'd be happy to make corrections.&lt;br&gt;
Plus this is my first Dev.to post, so everyone please let me know ways to improve my quality of contribution here.&lt;/p&gt;

&lt;p&gt;Happy coding :D&lt;/p&gt;

</description>
      <category>mongodb</category>
      <category>database</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
