Home > Net >  MongoDB $ifNull empty array still adding data
MongoDB $ifNull empty array still adding data

Time:10-18

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: [];
}

CodePudding user response:

Append this stage to you pipeline:

{
  $set: {
    tracks: {
      $filter: {
        input: "$tracks",
        cond: { $ne: [ { $type: "$$this._id" }, "missing" ] } 
      }
    }
  }
}

CodePudding user response:

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"
          ]
        }
      }
    }
  }
])
  • Related