使用 $lookup 进行 MongoDB 嵌套查询

Go MongoDB nested query with $lookup

我的项目需要一个相当复杂的查询,但似乎找不到适用于它的有效实现。我下面的函数有效,但现在我需要添加一个 $lookup 语句来填充配置文件对象。如您所见,每个 Match 都有 PartnerApartnerB 类型 Partner。每个 Partner 都有一个 LiteOffer 有个人资料。需要使用 PartnerA.IDPartnerB.ID 作为查找的 localFields 从配置文件集合中添加配置文件。

基本上问题是这样的:我在我的函数中收到一个 Partner.ID,它也是 profile 集合中配置文件的 ID。然后我需要从 matches 集合中过滤掉在 PartnerA 或 PartnerB 中具有相同 Partner.ID 的所有 matches(只有一个合作伙伴具有相同的 ID。另一个会有不同的)。最后,我必须使用 mongo $lookup 使用相应的 Partner.ID

检索 PartnerA.Offer.Profile 和 PartnerB.Offer.Profile 的配置文件

这是 Match 结构(我删除了很多字段以便于阅读):

type Match struct {
    ID        primitive.ObjectID `json:"_id,omitempty" bson:"_id,omitempty"`
    PartnerA  Partner            `json:"partnerA" bson:"partnerA"`
    PartnerB  Partner            `json:"partnerB" bson:"partnerB"`
    Unlocked  bool               `json:"unlocked" bson:"unlocked"`
    DeletedAt time.Time          `json:"deletedAt,omitempty" bson:"deletedAt,omitempty"`
}

type Partner struct {
    ID               primitive.ObjectID `json:"id,omitempty" bson:"id,omitempty"`
    Offer            LiteOffer          `json:"offer,omitempty" bson:"offer,omitempty"`
    LooksInteresting bool               `json:"looksInteresting" bson:"looksInteresting"`
    Unlocked         bool               `json:"unlocked" bson:"unlocked"`
}

type LiteOffer struct {
    ID           primitive.ObjectID `json:"id,omitempty" bson:"id,omitempty"`
    Tags         []Tag              `json:"tags" bson:"tags,omitempty"`
    Profile      Profile            `json:"profile,omitempty" bson:"profile,omitempty"`
}

type Profile struct {
    ID          primitive.ObjectID `json:"id,omitempty" bson:"id" diff:"-"`
    Name        string             `json:"name,omitempty" bson:"name,omitempty" diff:"-"`
    Surname     string             `json:"surname,omitempty" bson:"surname,omitempty" 
}

这是我的函数:

func getMatchesByProfileId(id primitive.ObjectID) (*[]Match, error) {
    var matches []Match

    filter := bson.M{
        "$or": []bson.M{
            {
                "partnerA.id":               id,
                "partnerA.looksInteresting": false,
            },
            {
                "partnerB.id":               id,
                "partnerB.looksInteresting": false,
            },
        },
        "unlocked":  false,
        "deletedAt": nil,
    }

    ctx, _ := db.GetTimeoutContext()
    result, err := getMatchCollection().Find(ctx, filter)
    if err != nil {
        log.Error("Could not find matches, Error: ", err)
        return nil, err
    }

    for result.Next(ctx) {
        var m Match
        if err = result.Decode(&m); err != nil {
            log.Error("Could not decode offer in getMatchesByProfileId", err)
            return nil, err
        }
        matches = append(matches, m)
    }
    return &matches, nil
}

这是我使用的有效管道,但现在我需要以某种方式将这两个查询合并为一个:

    pipeline := mongo.Pipeline{
        {{"$match", match}},
        {{"$lookup", bson.M{
            "from":         "profile",
            "localField":   "partnerA.id",
            "foreignField": "_id",
            "as":           "profile",
        }}},
        {{"$unwind", "$profile"}},
    }

我希望这能解释一切。我花了几个小时,但找不到解决方案。任何帮助将不胜感激。

如果您有任何问题,请随时提出。

谢谢!

带查找的聚合查询(在mongoshmongoshell中运行):

var ID = 'some_value'  // to match with the partner A and B ids

var pipeline = [

  // Initial filter on the 'match' collection
  { 
      $match: { 
          unlocked:  false, 
          deletedAt: null,
          $or: [
              { $and: [ { 'partnerA.id': ID }, { 'partnerA.looksInteresting': false } ] },
              { $and: [ { 'partnerB.id': ID }, { 'partnerA.looksInteresting': false } ] },
          ]
  } },

  // Lookup 'profile' collection and get the matching details for the corresponding partner A and B (of 'match')
  { 
      $lookup: {
          from: 'profile',
          let: { pa_id: '$partnerA.id', pb_id: '$partnerB.id' },
          as: 'matched_partner_profiles',
          pipeline: [
              { 
                  $match: { 
                      $expr: {
                          $or: [
                              { $eq: [ '$$pa_id', '$_id' ] },
                              { $eq: [ '$$pb_id', '$_id' ] },
                          ]
                      }
              }},
          ]
  }},
]

// Run the aggregation query, using the pipeline defined above
db.getCollection('match').aggregate(pipeline)

所以我设法自己解决了这个问题,这是功能代码:

func getMatchesByProfileId(id primitive.ObjectID) (*[]Match, error) {
    var matches []Match

    match := bson.D{
        {"unlocked", false},
        {"deletedAt", nil},
        {"$or", []bson.M{
            {
                "partnerA.id":               id,
                "partnerA.looksInteresting": false,
            },
            {
                "partnerB.id":               id,
                "partnerB.looksInteresting": false,
            },
        }},
    }

    pipeline := mongo.Pipeline{
        {{"$match", match}},
        {{"$lookup", bson.M{
            "from":         "profile",
            "localField":   "partnerA.id",
            "foreignField": "_id",
            "as":           "partnerA.offer.profile",
        }}},
        {{"$unwind", "$partnerA.offer.profile"}},

        {{"$lookup", bson.M{
            "from":         "profile",
            "localField":   "partnerB.id",
            "foreignField": "_id",
            "as":           "partnerB.offer.profile",
        }}},
        {{"$unwind", "$partnerB.offer.profile"}},
    }

    ctx, _ := db.GetTimeoutContext()
    cursor, err := getMatchCollection().Aggregate(ctx, pipeline)
    if err != nil {
        log.Error("Could not aggregate matches, Error: ", err)
        return nil, err
    }

    defer cursor.Close(ctx)

    for cursor.Next(ctx) {
        var m Match
        if err = cursor.Decode(&m); err != nil {
            log.Error("Could not decode matches in getMatchesByProfileId error: ", err)
            return nil, err
        }
        matches = append(matches, m)
    }
    return &matches, nil
}