二丫讲梵 二丫讲梵
首页
  • 最佳实践
  • 迎刃而解
  • Nginx
  • Php
  • Zabbix
  • AWS
  • Prometheus
  • Grafana
  • CentOS
  • Systemd
  • Docker
  • Rancher
  • Ansible
  • Ldap
  • Gitlab
  • GitHub
  • Etcd
  • Consul
  • RabbitMQ
  • Kafka
  • MySql
  • MongoDB
  • OpenVPN
  • KVM
  • VMware
  • Other
  • ELK
  • K8S
  • LLM
  • Nexus
  • Jenkins
  • 随写编年
  • 家人物语
  • 追忆青春
  • 父亲的朋友圈
  • 电影音乐
  • 效率工具
  • 博客相关
  • Shell
  • 前端实践
  • Vue学习笔记
  • Golang学习笔记
  • Golang编程技巧
  • 学习周刊
  • Obsidian插件周刊
关于
友链
  • 本站索引

    • 分类
    • 标签
    • 归档
  • 本站页面

    • 导航
    • 打赏
  • 我的工具

    • 备忘录清单 (opens new window)
    • json2go (opens new window)
    • gopher (opens new window)
    • 微信MD编辑 (opens new window)
    • 国内镜像 (opens new window)
    • 出口IP查询 (opens new window)
    • 代码高亮工具 (opens new window)
  • 外站页面

    • 开往 (opens new window)
    • ldapdoc (opens new window)
    • HowToStartOpenSource (opens new window)
    • vdoing-template (opens new window)
GitHub (opens new window)

二丫讲梵

行者常至,为者常成
首页
  • 最佳实践
  • 迎刃而解
  • Nginx
  • Php
  • Zabbix
  • AWS
  • Prometheus
  • Grafana
  • CentOS
  • Systemd
  • Docker
  • Rancher
  • Ansible
  • Ldap
  • Gitlab
  • GitHub
  • Etcd
  • Consul
  • RabbitMQ
  • Kafka
  • MySql
  • MongoDB
  • OpenVPN
  • KVM
  • VMware
  • Other
  • ELK
  • K8S
  • LLM
  • Nexus
  • Jenkins
  • 随写编年
  • 家人物语
  • 追忆青春
  • 父亲的朋友圈
  • 电影音乐
  • 效率工具
  • 博客相关
  • Shell
  • 前端实践
  • Vue学习笔记
  • Golang学习笔记
  • Golang编程技巧
  • 学习周刊
  • Obsidian插件周刊
关于
友链
  • 本站索引

    • 分类
    • 标签
    • 归档
  • 本站页面

    • 导航
    • 打赏
  • 我的工具

    • 备忘录清单 (opens new window)
    • json2go (opens new window)
    • gopher (opens new window)
    • 微信MD编辑 (opens new window)
    • 国内镜像 (opens new window)
    • 出口IP查询 (opens new window)
    • 代码高亮工具 (opens new window)
  • 外站页面

    • 开往 (opens new window)
    • ldapdoc (opens new window)
    • HowToStartOpenSource (opens new window)
    • vdoing-template (opens new window)
GitHub (opens new window)
  • Shell编程

  • Go编程笔记

    • 开发技巧

      • go日常开发代码片段
      • golang交叉编译
      • 两个切片内容相减的几种方法
      • golang以结构体中某个字段进行排序
      • vscode开发golang报黄提示composite literal uses unkeyed fields
      • golang使用$in或$nin查询MongoDB是否在数组内的数据
        • 前言
        • 实践
          • 原始语句
          • golang 代码
          • 再验证
      • golang使用$push和$addToSet往数组添加字段的异同
      • MongoDB自增ID在golang中的实践
      • golang数据类型转换汇总
      • 记录VSCode中写Go代码切换Sqlite无CGO依赖版本的过程以及遇到的五个问题
      • 企业微信自建应用-golang校验回调
      • 对接腾讯云未集成到SDK的接口开发实践小记
      • Go开发实践之Gin框架将前端的dist目录embed到二进制
    • 库包研究

    • 个人项目

  • 前端编程笔记

  • Go学习笔记

  • Vue-21年学习笔记

  • Vue-22年重学笔记

  • 编程世界
  • Go编程笔记
  • 开发技巧
二丫讲梵
2022-02-26
目录

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

插入之后三条数据如下:

/* 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

然后插入一条 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

这个时候我们可以看到在 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

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

$ db.getCollection('users').find({"_id": {"$in": []}})
Fetched 0 record(s) in 3ms
1
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

此时运行代码,会报一个错误: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

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

$ go run main.go
用户名:aaa 年龄:20
用户名:bbb 年龄:2
用户名:ccc 年龄:30
1
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

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

$ 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

查询不在组内的用户:

$ 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

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

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

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

微信 支付宝
#MongoDB
上次更新: 2024/07/04, 22:40:37
vscode开发golang报黄提示composite literal uses unkeyed fields
golang使用$push和$addToSet往数组添加字段的异同

← vscode开发golang报黄提示composite literal uses unkeyed fields golang使用$push和$addToSet往数组添加字段的异同→

最近更新
01
学习周刊-总第213期-2025年第22周
05-29
02
学习周刊-总第212期-2025年第21周
05-22
03
从赵心童世锦赛夺冠聊聊我的斯诺克情缘
05-16
更多文章>
Theme by Vdoing | Copyright © 2017-2025 | 点击查看十年之约 | 浙ICP备18057030号
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式