Vipin
Vipin

Reputation: 153

mongodb $lookup with foreignfield _id

I want to fetch the result using $lookup having local fields as the array of _id as a string. But its not happening. I also tried by using _id.str but still no results.

db.posts.aggregate([

{
    $addFields: {
        TagCount: { $size: "$tags" }
    }
},
{
    $lookup:
    {
        from: "tags",
        localField: "tags",
        foreignField: "_id",
        as: "tagList"
    }
},
]));

The schema for Post Collection

const postSchema = new mongoose.Schema({
  title: {
    type: String,
    required: true,
    minlength: 10,
    set: v => v.replace(/\s\s+/gi, ' '),
    get: v => { console.log(v); return v.replace(/\s\s+/gi, ' '); }
  },
  content: {
    type: String,
    required: true
  },
  headerImage: {
    type: String
  },
  tags: [{
    type: mongoose.Schema.Types.ObjectId,
    required: true,
    validate: {
    validator: function (v) {
       return v.length > 0;
    }
  },
  message: 'There must be one tag',
  ref: 'tags'
 }],
  creationDate: { type: Date, default: Date.now }
 });

Here is the tag Schema Collection

 const tagSchema = new mongoose.Schema({
   name: {
    type: String,
    required: true,
    minlength: 3,
    set: value => value.replace(/\s\s+/gi, ' '),
    get: value => value.replace(/\s\s+/gi, ' ')
  },
  description: {
    type: String
  },
  creationDate: {
    type: Date,
    default: Date.now
 }  
 }, {
 collection: 'tags'
});

Also, I tried other StackOverflow questions but I didn't find any solutions.

Upvotes: 0

Views: 1927

Answers (2)

Super Dev
Super Dev

Reputation: 1

db.posts.aggregate([
{
   $lookup: { 
   from: "tags",
   let: { pid: "$tags" },
   pipeline: [
      {
         $match: {
            $expr: {
                $eq: ["$_id", { $toObjectId: "$$pid" }]
            }
         }
      }
   ],
   as: "tagList"
}]);

Upvotes: 0

Joe
Joe

Reputation: 28316

Including the ref: 'tags' in the schema definition for the tags array makes mongoose store the values as DBRef, not ObjectID.

This is useful if you intend to use the populate function to retrieve the tag documents on the client side.

The field names in an aggregation are $id, $ref, and $db, which are not valid field names when using dotted notation, like you need to do in the aggregation pipeline.

You could convert each object to an array, which would make it look something like [ {"k":"$id", v:ObjectId("...")}, {"k":"$ref","v":"tags"}, {"k":"$db","v":"dbname"}]

Then filter for the $id key, retain the corresponding value, and use that array as the local field.

db.posts.aggregate([
  {$addFields: {
      TagCount: {$size: "$tags"},
      tag_ids: {
        $map: {
          input: "$tags",
          as: "dbref",
          in: {
            $reduce: {
              input: {$objectToArray: "$$dbref"},
              initialValue: [],
              in: {
                $cond: {
                  if: {$eq: ["$$this.k",{$literal: "$id"}]},
                  then: "$$this.v",
                  else: "$$value"
                }
              }
            }
          }
        }
      }
  }},
  {
    $lookup: {
      from: "tags",
      as: "tag_list",
      localField: "tag_ids",
      foreignField: "_id"
    }
  }
])

Upvotes: 1

Related Questions