MongoDB在Go语言中的聚合查询(mgo.v2)

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

MongoDB Aggregate lookup in Go (mgo.v2)

问题

我正在尝试在使用mgo包的go(golang)中实现$lookup功能的一个mongoDB查询中进行实现。

以下是我的集合:

folders:

"_id"    : ObjectId("22222222222222"),
"name"   : "Media",
"level"  : 1,
"userIDs": [ObjectId("4444444444444")]

documents:

"_id"      : ObjectId("11111111111111"),
"title"    : "Media Management",
"body"     : BinData(0,"PvQ6z2NBm4265duo/e2XsYxA5bXKo="),
"level"    : 1,
"folderID" : ObjectId("22222222222222"), // 外键/字段
"userIDs"  : [ObjectId("44444444444444")]

以下是我在shell上成功运行的查询:

var query = [
{ 
  "$lookup": {
    "from":         "documents",
    "localField":   "_id",
    "foreignField": "folderID",
    "as":           "documents",
  }
}
 ,{
   "$match": {
      "userIDs": ObjectId("userIdHere"), // 根据userID过滤
      "level": {$gte: 0},                // 根据文件夹级别过滤
    },
  }
];

db.folders.aggregate(query).pretty().shellPrint();

如果我在shell上运行此脚本,我会得到所需的结果。基本上,返回给我的是包含通过$lookup链接的完整相关documentsfolder集合。我没有在这里包含它,因为这个问题已经太长了。

我尝试将此查询转换为mgo能够解析和执行的内容。以下是go代码中的查询:

query := bson.M{
  "$lookup": bson.M{ // 在这里查找documents表
  "from":         "documents",
  "localField":   "_id",
  "foreignField": "folderID",
  "as":           "documents",
},
  "$match": bson.M{
    "level":   bson.M{"$gte": user.Level}, // 根据级别过滤
    "userIDs": user.ID,                    // 根据用户过滤
  },
}

pipe := collection.Pipe(query) // 查询"folders"集合
err := pipe.All(&result)

我始终收到相同的错误:字段的类型错误(pipeline)3 != 4

如果我理解正确,这是因为它无法正确解析结果返回到result对象中。我已经尽一切努力确保结构具有所需的确切结构。我还尝试传递一个通用的[]interface{}和一个空的bson.M{}对象。仍然收到相同的错误。

以下是我的Folders结构:

type Folder struct {
  ID        bson.ObjectId   `json:"id" bson:"_id"`
  Name      string          `json:"name"`
  Level     int             `json:"level"`
  UserIDs   []bson.ObjectId `json:"userIDs" bson:"userIDs"`
  Users     []User          `json:"-" bson:"-"`
  Documents []Document      `json:"-" bson:"-"` // 不存储在数据库中
}

我还删除了$match子句,以查看是否可以从$lookup查询中获取任何内容。但我仍然收到相同的错误。

也许mgo包不支持$lookup?如果是这样,是否有其他方法?也许我可以将原始查询文本发送到mongo并接收原始响应并自行解析?

英文:

I'm trying to implement $lookup functionality in one of my mongoDB queries in go (golang) using the mgo package.

Below are my collections:

folders:

"_id"    : ObjectId("22222222222222"),
"name"   : "Media",
"level"  : 1,
"userIDs": [ObjectId("4444444444444")]

documents:

"_id"      : ObjectId("11111111111111"),
"title"    : "Media Management",
"body"     : BinData(0,"PvQ6z2NBm4265duo/e2XsYxA5bXKo="),
"level"    : 1,
"folderID" : ObjectId("22222222222222"), // Foreign Key/Field
"userIDs"  : [ObjectId("44444444444444")]

Below is the query I've written that successfully runs on the shell:

var query = [
{ 
  "$lookup": {
    "from":         "documents",
    "localField":   "_id",
    "foreignField": "folderID",
    "as":           "documents",
  }
}
 ,{
   "$match": {
      "userIDs": ObjectId("userIdHere"), // filder by a userID
      "level": {$gte: 0},                // filter by a folder level
    },
  }
];

db.folders.aggregate(query).pretty().shellPrint();

If I run this script on the shell, I get the desired result. Basically, the folder collection is returned to me containing the full relevant documents that were linked through the $lookup. I'm not including it here because this question already seems too long.

