@@ -1,58 +0,0 @@ 06.golang使用$in或$nin查询MongoDB是否在数组内的数据 | 凤凰涅槃进阶之路

06.golang使用$in或$nin查询MongoDB是否在数组内的数据

Abel sun2023年1月6日约 1139 字大约 4 分钟

06.golang使用$in或$nin查询MongoDB是否在数组内的数据

前言

开发cmdb系统的时候,有一个场景是A对象数据关联B对象的数据,此时有一个接口需要透出已经在(或者不在)A对象某条数据关联列表的B对象的数据。

因为两边都是一个列表对象,如果单纯使用代码的思路来解决,大概会是下边这样:

  1. 首先查询A对象这条数,能够拿到关联的B对象的数据ID。
  2. 然后查询B对象对应的所有数据,遍历这数据,判断是否已经在A绑定的列表中。

这个思路虽然能够解题,但是不够优雅,后来看到MongoDB有一个$in$nin的方法,能够很方便的满足这个需求。

  • $in:相当于关系型数据库中的in()查询,但$in操作符指定查询对象是一个数组。
  • $nin:与$in一样,只不过是取反的意思。

实践

原始语句

首先准备三条B对象的数据:

db.users.insert([{"name":"aaa","age":20},{"name":"bbb","age":2},{"name":"ccc","age":30}]);

插入之后三条数据如下:

/* 1 */
{
    "_id" : ObjectId("6219c18d6e5030b4a6caa42b"),
    "name" : "aaa",
    "age" : 20.0
}

/* 2 */
{
    "_id" : ObjectId("6219c18d6e5030b4a6caa42c"),
    "name" : "bbb",
    "age" : 2.0
}

/* 3 */
{
    "_id" : ObjectId("6219c2016e5030b4a6caa42d"),
    "name" : "ccc",
    "age" : 30.0
}

然后插入一条A对象的数据:

db.groups.insert([{"name":"ops","nick_name":"运维","users":[]}]);

输入插入之后如下:

/* 1 */
{
    "_id" : ObjectId("6219c27c6e5030b4a6caa42e"),
    "name" : "ops",
    "nick_name" : "运维",
    "users" : []
}

这个时候我们可以看到在ops组里的用户为空,那么在我们将用户往组里添加的时候,需要获取到不在这个组内的用户,可以使用如下语句:

$ db.getCollection('users').find({"_id": {"$nin": []}})

/* 1 */
{
    "_id" : ObjectId("6219c18d6e5030b4a6caa42b"),
    "name" : "aaa",
    "age" : 20.0
}

/* 2 */
{
    "_id" : ObjectId("6219c18d6e5030b4a6caa42c"),
    "name" : "bbb",
    "age" : 2.0
}

/* 3 */
{
    "_id" : ObjectId("6219c2016e5030b4a6caa42d"),
    "name" : "ccc",
    "age" : 30.0
}

同理如果直接将$nin改掉,则可以看到获取到的数据为空:

$ db.getCollection('users').find({"_id": {"$in": []}})
Fetched 0 record(s) in 3ms

golang代码

接着我们将如上的查询转换成golang代码,这里仅展示主要查询的思路代码:

type Group struct {
 Name     string   `json:"name" bson:"name"`
 NickName string   `json:"nick_name" bson:"nick_name"`
 Users    []string `json:"users" bson:"users"`
}

type User struct {
 Name string `json:"name" bson:"name"`
 Age  int    `json:"age" bson:"age"`
}

func FindTest() {
 var group Group
 table := DB.Collection("groups")
 res := table.FindOne(ctx, bson.M{"name": "ops"})
 if err := res.Err(); err != nil {
  fmt.Printf("find data failed: %v\n", err)
 }
 if err := res.Decode(&group); err != nil {
  fmt.Printf("decode data failed: %v\n", err)
 }

 var alreadyLinks []primitive.ObjectID
 for _, v := range group.Users {
  objid, err := primitive.ObjectIDFromHex(v)
  if err != nil {
   fmt.Printf("%v\n", err)
  }
  alreadyLinks = append(alreadyLinks, objid)
 }

 filter := bson.D{}
 filter = append(filter, bson.E{Key: "_id", Value: bson.M{"$in": alreadyLinks}})
 users, err := ListUser(filter, options.FindOptions{})
 if err != nil {
  fmt.Printf("get data failed: %v\n", err)
 }
 for _, v := range users {
  fmt.Printf("用户名: %v 年龄: %v\n", v.Name, v.Age)
 }
}

// ListUser 获取用户列表
func ListUser(filter bson.D, options options.FindOptions) ([]*User, error) {
 table := DB.Collection("users")
 cus, err := table.Find(ctx, filter, &options)
 if err != nil {
  fmt.Printf("find data failed: %v\n", err)
 }
 defer func(cus *mongo.Cursor, ctx context.Context) {
  err := cus.Close(ctx)
  if err != nil {
   return
  }
 }(cus, ctx)

 list := make([]*User, 0)
 for cus.Next(ctx) {
  user := new(User)
  err := cus.Decode(&user)
  if err != nil {
   fmt.Printf("decode data failed: %v\n", err)
  }
  list = append(list, user)
 }

 return list, nil
}

此时运行代码,会报一个错误:find data failed: (BadValue) $nin needs an array

当我们使用这个方法的时候,MongoDB要求数组里边至少有一个值,如果没有值,则可以塞一个空的值进去用于聚合,因此加入下边的判断代码:

 //如果数组为空,则需要为其填充一个空的ID,否则会报错  $nin needs an array
 if len(alreadyLinks) == 0 { 
  alreadyLinks = append(alreadyLinks, primitive.NilObjectID)
 }

此时再次运行,可以得到如下结果:

$ go run main.go
用户名:aaa 年龄:20
用户名:bbb 年龄:2
用户名:ccc 年龄:30

可以看到跟我们用sql查询的结果是一致的。

再验证

此时我们将aaa用户的ID塞进ops组内,数据如下:

$ db.getCollection('groups').find({})
/* 1 */
{
    "_id" : ObjectId("6219c2c46e5030b4a6caa42f"),
    "name" : "ops",
    "nick_name" : "运维",
    "users" : [ 
        "6219c18d6e5030b4a6caa42b"
    ]
}

然后查询已经在组内的用户:

$ db.getCollection('users').find({"_id": {"$in": [ObjectId("6219c18d6e5030b4a6caa42b")]}})

/* 1 */
{
    "_id" : ObjectId("6219c18d6e5030b4a6caa42b"),
    "name" : "aaa",
    "age" : 20.0
}

查询不在组内的用户:

$ db.getCollection('users').find({"_id": {"$nin": [ObjectId("6219c18d6e5030b4a6caa42b")]}})

/* 1 */
{
    "_id" : ObjectId("6219c18d6e5030b4a6caa42c"),
    "name" : "bbb",
    "age" : 2.0
}

/* 2 */
{
    "_id" : ObjectId("6219c2016e5030b4a6caa42d"),
    "name" : "ccc",
    "age" : 30.0
}

此时运行我们的代码,可以看到相同的想要的结果:

$ go run main.go
用户名: bbb 年龄: 2
用户名: ccc 年龄: 30

这个例子就是结合MongoDB提供的方法,来解决实际场景中的问题的,能够更加优雅地解决问题,也能节约一定的资源开销。

评论
  • 按正序
  • 按倒序
  • 按热度
Powered by Waline v2.9.1