DEV Community

loading...
Cover image for Mastering The Database - Accessing Nested Records - Series #10

Mastering The Database - Accessing Nested Records - Series #10

functional_js profile image Functional Javascript ・2 min read

Intro

In the last series we retrieved a random sample of docs.
It was very simple. Essentially a oneliner.

But supposes we wanted to select a random sample of items that existed as subdocs of the outer level docs in our app?

Actually, it's not too hard.
And we don't use any loops.
Instead we "unwind", or your could think of it as "flattening" our arr of docs.

If an artist has an arr of albums, then each artist doc has an arr of subdocs.

If an artist's arr of albums each contain an arr of song, then we have a doc that has an arr of subdocs whose subdocs also have an arr of subdocs.

Here is what the outer doc's data shape look like:

/*
data shape:
 {
    "artist": "Julian Lennon",
    "albums": [
      {
        "albumTitle": "Valotte (1984)",
        "albumSongs": [
          {
            "song": "Valotte"
          },
          {
            "song": "O.K. for You"
          },
          {
            "song": "On the Phone"
//....
*/
Enter fullscreen mode Exit fullscreen mode

Here is the query to select a random set of song (in this case 5 of them)

  mgArr(dbEnum.nlpdb, collEnum.songsColl,
    unwindArr("albums"),
    unwindArr("albums.albumSongs"),
    randomSample(5),
    projectIncludeNoId("artist", "albums.albumSongs.song"),
  )
Enter fullscreen mode Exit fullscreen mode

Here are five random songs.
We've included the artist so we can see who the song is performed by:

/*
output:

[
  {
    "artist": "Miley Cyrus",
    "albums": {
      "albumSongs": {
        "song": "Girls Just Wanna Have Fun"
      }
    }
  },
  {
    "artist": "Creedence Clearwater Revival",
    "albums": {
      "albumSongs": {
        "song": "Penthouse Pauper"
      }
    }
  },
  {
    "artist": "Judas Priest",
    "albums": {
      "albumSongs": {
        "song": "Out In The Cold"
      }
    }
  },
  {
    "artist": "Akon",
    "albums": {
      "albumSongs": {
        "song": "Throw Dat"
      }
    }
  },
  {
    "artist": "Nazareth",
    "albums": {
      "albumSongs": {
        "song": "Hit The Fan"
      }
    }
  }
]
*/

Enter fullscreen mode Exit fullscreen mode

Notes

1.
The unwindArr is a wrapper func around the $unwind stage operator.
The wrappers make the code look cleaner, and they're usually oneliners:

export const unwindArr = k => ({ $unwind: "$" + k });
Enter fullscreen mode Exit fullscreen mode

2.
We have to unwind the outer arr, then we can next unwind the inner arr. We could keep on going if there were more arrs. For example, each song could have an arr of songwriters.

3.
Once all the songs are unwound, we can grab a random sample of them using the $sample stage operator.
The RandomSample wrapper is a simple oneliner:

export const randomSample = lim => ({ $sample: { size: lim } });
Enter fullscreen mode Exit fullscreen mode

4.
Notice the dot notation in the unwind func in order to express the path to the nest arrs.

5.
Finally, for ease of display reason, I "project" out only two fields. My wrapper func, the "NoId" variant, excludes the Primary Key. I use this variant just for testing
The raw $project syntax would look like this (0 means exclude, 1 means include):

{ $project: { _id: 0, artist: 1, "albums.albumSongs.song": 1 } },
Enter fullscreen mode Exit fullscreen mode

6.
Take note also, that conveniently, we can swap out and use either the wrapper funcs, or the raw MongoDB stage syntax with our call to the MongoDB aggregate func. This is because the wrapper funcs simply just returns the raw syntax anyhow, of each stage in the pipeline.

Resources

https://docs.mongodb.com/manual/reference/operator/aggregation/unwind

https://docs.mongodb.com/manual/reference/operator/aggregation/sample

Discussion (0)

Forem Open with the Forem app