I've tried to translate this query into something that mgo would be able to parse and execute. Here it is below in go code:

query := bson.M{
  "$lookup": bson.M{ // lookup the documents table here
  "from":         "documents",
  "localField":   "_id",
  "foreignField": "folderID",
  "as":           "documents",
},
  "$match": bson.M{
    "level":   bson.M{"$gte": user.Level}, // filter by level
    "userIDs": user.ID,                    // filter by user
  },
}

pipe := collection.Pipe(query) // querying the "folders" collection
err := pipe.All(&result)

I always get the same error: wrong type for field (pipeline) 3 != 4

If I understand correctly, it's because it can't properly parse the result back into the $result object. I've done everything I can to ensure the struct has the exact structure that is required. I've also tried to pass in a genereric []interface{} and an empty bson.M{} objects. Still receive the same error.

Below is my Folders struct:

type Folder struct {
  ID        bson.ObjectId   `json:"id" bson:"_id"`
  Name      string          `json:"name"`
  Level     int             `json:"level"`
  UserIDs   []bson.ObjectId `json:"userIDs" bson:"userIDs"`
  Users     []User          `json:"-" bson:"-"` // doesn't get stored in the database
  Documents []Document      `json:"-" bson:"-"` // doesn't get stored in the database
}

I've also removed the $match clause to see if I could get anything at all back from that $lookup query. But I still get the same error.

Perhaps the mgo package doesn't support $lookup? If so, would there be another way? Perhaps I could send the raw query text to mongo and receive the raw response and parse it myself?

答案1

得分: 8

找到解决方案!

关键是在一个切片([]bson.M)中创建查询,并稍微改变查询的结构:

query := []bson.M{{
  "$lookup": bson.M{ // 在这里查找documents表
    "from":         "documents",
    "localField":   "_id",
    "foreignField": "folderID",
    "as":           "documents",
  }},
  {"$match": bson.M{
    "level":    bson.M{"$lte": user.Level},
    "userIDs":  user.ID,
  }},
}

pipe := collection.Pipe(query)
err := pipe.All(&folders)

我在mgo的Pipe函数文档中找到了一些线索。此外,我还必须更改Folders结构中Documents字段的标签,以便mgo可以填充该字段:

type Folder struct {
  ID        bson.ObjectId   `json:"id" bson:"_id"`
  Name      string          `json:"name"`
  Level     int             `json:"level"`
  UserIDs   []bson.ObjectId `json:"userIDs" bson:"userIDs"`
  Users     []User          `json:"-" bson:"-"`
  Documents []Document      // `json:"-" bson:"-"` 我移除了这部分,以便mgo可以正确地解组文档
}

现在我只需要找到一种方法,在保存Folder时不将Documents字段存储在数据库中。

英文:

Found the solution!

The trick was to create the query in a slice ([]bson.M) and change the structure of the query a bit:

query := []bson.M{{
  "$lookup": bson.M{ // lookup the documents table here
    "from":         "documents",
	"localField":   "_id",
	"foreignField": "folderID",
	"as":           "documents",
  }},
  {"$match": bson.M{
	"level": bson.M{"$lte": user.Level},
	"userIDs": user.ID,
}}}

pipe := collection.Pipe(query)
err := pipe.All(&folders)

I found a clue in mgo's Pipe function docs. Also, I had to change the tags for the Documents field in my Folders struct for mgo to pupolate that field:

type Folder struct {
  ID        bson.ObjectId   `json:"id" bson:"_id"`
  Name      string          `json:"name"`
  Level     int             `json:"level"`
  UserIDs   []bson.ObjectId `json:"userIDs" bson:"userIDs"`
  Users     []User          `json:"-" bson:"-"` // doesn't get stored in the database
  Documents []Document      // `json:"-" bson:"-" Removed this so that mgo can unmarshal
                            // the documents correctly
}

Now I just have to figure out a way to not store the Documents field in the database when I save a Folder.

huangapple
  • 本文由 发表于 2016年11月8日 18:15:13
  • 转载请务必保留本文链接:https://go.coder-hub.com/40484271.html
匿名

发表评论

匿名网友

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

确定