pymongo的基本操作
前言
前面有了 mongodb命令操作学习 以及 pymongo单例连接池的学习,
接下来就学习pymongo 的增删查改操作,具体源码地址:https://gitee.com/allen-huang/python/blob/master/python-code/do-mongodb/test_curd.py
新增
1、新增一条记录
- 格式:
集合.insert_one(<字典结构>)
- 测试用例代码
def test_insert_one(self):
"""
单条插入数据
@return:
"""
insert_data = {
"uid": 1,
"name": "0001",
"real_name": "黄飞鸿",
"email": "000@sina.cn",
"age": 30,
"sex": 1,
}
res = MongoPool().test.user.insert_one(insert_data)
print(res.inserted_id)
pass
2、新增多条记录
- 格式:
集合.insert_one(<列表结构>)
- 测试用例代码:
def test_insert_many(self):
"""
批量插入数据
@return:
"""
user_list = [
{"uid": 2, "name": "0002", "real_name": "张三", "email": "001@sina.cn", "age": 29, "sex": 1},
{"uid": 3, "name": "0003", "real_name": "魏延", "email": "002@sina.cn", "age": 28, "sex": 1},
{"uid": 4, "name": "0004", "real_name": "刘备", "email": "003@sina.cn", "age": 38, "sex": 1},
{"uid": 5, "name": "0005", "real_name": "关羽", "email": "004@sina.cn", "age": 32, "sex": 1},
{"uid": 6, "name": "0006", "real_name": "张飞", "email": "005@sina.cn", "age": 33, "sex": 1},
{"uid": 7, "name": "0007", "real_name": "赵云", "email": "006@sina.cn", "age": 21, "sex": 1},
{"uid": 8, "name": "0008", "real_name": "马超", "email": "007@sina.cn", "age": 22, "sex": 1},
{"uid": 9, "name": "0009", "real_name": "黄忠", "email": "008@sina.cn", "age": 24, "sex": 1},
{"uid": 10, "name": "0010", "real_name": "孙尚香", "email": "009@sina.cn", "age": 28, "sex": 2},
{"uid": 11, "name": "0011", "real_name": "黄月英", "email": "0010@sina.cn", "age": 27, "sex": 2},
{"uid": 12, "name": "0012", "real_name": "诸葛亮", "email": "0011@sina.cn", "age": 29, "sex": 1},
]
res = MongoPool().test.user.insert_many(user_list)
print(res.inserted_ids)
pass
3、自定义_id 的新增
def test_insert_custom_ids(self):
"""
批量插入数据,自定义_id
@return:
"""
ids_list = [
{"_id": 1, "name": "曹操"},
{"_id": 2, "name": "曹丕"},
{"_id": 3, "name": "典韦"},
{"_id": 4, "name": "许诸"}
]
res = MongoPool().test.user_ids.insert_many(ids_list)
print(res.inserted_ids)
pass
更新
1、更新一条记录
- 格式:
第三个参数 upsert=True 表示存在更新,不存在就新增
集合.update_one(
filter=<更新条件>,
update=<更新的数据属于字典结构>,
upsert=True|False
)
- 不带 upsert测试用例代码:
def test_update_one(self):
"""
更新一条记录
@return:
"""
# 更新条件
condition = {"uid": 5}
# 更新的数据
update_data = {"email": "0004@163.com"}
res = MongoPool().test.user.update_one(filter=condition, update={"$set": update_data})
print(res.modified_count)
pass
- 带上 upsert测试用例代码:
def test_update_upsert(self):
"""
更新一条记录,存在更新,不存在新增
@return:
"""
# 更新的数据
update_data = {
"uid": 13,
"name": "0013",
"real_name": "铁桥三",
"email": "013sina.cn",
"age": 27,
"sex": 1,
}
# 更新条件
condition = {"uid": 13}
# 更新的选项,存在更新,不存在新增
upsert_option = True
res = MongoPool().test.user.update_one(filter=condition, update={"$set": update_data}, upsert=upsert_option)
print(res)
pass
2、更新多条记录
- 格式:
格式和更新一条数据差不多
集合.update_many(
filter=<更新条件>,
update=<更新的数据属于字典结构>,
upsert=True|False
)
- 测试用例1:
def test_update_many(self):
"""
更新多条记录,更新年龄大于30岁的人,武力值更新为95
@return:
"""
# 更新的数据
update_data = {"$set": {"force_value": 95}}
# 更新条件
condition = {"age": {"$gt": 30}}
# 更新选项,存在更新,不存在就新增
upsert_option = True
res = MongoPool().test.user.update_many(filter=condition, update=update_data, upsert=upsert_option)
print(res)
pass
- 测试用例2:
def test_update_many_init(self):
"""
更新多条记录,筛选出name包含000的人员,武力值更新为90,初始化登录次数
@return:
"""
# 更新的数据,$setOnInsert类似新增数据后进行初始化操作
update_data = {"$set": {"force_value": 90}, "$setOnInsert": {"login_nums": 1}}
# 更新条件
condition = {"name": {"$regex": "^000"}}
# 更新选项,存在更新,不存在就新增
upsert_option = True
res = MongoPool().test.user.update_many(filter=condition, update=update_data, upsert=upsert_option)
print(res)
pass
删除
删除一条记录
- 格式:
集合.delete_one(<删除条件>)
- 测试用例
def test_delete_one(self):
"""
删除单条数据
@return:
"""
res = MongoPool().test.user.delete_one({'uid': 1})
print(res.deleted_count)
删除多条记录
- 格式:
集合.delete_many(<删除条件>)
- 测试用例
def test_delete_many(self):
"""
批量删除uid<=2的记录
@return:
"""
condition = {"uid": {"$lte": 2}}
res = MongoPool().test.user.delete_many(condition)
print(res)
查询
查询涉及到条件,运算符,正则表达式查询,排序,投影(查询部分字段)
find_one([查询条件]) 查询一条记录
find([查询条件]) 查询多条记录
条件查询
- 单个条件
def test_find_one(self):
"""
查询一条数据
@return:
"""
coll = MongoPool().test.user
data = coll.find_one({"uid": 1})
print(data)
pass
- 多个条件
是 and 条件的简写
def test_find_more_filter(self):
"""
查询sex=1,年龄大于30岁的数据
@return:
"""
coll = MongoPool().test.user
data_list = coll.find({"sex": 1, "age": {"$gte": 30}})
for data in data_list:
print(data)
- and条件
格式:集合对象.find({“$and” : [{条件1},{条件2}…]})
多个条件是放在一个列表中去的
def test_find_and(self):
"""
and条件,查询sex=1并且年龄大于30岁的数据
@return:
"""
coll = MongoPool().test.user
# 多个and条件用列表表示
condition = [
{"sex": 1},
{"age": {"$gte": 30}}
]
list_data = coll.find({"$and": condition})
for data in list_data:
pprint(data)
- or条件
格式:集合对象.find({“$and” : [{条件1},{条件2}…]})
多个条件是放在一个列表中去的
def test_find_or(self):
"""
或条件,查询sex=2或者年龄大于35岁的数据
@return:
"""
coll = MongoPool().test.user
# 多个or条件用列表表示
condition = [
{"sex": 2},
{"age": {"$gte": 35}}
]
list_data = coll.find({"$or": condition})
for data in list_data:
pprint(data)
根据运算符查询
- 大于
def test_find_gt(self):
"""
查询age大于30岁的数据
@return:
"""
coll = MongoPool().test.user
data_list = coll.find({"age": {"$gt": 30}})
for data in data_list:
print(data["uid"])
- 大于等于
def test_find_gte(self):
"""
查询 age 大于等于35岁的数据
@return:
"""
data_list = MongoPool().test.user.find({"age": {"$gte": 35}})
for data in data_list:
print(data["real_name"])
- 小于
def test_find_lt(self):
"""
查询 age 小于30岁的数据
@return:
"""
data_list = MongoPool().test.user.find({"age": {"$lt": 30}})
for data in data_list:
print(data)
- 小于等于
def test_find_lte(self):
"""
查询 age 小于等于27岁的数据
@return:
"""
data_list = MongoPool().test.user.find({"age": {"$lte": 27}})
for data in data_list:
print(data)
- 不等于
def test_find_ne(self):
"""
查询sex!=1的数据
@return:
"""
coll = MongoPool().test.user
data_list = coll.find({"sex": {"$ne": 1}})
for data in data_list:
print(data)
根据范围查找
- 数字范围start<=num<=end
def test_find_between(self):
"""
查询 age 在 20到25 之间的数据
@return:
"""
data_list = MongoPool().test.user.find({"age": {"$gte": 20, "$lte": 25}})
for data in data_list:
print(data)
- 数组范围
def test_find_range(self):
"""
范围查找,指定 uid来查找
@return:
"""
data_list = MongoPool().test.user.find({"uid": {"$in": [1, 2, 4, 5]}})
for data in data_list:
print(data)
根据正则表达式查询
- 类似 mysql 的 like 查找
def test_find_like(self):
"""
查询 name 包含 000的数据,类似mysql like
这和mongodb的shell命令行不同,mongodb 的 shell 命令行是/000/
"""
data_list = MongoPool().test.user.find({"name": {"$regex": "000"}})
for data in data_list:
print(data)
- 以某个字符串开头
def test_find_pos(self):
"""
查找邮箱以某个字符串001开头的数据
@return:
"""
data_list = MongoPool().test.user.find({"email": {"$regex": "^001"}})
for data in data_list:
print(data)
- 以某个字符串结尾
def test_find_rpos(self):
"""
查找邮箱以某个字符串163.com结尾的数据
@return:
"""
data_list = MongoPool().test.user.find({"email": {"$regex": "163.com$"}})
for data in data_list:
print(data)
投影
投影是查询若干个字段,不是所有字段的数据
注意:除了_id之外,其他字段的投影操作只能全部是1或0,不能共存,否则会报错
def test_find_projection(self):
"""
投影查找,只返回uid,real_name,age,sex
注意:除了_id之外,其他字段的投影操作只能全部是1或0,不能共存,否则会报错
@return:
"""
# 筛选条件
condition = {"age": {"$lte": 25}}
# 字段_id 不显示,uid,real_name,age,sex显示
data_list = MongoPool().test.user.find(condition, {"_id": 0, "uid": 1, "real_name": 1, "age": 1, "sex": 1})
for data in data_list:
print(data)
排序
在MongoDB中对结果进行排序,可使用sort()方法。
该方法接受一个包含字段列表及其排序顺序的参数,要指定排序顺序,使用1和-1,1用于升序,-1用于降序。
- 格式:
注意:对于复合排序是,哪个字段在前,谁就优先
集合.find([查询条件]).sort({field1:1},{field2:-1})
- 单个排序
def test_find_sort(self):
"""
查找 sex=1,按年龄降序排序
@return:
"""
coll = MongoPool().test.user
data_list = coll.find({"sex": 1}).sort({"age": -1})
for data in data_list:
print(data)
- 复合排序
def test_find_comp_sort(self):
"""
复合排序,先按性别升序,再按武力值降序
注意:排序字段的顺序,决定了排序的优先级
@return:
"""
coll = MongoPool().test.user
data_list = coll.find({"age": {"$lt": 35}}).sort({"age": 1, "force_value": -1})
for data in data_list:
print(data)
统计数量
老版本:采用 集合.find({条件}).count()
新版本:采用 集合.count_documents({条件})
def test_find_count(self):
"""
查找总数,条件,年龄小于35岁的人数
@return:
"""
coll = MongoPool().test.user
count_num = coll.count_documents({"age": {"$lt": 35}})
print(count_num)
分页查询
mongodb 分页是使用 skip 和 limit来进行分页
skip(N),表示跳过前N条后的结果
limit(N),表示只显示前 N条的结果
skip(N)和 limit(N)不分先后,它始终以 skip 先执行,这和管道的操作不一样
def test_find_page(self):
"""
分页查找
@return:
"""
coll = MongoPool().test.user
page_size = 10
page_num = 1
skip_num = (page_num - 1) * page_size
data_list = coll.find().sort({"age": -1}).skip(skip_num).limit(page_size)
for data in data_list:
print(data)
管道聚合
以下的操作还是以上面的 user 集合为例:
聚合:
聚合(aggregate)是基于数据处理的聚合管道,每个文档通过一个由多个阶段(stage)组成的管道,可以对每个阶段的的管道进行分组,过滤等功能,然后经过一系列的处理,输出相应的结果。
管道
在存在多个管道时,比如第一个管道的产生结果会 作为 第二个管道的输入值,后面的以此类推,下面是这个管道的原理图(网上找的)
语法格式:db.集合名词.aggregate([{管道1:{表达式1}},{管道2:{表达式2}}…])
在 aggregate中的参数,因有多个管道,所以它是以数组形式存在
常用管道说明
管道类型
$group: 将集合中的文档分组,可用于统计结果
$match: 过滤数据,只输出符合条件的结果
$project: 修改输入文档的结构,如重命名、增加、删除字段、创建计算结果
$sort: 将输入文档排序后输出
$limit: 限制聚合管道返回的文档数
$skip: 跳过指定数量的文档,并返回余下的文档
$unwind: 将数组类型的字段进行拆分
表达式
作用:处理输入文档并输出
常用表达式
$sum: 计算某个字段总和,$sum:"$字段名",$sum:1表示计数
$avg: 计算某个字段的平均值,$avg:"$字段名"
$min: 获得某个字段最小值,$min:"$字段名"
$max: 获得某个字段最大值,$max:"$字段名"
$push: 在结果文档中添加值到一个数组
$addToSet 将值加入一个数组中,会判断是否有重复的值,若相同的值在数组中已经存在了,则不加入
$first: 根据资源文档的排序获取第一个文档数据
$last: 根据资源文档的排序获取最后一个文档的审计局
$group
作用:将集合中的文档分组,可用于统计结果。其中
_id
表示分组的标识,其后面使用某个字段的格式为$字段
$group 注意点:
1、分组需要放在_id
后面
2、对应的字典中有几个键,结果就有几个键
3、取不同字段的值需要使用$age
,$ sex
…这样子
4、取字典嵌套的字典中的值时,$_id.email
例子1,按性别统计人数
按男女性别进行分组,并统计出人数
在Mongodb Compass中执行结果:
python代码:
def test_aggr_group_sex(self):
"""
$group分组示例1,分组查找,按性别分组,统计每个性别的人数
@return:
"""
coll = MongoPool().test.user
groups = {
"$group": {
"_id": "$sex",
"user_count": {"$sum": 1}
}
}
# 这是一个游标对象,可以遍历出来
res = coll.aggregate([groups])
for data in res:
pprint(data)
上面的例子类似于 sql语句:
select sex,count(*) from user group by sex
例子2,按性别统计武力值总和:
按性别统计武力值的总和
按性别统计 “_id”: “$sex”
计算武力值的和:“字段和的别名”: {“$sum”: “$force_value”}
在Compass的结果
python 代码
def test_aggr_group_total(self):
"""
$group分组示例2,分组查找,统计每个性别的总武力值
@return:
"""
coll = MongoPool().test.user
# $sum 后面跟上要求和的字段
groups = {
"$group": {
"_id": "$sex",
"total_force_value": {"$sum": "$force_value"}
}
}
res = coll.aggregate([groups])
for data in res:
print(data)
翻译成 SQL 语句
select sex,sum(force_value) from user group by sex
例子3,按年龄统计平均武力值
统计每个年龄段,平均武力值
按年龄统计 “_id”: “$age”
计算平均值:“字段平均值的别名”: {“$avg”: “$force_value”}
在Compass 中的结果
python 代码
def test_aggr_group_avg(self):
"""
$group分组示例3,分组查找,统计每个年龄的平均武力值
@return:
"""
coll = MongoPool().test.user
groups = {
"$group": {
"_id": "$age",
"avg_force_value": {"$avg": "$force_value"}
}
}
res = coll.aggregate([groups])
for data in res:
print(data)
翻译成 SQL 语句
select age,avg(force_value) from user group by age
例子4,统计每个性别的最大武力值
统计每个性别的最大武力值
def test_aggr_group_max(self):
"""
分组查找,统计每个性别的最大武力值
@return:
"""
coll = MongoPool().test.user
groups = {
"$group": {
"_id": "$sex",
"max_force_value": {"$max": "$force_value"}
}
}
res = coll.aggregate([groups])
for data in res:
print(data)
例子5,统计不重复的姓名,武力值列表
统计每个年龄段的 不重复的姓名,武力值列表
Compass 的结果
python 代码
def test_aggr_group_addtoset(self):
"""
分组查找,统计每个年龄的用户名字,武力值列表
@return:
"""
coll = MongoPool().test.user
groups = {
"$group": {
"_id": "$age",
"real_names": {"$addToSet": "$real_name"},
"force_values": {"$addToSet": "$force_value"}
}
}
res = coll.aggregate([groups])
for data in res:
print(data)
$match
相当于是 mysql 中的 where 条件,用于筛选出符合条件的文档。
例子1,筛选出>27,<=35岁的人员信息
Compass 的结果:
单独按姓名查询
Python的代码
def test_aggr_match1(self):
"""
筛选出年龄大于27岁的人,小于等于35岁的人员信息
@return:
"""
coll = MongoPool().test.user
matches = {
"$match": {
"age": {"$gt": 27, "$lte": 35}
}
}
res = coll.aggregate([matches])
for data in res:
pprint(data)
例子2,筛选出性别为男,根据年龄段分组的武力值信息
Compass的结果
python代码:
def test_aggr_group_match(self):
"""
筛选出性别为男,根据年龄段分组的武力值信息组成数组,可以重复的
@return:
"""
coll = MongoPool().test.user
matches = {
"$match": {
"sex": 1
}
}
groups = {
"$group": {
"_id": "$age",
"force_values": {"$push": "$force_value"}
}
}
# 这里是先将match的结果作为输入,再进行group操作
res = coll.aggregate([matches, groups])
for data in res:
print(data)
翻译成 SQL 语句:
select age,group_concat(force_values) from user where sex=1 group by age
$project
这个就是查询的投影,查询出需要的字段
例子1,查询出年龄大于30岁的用户编号,真实姓名,武力值
compass的结果
python代码
def test_aggr_project(self):
"""
投影查找,只返回uid,real_name,force_value
注意:除了_id之外,其他字段的投影操作只能全部是1或0,不能共存,否则会报错
@return:
"""
coll = MongoPool().test.user
# 筛选条件
matches = {
"$match": {
"age": {"$gt": 30}
}
}
# 需要查询的字段
project = {
"$project": {
"_id": 0,
"uid": 1,
"real_name": 1,
"force_value": 1
}
}
res = coll.aggregate([matches, project])
for data in res:
print(data)
翻译成 SQL 语句
select uid,real_name,force_value from user where age > 30
sort
对管道内的结果进行排序操作
例子1:对性别为男的人,进行武力值分组,并武力值进行倒序排
compass的结果
python代码
def test_aggr_sort(self):
"""
排序查找,按武力值降序排序
@return:
"""
coll = MongoPool().test.user
matches = {
"$match": {"sex": 1}
}
groups = {
"$group": {
"_id": "$age",
"force_values": {"$sum": "$force_value"},
"force_value_avg": {"$avg": "$force_value"}
}
}
# 根据前面的 match,group 的结果,再进行排序操作
sort = {
"$sort": {"force_value_avg": -1}
}
res = coll.aggregate([matches, groups, sort])
for data in res:
print(data)
SQL语句和聚合对应表
SQL语句 | Mongodb 的聚合操作 |
---|---|
SELECT | $project |
WHERE | $match |
GROUP BY | $group |
Having | $match |
ORDER BY | $sort |
SUM(字段名) | $sum:“$字段名” |
COUNT(*) | $sum:1 |
AVG(字段名) | $avg:“$字段名” |
MAX(字段名) | $max:“$字段名” |
MIN(字段名) | $min:“$字段名” |