Go MongoDB嵌套查询使用$lookup。

huangapple go评论78阅读模式
英文:

Go MongoDB nested query with $lookup

问题

我有一个非常复杂的查询,需要在我的项目中使用,但似乎找不到一个可行的实现方法。下面是我的函数,它可以工作,但现在我需要添加一个$lookup语句来填充profile对象。如你所见,每个Match都有一个类型为Partner的PartnerA和partnerB。每个Partner都有一个LiteOffer,其中包含一个profile。需要使用PartnerA.ID和PartnerB.ID作为lookup的localFields,从profile集合中添加profile。

基本上问题是这样的:我在函数中收到一个Partner.ID,它也是profile集合中一个profile的id。然后,我需要过滤掉所有在matches集合中具有相同Partner.ID的matches,无论是在PartnerA还是PartnerB中(只有一个Partner将具有相同的ID,另一个将具有不同的ID)。最后,我必须使用mongo的$lookup来检索PartnerA.Offer.Profile和PartnerB.Offer.Profile的profile,使用相应的Partner.ID。

这是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" diff:"-" 
}

这是我的函数:

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,但现在我需要将这两个查询合并为一个:

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

希望这样解释清楚了。我花了几个小时,找不到解决办法。非常感谢您的帮助。

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

谢谢!

英文:

I have quite a complex query that I need for my project and can't seem to find a working implementation for it. My function below works, but now I need to add a $lookup statement to populate the profile object. As you can see every Match has a PartnerA and partnerB of type Partner. Every Partner has a LiteOffer witch has a profile. The profile needs to be added from the profile collection using PartnerA.ID and PartnerB.ID as the localFields for lookup.

Basically the issue is this: I receive one Partner.ID in my function which is also an id of a profile in a profile collection. Then I need to filter out all matches from the matches collection that have the same Partner.ID either in PartnerA or in PartnerB (only one of the Partners will have the same ID. The other one will have a different one). Finally I have to use mongo $lookup to retrieve the profiles for both PartnerA.Offer.Profile and PartnerB.Offer.Profile using the corresponding Partner.ID

Here is the Match struct (I cut out many of the fields so it would be easier to read):

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" 
}

Here is my function:

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
}

Here is a pipeline that I used that works, but now I need to somehow combine these two queries into one:

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

I hope this explained everything. It has taken me hours and I can't find the solution. any help would be highly appreciated.

If you have any questions feel free to ask.

Thank you!

答案1

得分: 1

所以我成功解决了这个问题,以下是函数代码:

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
}
英文:

So I managed To solve this issue myself and here is the function code:

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
}

答案2

得分: 0

聚合查询与查找(在mongoshmongo shell中运行):

var ID = 'some_value'  // 与合作伙伴A和B的ID匹配

var pipeline = [

  // 在'match'集合上进行初始过滤
  { 
      $match: { 
          unlocked:  false, 
          deletedAt: null,
          $or: [
              { $and: [ { 'partnerA.id': ID }, { 'partnerA.looksInteresting': false } ] },
              { $and: [ { 'partnerB.id': ID }, { 'partnerA.looksInteresting': false } ] },
          ]
  } },

  // 查找'profile'集合,并获取相应合作伙伴A和B(来自'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' ] },
                          ]
                      }
              }},
          ]
  }},
]

// 运行聚合查询,使用上面定义的管道
db.getCollection('match').aggregate(pipeline)

以上是使用查找的聚合查询代码。

英文:

The aggregation query with lookup (runs in mongosh or mongo shell):

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)

huangapple
  • 本文由 发表于 2022年3月8日 05:14:09
  • 转载请务必保留本文链接:https://go.coder-hub.com/71387340.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定