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
插入之后三条数据如下:
/* 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
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
然后插入一条 A 对象的数据:
db.groups.insert([{"name":"ops","nick_name":"运维","users":[]}]);
1
输入插入之后如下:
/* 1 */
{
"_id" : ObjectId("6219c27c6e5030b4a6caa42e"),
"name" : "ops",
"nick_name" : "运维",
"users" : []
}
1
2
3
4
5
6
7
2
3
4
5
6
7
这个时候我们可以看到在 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
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
同理如果直接将$nin 改掉,则可以看到获取到的数据为空:
$ db.getCollection('users').find({"_id": {"$in": []}})
Fetched 0 record(s) in 3ms
1
2
2
# 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
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
此时运行代码,会报一个错误:find data failed: (BadValue) $nin needs an array
。
当我们使用这个方法的时候,MongoDB 要求数组里边至少有一个值,如果没有值,则可以塞一个空的值进去用于聚合,因此加入下边的判断代码:
//如果数组为空,则需要为其填充一个空的ID,否则会报错 $nin needs an array
if len(alreadyLinks) == 0 {
alreadyLinks = append(alreadyLinks, primitive.NilObjectID)
}
1
2
3
4
2
3
4
此时再次运行,可以得到如下结果:
$ go run main.go
用户名:aaa 年龄:20
用户名:bbb 年龄:2
用户名:ccc 年龄:30
1
2
3
4
2
3
4
可以看到跟我们用 sql 查询的结果是一致的。
# 再验证
此时我们将 aaa 用户的 ID 塞进 ops 组内,数据如下:
$ db.getCollection('groups').find({})
/* 1 */
{
"_id" : ObjectId("6219c2c46e5030b4a6caa42f"),
"name" : "ops",
"nick_name" : "运维",
"users" : [
"6219c18d6e5030b4a6caa42b"
]
}
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
然后查询已经在组内的用户:
$ db.getCollection('users').find({"_id": {"$in": [ObjectId("6219c18d6e5030b4a6caa42b")]}})
/* 1 */
{
"_id" : ObjectId("6219c18d6e5030b4a6caa42b"),
"name" : "aaa",
"age" : 20.0
}
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
查询不在组内的用户:
$ 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
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
此时运行我们的代码,可以看到相同的想要的结果:
$ go run main.go
用户名: bbb 年龄: 2
用户名: ccc 年龄: 30
1
2
3
2
3
这个例子就是结合 MongoDB 提供的方法,来解决实际场景中的问题的,能够更加优雅地解决问题,也能节约一定的资源开销。
上次更新: 2024/11/19, 23:11:42