06.golang使用$in或$nin查询MongoDB是否在数组内的数据
2023年1月6日约 1139 字大约 4 分钟
06.golang使用$in或$nin查询MongoDB是否在数组内的数据
前言
开发cmdb系统的时候,有一个场景是A对象数据关联B对象的数据,此时有一个接口需要透出已经在(或者不在)A对象某条数据关联列表的B对象的数据。
因为两边都是一个列表对象,如果单纯使用代码的思路来解决,大概会是下边这样:
- 首先查询A对象这条数,能够拿到关联的B对象的数据ID。
- 然后查询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