1

I have 2 collections, users and tracks. When I fetch the user profile I want to get his tracks. The user object has tracks as an array of track IDs, and the second collection is of the tracks.

Now I am running this code below:

users
    .aggregate([
      {
        $match: { _id: ObjectId(userId) },
      },
      {
        $lookup: {
          from: "tracks",
          localField: "tracks",
          foreignField: "_id",
          as: "tracks",
        },
      },
      {
        $unwind: {
          path: "$tracks",
          preserveNullAndEmptyArrays: true,
        },
      },
      {
        $group: {
          _id: ObjectId(userId),
          username: { $first: "$username" },
          profileImg: { $first: "$profileImg" },
          socialLinks: { $first: "$socialLinks" },
          tracks: {
            $push: {
              _id: "$tracks._id",
              name: "$tracks.name",
              categoryId: "$tracks.categoryId",
              links: "$tracks.links",
              mediaId: isLogged ? "$tracks.mediaId" : null,
              thumbnailId: "$tracks.thumbnailId",
              views: { $size: { $ifNull: ["$tracks.views", []] } },
              downloads: { $size: { $ifNull: ["$tracks.downloads", []] } },
              uploadedDate: "$tracks.uploadedDate",
            },
          },
        },
      },
    ])

In case the user does not have tracks or there are no tracks, the $ifNull statement returns an object only with these fields so it looks like that:

user: {
    // user data
    tracks: [{ views: 0, downloads: 0, mediaId: null }]
}

There are no tracks found so "tracks.views" cannot be read so I added the $ifNull statement, how can I avoid it from returning an empty data? also, the API call knows whether the user is logged or not (isLogged), I set the mediaId to null. If there are no tracks found, why does the code still add these 3 fields only? no tracks to go through them...

Edit: Any track has downloads and views containing user IDs whom downloaded / viewed the track, the track looks like that

{
    "name": "New Track #2227",
    "categoryId": "61695d57893f048528d049e5",
    "links": {
        "youtube": "https://www.youtube.com",
        "soundcloud": null,
        "spotify": null
    },
    "_id": "616c90651ab67bbd0b0a1172",
    "creatorId": "61695b5986ed44e5c1e1d29d",
    "mediaId": "616c90651ab67bbd0b0a1171",
    "thumbnailId": "616c90651ab67bbd0b0a1170",
    "plays": [],
    "status": "pending",
    "downloads": [],
    "uploadedDate": 1634504805
}

When the fetched user doesn't have any track post yet, I receive an array with one object that contains the fields mentioned above.

What I expect to get when the user has no tracks is an empty array, as you can see the response above, the track object isn't full and contains only the conditional keys with zero value and null. Bottom line I want the length of views and downloads only if there is a track or more, also for the mediaId which I want to hide it in case the user isn't logged. If there are no tracks I don't understand why it returns these 3 fields

expected result when the user has one track or more

user: {
  // user data
  tracks: [
    {
      name: "New Track #2227",
      categoryId: "61695d57893f048528d049e5",
      links: {
        youtube: "https://www.youtube.com",
        soundcloud: null,
        spotify: null,
      },
      _id: "616c90651ab67bbd0b0a1172",
      creatorId: "61695b5986ed44e5c1e1d29d",
      mediaId: "616c90651ab67bbd0b0a1171",
      thumbnailId: "616c90651ab67bbd0b0a1170",
      plays: 0,
      status: "pending",
      downloads: 0,
      uploadedDate: 1634504805,
    },
  ];
}

expected result when the user has no tracks

user: {
  // user data
  tracks: [];
}
6
  • what is the desired output,can you add a sample? if empty data field to be missing? check $$REMOVE also maybe it can help you Commented Oct 17, 2021 at 20:40
  • @Takis_ Hi again! in case the user doesn't have tracks posted, I expect to get an empty array for the downloads / views, right now it sends these 3 fields. it should be the entire track (if found) or nothing at all... Commented Oct 17, 2021 at 20:59
  • Not clear what you like to get, perhaps try views: { $ifNull: ["$tracks.views", []] } or views: { $ifNull: ["$tracks.views", "$$REMOVE"] } Commented Oct 17, 2021 at 21:03
  • @WernfriedDomscheit updated the question, tried "$$REMOVE" instead of [] and it throws this error The argument to $size must be an array, but was of type: missing Commented Oct 17, 2021 at 21:14
  • Providing some sample input data is a good starting point. Please also provide expected sample output data. Commented Oct 17, 2021 at 21:16

2 Answers 2

1

Append this stage to your pipeline:

{
  $set: {
    tracks: {
      $filter: {
        input: "$tracks",
        cond: { $ne: [ { $type: "$$this._id" }, "missing" ] } 
      }
    }
  }
}
Sign up to request clarification or add additional context in comments.

1 Comment

You got me! Thanks, that's what I needed.
1

You could also modify your query to look something like this: (check out a live demo here)

This query uses a conditional push via $cond coupled with the $and operator to search for more than one condition. If tracks.downloads or tracks.plays are not greater than 0, we use the $$REMOVE variable (which just ignores that document and returns an empty array, like you are looking for).

Query

db.users.aggregate([
  {
    $match: {
      _id: ObjectId("616c80793235ab5cc26dbaff")
    },
    
  },
  {
    $lookup: {
      from: "tracks",
      localField: "tracks",
      foreignField: "_id",
      as: "tracks"
    }
  },
  {
    $unwind: {
      path: "$tracks",
      preserveNullAndEmptyArrays: true
    }
  },
  {
    $group: {
      _id: ObjectId("616c80793235ab5cc26dbaff"),
      username: {
        $first: "$username"
      },
      profileImg: {
        $first: "$profileImg"
      },
      socialLinks: {
        $first: "$socialLinks"
      },
      tracks: {
        $push: {
          // "IF" plays and downloads > 0
          $cond: [
            {
              $and: [
                {
                  $gt: [
                    "$tracks.plays",
                    0
                  ]
                },
                {
                  $gt: [
                    "$tracks.downloads",
                    0
                  ]
                },
                
              ]
            },
            // "THEN" return document
            {
              _id: "$tracks._id",
              name: "$tracks.name",
              categoryId: "$tracks.categoryId",
              links: "$tracks.links",
              mediaId: "$tracks.mediaId",
              thumbnailId: "$tracks.thumbnailId",
              plays2: {},
              plays: "$tracks.plays",
              downloads: "$tracks.downloads",
              uploadedDate: "$tracks.uploadedDate"
            },
            // "ELSE" remove
            "$$REMOVE"
          ]
        }
      }
    }
  }
])

2 Comments

i can see there is a condition of $and for the plays and downloads > 0, what if there is a track of that user that has 0 downloads/plays? or a track that has 1+ downloads and 0 views (and vice versa)?
I changed the condition to $ifNull: ["$tracks._id", false] and it works :) only if there is an ID, so whether plays/downloads are 0 or something else it will be fine as long as the track exists mongoplayground.net/p/xtvrczmOzdk

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.