mongo数据库CRUD

11 篇文章 0 订阅
5 篇文章 0 订阅

#准备

从官网下载合适的安装包。这里以win10为例,一路next即可完成安装。安装完成后,进入这个目录:

C:\Program Files\MongoDB\Server\4.0\bin\

在当前目录打开PowerShell窗口,先启动服务端:

 .\mongod.exe

服务端默认在本地的27017端口运行。

启动客户端:

 .\mongo.exe

将默认连接本地27017端口的服务端,并启动mongo shell交互式命令行窗口。

如果想指定端口或者连接远程的服务,可以在启动时指定其他参数,比如:

 .\mongo.exe --host www.example.com --port 28015

显示当前使用的数据库:

db
# 默认数据库是test

切换数据库:

use <database>

你也可以切换到一个不存在的数据库。因为当你往其中插入数据时,mongo将自动创建这个数据库(或者集合),比如:

> use first
switched to db first
> db.test_coll.insertOne({x:1})
{
        "acknowledged" : true,
        "insertedId" : ObjectId("5c52cdf38410c398987c5c0e")
}

上述的insertOne()操作将同时创建数据库first和集合test_coll

PyMongo

连接和指定数据库

PyMongo是mongo的python驱动,安装好之后我们连接MongoDB

方式一:指定主机和端口

import pymongo
client = pymongo.MongoClient(host='localhost', port=27017)

方式二:mongodb连接字符串

client = pymongo.MongoClient('mongodb://localhost:27017/')
db = db.test  #  默认数据库是test

插入数据

数据库

默认数据库是test,我们也可以指定其他数据库,比如school

client = pymongo.MongoClient('mongodb://localhost:27017/')
db = client.school
# 或者
db = client['school']

注意,可以直接连接一个不存在的数据库。mongo会在需要时(比如插入数据),创建这个数据库。

集合

mongo中的集合(collection),类似于SQL中的表(table)。指定集合的操作,与指定数据库的方式类似:

collection = db.teacher
# 或者
collection = db['teacher']

注意,可以指定一个不存在的集合。mongo会在需要时(比如插入数据),创建这个集合。

文档

MongoDB将数据存储为BSON文档(BSON是JSON文档的二进制表示,但是包含更多的数据类型)。文档由字段(field)和值(value)组成,具有如下结构:

{
   field1: value1,
   field2: value2,
   field3: value3,
   ...
   fieldN: valueN
}

字段名是字符串,具有如下约束条件:

  • _id是保留字段,用作主键,必须保证唯一且不可以变(因此不能是数组)
  • 字段名不可包含null字符
  • 不要以$.开头
  • 字段名不可重复

在PyMongo中,使用python的字典表示文档,例如下面字典可以用来表示一篇博文:

>>> import datetime
>>> post = {"author": "Mike",
...         "text": "My first blog post!",
...         "tags": ["mongodb", "python", "pymongo"],
...         "date": datetime.datetime.utcnow()}

值得注意的是,文档可以包含python中的原生类型(比如datetime.datetime实例),PyMongo会将其自动转化为合适的BSON类型。

MongoDB使用.点来访问数组或嵌套文档中的元素:

{
   ...
   contribs: [ "Turing machine", "Turing test", "Turingery" ],
   ...
}

如果要访问contribs字段的第三个元素,使用contribs.2

{
   ...
   name: { first: "Alan", last: "Turing" },
   contact: { phone: { type: "cell", number: "111-222-3333" } },
   ...
}

如果要访问name字段的last字段,使用name.last

文档的限制

  • BSON文档的最大大小是16MB
  • 字段的顺序:MongoDB保持写操作时的字段顺序,除了以下情况:
    • _id字段总是文档的第一个字段
    • 重新命名字段等更新操作可能影响字段排序
  • _id字段:
    • 如果插入文档没有指定_id字段,MongoDB会自动创建ObjectId对象作为该字段的值
    • 创建集合(collection)时,MongoDB默认为该字段创建唯一索引
    • 该字段的值通常是:
      • ObjectId
      • 自增数字
      • 代码生成的UUID

插入数据

MongoDB的所有写操作,在当个文档上是原子性的。插入文档时,如果集合不存在,MongoDB会自动创建。

db.collection.insert()

下面我们往teacher这个集合中插入一条数据

# 数据用字典结构
teacher = {
    'name': 'Sven',
    'gender': 'male',
    'major': 'Math'
}

# 插入成功后,返回该条数据的 _id
res = db.teacher.insert(teacher)  # 5c53c429bddaf0ac186d935b

插入多条数据

teacher1 = {
    'name': 'Sylvia',
    'gender': 'female',
    'major': 'Deutsch'
}

teacher2 = {
    'name': 'Lolle',
    'gender': 'female',
    'major': 'Geschichte'
}

res = db.teacher.insert([teacher1, teacher2]) 
# 返回_id的列表
# [ObjectId('5c53c628bddaf0b2bc028a73'), ObjectId('5c53c628bddaf0b2bc028a74')]

以上我们插入单条和多条都用insert方法,更推荐的方式是使用insert_one()insert_many()

db.collection.insert_one()

插入单条数据

res = db.inventory.insert_one(
    {"item": "canvas",
     "qty": 100,
     "tags": ["cotton"],
     "size": {"h": 28, "w": 35.5, "uom": "cm"}})

print(res)  # <pymongo.results.InsertOneResult object at 0x0332EF58>
print(res.inserted_id)  # 5c53c7a8bddaf0208cd858f5
db.collection.insert_many()

插入多条数据

res = db.inventory.insert_many([
    {"item": "journal",
     "qty": 25,
     "tags": ["blank", "red"],
     "size": {"h": 14, "w": 21, "uom": "cm"}},
    {"item": "mat",
     "qty": 85,
     "tags": ["gray"],
     "size": {"h": 27.9, "w": 35.5, "uom": "cm"}},
    {"item": "mousepad",
     "qty": 25,
     "tags": ["gel", "blue"],
     "size": {"h": 19, "w": 22.85, "uom": "cm"}}])

print(res)  
# <pymongo.results.InsertManyResult object at 0x031C3F30>
print(res.inserted_ids)  
# [ObjectId('5c53e48bbddaf04c44880d70'), ObjectId('5c53e48bbddaf04c44880d71'), ObjectId('5c53e48bbddaf04c44880d72')]

插入数据时,如果指定_id的值,那么该值不能和集合中已有文档的_id重复。

查询数据

查询使用db.collection.find()方法。

首先,准备一些原始数据

import pymongo

client = pymongo.MongoClient('mongodb://localhost:27017/')
db = client.test1

db.inventory.insert_many([
    {"item": "journal",
     "qty": 25,
     "size": {"h": 14, "w": 21, "uom": "cm"},
     "status": "A"},
    {"item": "notebook",
     "qty": 50,
     "size": {"h": 8.5, "w": 11, "uom": "in"},
     "status": "A"},
    {"item": "paper",
     "qty": 100,
     "size": {"h": 8.5, "w": 11, "uom": "in"},
     "status": "D"},
    {"item": "planner",
     "qty": 75, "size": {"h": 22.85, "w": 30, "uom": "cm"},
     "status": "D"},
    {"item": "postcard",
     "qty": 45,
     "size": {"h": 10, "w": 15.25, "uom": "cm"},
     "status": "A"}])

查询集合中的所有文档:find()

传入一个空文档作为find的查询条件即可

cursor = db.inventory.find({})

以上操作相当于SQL中的:

SELECT * FROM inventory

find方法返回一个Cursor实例,遍历它即可访问所有文档:

docs = [doc for doc in cursor]
"""
[{'status': 'A', 'item': 'journal', ...}, ...]
"""

查询单个文档:find_one()

find_one()方法返回满足查询条件的一个文档,或者返回None(如果没有匹配的结果)。适用于:

  • 你知道有且只有一个匹配的文档,比如通过ObjectId进行查询
  • 你只需要第一个匹配的文档

根据ObjectID对象查询

注意,ObjectId对象和它的字符串表现是完全不同的,后者不可直接用于查询

import pymongo
import datetime


client = pymongo.MongoClient(
    host='localhost',
    port=27017,
    retryWrites=True
)
db = client.test8

blog = {"author": "Ayhan",
        "text": "My first blog post!",
        "tags": ["mongodb", "python", "pymongo"],
        "date": datetime.datetime.utcnow()}

post_id = db.posts.insert_one(blog).inserted_id  # 返回ObjectId对象
print(post_id)  # 5c948354bddaf02674a54faa

res1 = db.posts.find_one({'_id': post_id})  # 通过ObjectId对象查询
print(res1)
"""
{
	'_id': ObjectId('5c948354bddaf02674a54faa'),
	'author': 'Ayhan',
	'text': 'My first blog post!',
	'tags': ['mongodb', 'python', 'pymongo'],
	'date': datetime.datetime(2019, 3, 22, 6, 40, 20, 164000)
}
"""

res2 = db.posts.find_one({'_id': str(post_id)}) # 通过ObjectId对象的字符串表现进行查询
print(res2)  # None

如果想通过ObjectId的字符串进行查询,需要将字符串转为对象:

from bson.objectid import ObjectId

object_id_str = '5c948354bddaf02674a54faa'
db.posts.find_one({'_id': ObjectId(object_id_str)})

指定相等条件

如果要指定相等条件,可以给find方法传入文档查询条件:

{ <field1>: <value1>, ... }

比如,下面的示例从inventory集合中查询所有符合status等于D的文档:

cursor = db.inventory.find({'status': 'D'})

上述操作相当于SQL中的

SELECT * FROM inventory WHERE status = "D"

使用查询运算符

文档查询条件:

{ <field1>: { <operator1>: <value1> }, ... }

下面的示例从inventory集合中查询所有status等于A或D的文档:

cursor = db.inventory.find({'status': {'$in': ['A', 'D']}})

以上查询相当于SQL中的:

SELECT * FROM inventory WHERE status in ("A", "D")

查询运算符一览,可查看官方文档:https://docs.mongodb.com/manual/reference/operator/query/#projection-operators

AND查询

复合查询可以指定多个字段。下面的示例从inventory集合中查询所有status等于A,并且qty小于30的文档:

cursor = db.inventory.find({'status': 'A', 'qty': {'$lt': 30}})

以上操作相当于SQL中的:

SELECT * FROM inventory WHERE status = "A" AND qty < 30

OR查询

使用$or运算符,可以进行逻辑或查询:

cursor = db.inventory.find(
    {'$or': [{'status': 'A'}, {'qty': {'$lt': 30}}]}
)

以上操作相当于SQL中的:

SELECT * FROM inventory WHERE status = "A" OR qty < 3

AND 和 OR 查询

cursor = db.inventory.find({
    'status': 'A',
    '$or': [{'qty': {'$lt': 30}}, {'item': {'$regex': '^p'}}]
})

以上操作相当于SQL中的:

SELECT * FROM inventory WHERE status = "A" AND ( qty < 30 OR item LIKE "p%")

嵌套查询

适用于文档中嵌套文档的查询。下面先填充数据:

import pymongo
from bson.son import SON

client = pymongo.MongoClient('mongodb://localhost:27017/')
db = client.test2

db.inventory.insert_many([
    {"item": "journal",
     "qty": 25,
     "size": SON([("h", 14), ("w", 21), ("uom", "cm")]),
     "status": "A"},
    {"item": "notebook",
     "qty": 50,
     "size": SON([("h", 8.5), ("w", 11), ("uom", "in")]),
     "status": "A"},
    {"item": "paper",
     "qty": 100,
     "size": SON([("h", 8.5), ("w", 11), ("uom", "in")]),
     "status": "D"},
    {"item": "planner",
     "qty": 75,
     "size": SON([("h", 22.85), ("w", 30), ("uom", "cm")]),
     "status": "D"},
    {"item": "postcard",
     "qty": 45,
     "size": SON([("h", 10), ("w", 15.25), ("uom", "cm")]),
     "status": "A"}])

注意,子文档键的顺序在这些示例中很重要,因此这里的子文档使用bson.son.SON的实例,而不是使用python的字典形式。

插入后,数据结构如下:

[
    {  // 文档
    "_id" : ObjectId("5c540556bddaf0b00803d342"),
    "status" : "A",
    "item" : "journal",
    "size" : {  // 子文档(嵌套文档)
        "h" : 14,
        "w" : 21,
        "uom" : "cm"
    },
    "qty" : 25
    }
]
根据子文档进行查询

如果某个字段是一个子文档,要为该字段指定相等条件时,使用如下文档查询条件:{ <field>: <value> },其中<value>是作为匹配条件的子文档。

下面的示例从inventory集合中查询size字段等于子文档{ h: 14, w:21, uom: "cm" }的文档

cursor = db.inventory.find(
    {'size': SON([("h", 14), ("w", 21), ("uom", "cm")])}
)
"""
[{
	u 'status': u 'A',
	u 'item': u 'journal',
	u '_id': ObjectId('5c540556bddaf0b00803d342'),
	u 'qty': 25,
	u 'size': {
		u 'h': 14,
		u 'uom': u 'cm',
		u 'w': 21
	}
}]
"""

注意:

根据子文档进行查询时,子文档必须完全匹配,包括字段的顺序。也就也是说,下面的查询无法匹配任何结果:

cursor = db.inventory.find(
    {"size": SON([("w", 21), ("h", 14), ("uom", "cm")])}
)
# []
根据嵌套字段进行查询

通过子文档的字段进行查询,需要使用.操作:field.nestedField

# 查询 size.uom 等于 in 的文档
cursor = db.inventory.find(
    {'size.uom': 'in'}
)
"""
[{
	u 'status': u 'A',
	u 'item': u 'notebook',
	u '_id': ObjectId('5c540556bddaf0b00803d343'),
	u 'qty': 50,
	u 'size': {
		u 'h': 8.5,
		u 'uom': u 'in',
		u 'w': 11
	}
}, {
	u 'status': u 'D',
	u 'item': u 'paper',
	u '_id': ObjectId('5c540556bddaf0b00803d344'),
	u 'qty': 100,
	u 'size': {
		u 'h': 8.5,
		u 'uom': u 'in',
		u 'w': 11
	}
}]
"""
# 查询 size.h 小于 10 的文档
cursor = db.inventory.find(
    {'size.h': {'$lt': 10}}
)
"""
[{
	u 'status': u 'A',
	u 'item': u 'notebook',
	u '_id': ObjectId('5c540556bddaf0b00803d343'),
	u 'qty': 50,
	u 'size': {
		u 'h': 8.5,
		u 'uom': u 'in',
		u 'w': 11
	}
}, {
	u 'status': u 'D',
	u 'item': u 'paper',
	u '_id': ObjectId('5c540556bddaf0b00803d344'),
	u 'qty': 100,
	u 'size': {
		u 'h': 8.5,
		u 'uom': u 'in',
		u 'w': 11
	}
}]
"""
# 查询 size.h 小于 15,size.uom 等于 in, 并且status 等于D的文档
cursor = db.inventory.find(
    {'size.h': {'$lt': 15}, 'size.uom': 'in', 'status': 'D'}
)
"""
[{
	u 'status': u 'D',
	u 'item': u 'paper',
	u '_id': ObjectId('5c540556bddaf0b00803d344'),
	u 'qty': 100,
	u 'size': {
		u 'h': 8.5,
		u 'uom': u 'in',
		u 'w': 11
	}
}]
"""

数组查询

首先准备数据:

import pymongo

client = pymongo.MongoClient('mongodb://localhost:27017/')
db = client.test3

db.inventory.insert_many([
    {"item": "journal",
     "qty": 25,
     "tags": ["blank", "red"],
     "dim_cm": [14, 21]},
    {"item": "notebook",
     "qty": 50,
     "tags": ["red", "blank"],
     "dim_cm": [14, 21]},
    {"item": "paper",
     "qty": 100,
     "tags": ["red", "blank", "plain"],
     "dim_cm": [14, 21]},
    {"item": "planner",
     "qty": 75,
     "tags": ["blank", "red"],
     "dim_cm": [22.85, 30]},
    {"item": "postcard",
     "qty": 45,
     "tags": ["blue"],
     "dim_cm": [10, 15.25]},
    {"item": "mac",
     "qty": 12,
     "tags": ["silver", "golden"],
     "dim_cm": [25, 34]}
])
查询整个数组

根据数组指定相等条件时,使用文档查询条件:{ <field>: <value> },其中<value>就是要进行匹配的数组本身,包括其中元素的顺序。

# 查询tags字段的值等于['red', 'blank']的文档
cursor = db.inventory.find(
    {'tags': ['red', 'blank']}
)
"""
[{
	u 'item': u 'notebook',
	u '_id': ObjectId('5c54138fbddaf0a1e075f0ff'),
	u 'qty': 50,
	u 'dim_cm': [14, 21],
	u 'tags': [u 'red', u 'blank']
}]
"""

当然,如果你希望查询数组中包含red和blank元素(不关心元素顺序,或者是否有多余元素)的所有文档,可以使用$all操作符:

cursor = db.inventory.find(
    {'tags': {'$all': ['red', 'blank']}}
)
"""
[{
	u 'item': u 'journal',
	u '_id': ObjectId('5c54138fbddaf0a1e075f0fe'),
	u 'qty': 25,
	u 'dim_cm': [14, 21],
	u 'tags': [u 'blank', u 'red']
}, {
	u 'item': u 'notebook',
	u '_id': ObjectId('5c54138fbddaf0a1e075f0ff'),
	u 'qty': 50,
	u 'dim_cm': [14, 21],
	u 'tags': [u 'red', u 'blank']
}, {
	u 'item': u 'paper',
	u '_id': ObjectId('5c54138fbddaf0a1e075f100'),
	u 'qty': 100,
	u 'dim_cm': [14, 21],
	u 'tags': [u 'red', u 'blank', u 'plain']
}, {
	u 'item': u 'planner',
	u '_id': ObjectId('5c54138fbddaf0a1e075f101'),
	u 'qty': 75,
	u 'dim_cm': [22.85, 30],
	u 'tags': [u 'blank', u 'red']
}]
"""
查询数组中的某个元素(指定单个查询条件)

查询数组中包含某个元素的文档时,使用{ <field>: <value> },其中<value>是数组中的元素

# 查询tags中包含red的文档
cursor = db.inventory.find(
    {'tags': 'red'}
)
"""
[{
	u 'item': u 'journal',
	u '_id': ObjectId('5c54138fbddaf0a1e075f0fe'),
	u 'qty': 25,
	u 'dim_cm': [14, 21],
	u 'tags': [u 'blank', u 'red']
}, {
	u 'item': u 'notebook',
	u '_id': ObjectId('5c54138fbddaf0a1e075f0ff'),
	u 'qty': 50,
	u 'dim_cm': [14, 21],
	u 'tags': [u 'red', u 'blank']
}, {
	u 'item': u 'paper',
	u '_id': ObjectId('5c54138fbddaf0a1e075f100'),
	u 'qty': 100,
	u 'dim_cm': [14, 21],
	u 'tags': [u 'red', u 'blank', u 'plain']
}, {
	u 'item': u 'planner',
	u '_id': ObjectId('5c54138fbddaf0a1e075f101'),
	u 'qty': 75,
	u 'dim_cm': [22.85, 30],
	u 'tags': [u 'blank', u 'red']
}]
"""

如果要给数组元素指定多个条件,可以在查询条件中使用查询运算符:{<array filed>: {<operator1>: <value1>, ...},具体如下个部分

为数组内的元素指定多个查询条件

当为数组内的元素指定多个条件时,你可以指定要么单个元素满足这些条件,或者数组内元素的任意组合满足这些条件。

# 查询dim_cm数组中元素的组合满足查询条件:
# 一个元素大于15,另一个元素小于20,或者一个元素满足两种条件的的文档
cursor = db.inventory.find(
    {'dim_cm': {'$gt': 15, '$lt': 20}}
)
"""
[{
	u 'item': u 'journal',
	u '_id': ObjectId('5c54138fbddaf0a1e075f0fe'),
	u 'qty': 25,
	u 'dim_cm': [14, 21],
	u 'tags': [u 'blank', u 'red']
}, {
	u 'item': u 'notebook',
	u '_id': ObjectId('5c54138fbddaf0a1e075f0ff'),
	u 'qty': 50,
	u 'dim_cm': [14, 21],
	u 'tags': [u 'red', u 'blank']
}, {
	u 'item': u 'paper',
	u '_id': ObjectId('5c54138fbddaf0a1e075f100'),
	u 'qty': 100,
	u 'dim_cm': [14, 21],
	u 'tags': [u 'red', u 'blank', u 'plain']
}, {
	u 'item': u 'postcard',
	u '_id': ObjectId('5c54138fbddaf0a1e075f102'),
	u 'qty': 45,
	u 'dim_cm': [10, 15.25],
	u 'tags': [u 'blue']
}]
注意:"dim_cm": [25, 34]的元素并不会被过滤出来。
"""
一个数组元素同时满足多个条件

使用$elemMatch运算符,指定多个条件,只要有1个元素同时满足这些条件即可。

# 查询dim_cm数组中,至少有一个大于15且小于20的元素的文档
cursor = db.inventory.find(
    {'dim_cm': {'$elemMatch': {'$gt': 15, '$lt': 20}}}
)
"""
[{
	u 'item': u 'postcard',
	u '_id': ObjectId('5c8ba39bbddaf00ea0365693'),
	u 'qty': 45,
	u 'dim_cm': [10, 15.25],
	u 'tags': [u 'blue']
}]
15.25 满足既大于15,又小于20的条件
"""
元素的组合满足复合查询条件
# 查询dim_cm数组中元素的组合满足查询条件:
# 存在某些元素大于15,且某些元素小于20,或者一个元素满足两种条件的的文档
# (若instock下的所有元素的组合都不能满足以上条件,则无法被过滤出来)
# 注意:"dim_cm": [25, 34]的元素并不会被过滤出来。
cursor = db.inventory.find(
    {'dim_cm': {'$gt': 15, '$lt': 20}}
)
"""
[{
	u 'item': u 'journal',
	u '_id': ObjectId('5c54138fbddaf0a1e075f0fe'),
	u 'qty': 25,
	u 'dim_cm': [14, 21],
	u 'tags': [u 'blank', u 'red']
}, {
	u 'item': u 'notebook',
	u '_id': ObjectId('5c54138fbddaf0a1e075f0ff'),
	u 'qty': 50,
	u 'dim_cm': [14, 21],
	u 'tags': [u 'red', u 'blank']
}, {
	u 'item': u 'paper',
	u '_id': ObjectId('5c54138fbddaf0a1e075f100'),
	u 'qty': 100,
	u 'dim_cm': [14, 21],
	u 'tags': [u 'red', u 'blank', u 'plain']
}, {
	u 'item': u 'postcard',
	u '_id': ObjectId('5c54138fbddaf0a1e075f102'),
	u 'qty': 45,
	u 'dim_cm': [10, 15.25],
	u 'tags': [u 'blue']
}]
"""
通过数组的索引查询元素

使用.点,根据数组中指定索引的元素进行查询。索引从0开始。

# 根据dim_cm数组的第一个元素,满足大于20,且小于25进行查询
cursor = db.inventory.find(
    {'dim_cm.0': {'$gt': 20, '$lt': 25}}
)
"""
[{
	u 'item': u 'planner',
	u '_id': ObjectId('5c8ba39bbddaf00ea0365692'),
	u 'qty': 75,
	u 'dim_cm': [22.85, 30],
	u 'tags': [u 'blank', u 'red']
}]
"""

包含子文档的数组

准备数据:

import pymongo
from bson.son import SON

client = pymongo.MongoClient('mongodb://localhost:27017/')
db = client.test4

db.inventory.insert_many([
    {"item": "journal",
     "instock": [
         SON([("warehouse", "A"), ("qty", 5)]),
         SON([("warehouse", "C"), ("qty", 15)])]},
    {"item": "notebook",
     "instock": [
         SON([("warehouse", "C"), ("qty", 5)])]},
    {"item": "paper",
     "instock": [
         SON([("warehouse", "A"), ("qty", 60)]),
         SON([("warehouse", "B"), ("qty", 15)])]},
    {"item": "planner",
     "instock": [
         SON([("warehouse", "A"), ("qty", 40)]),
         SON([("warehouse", "B"), ("qty", 5)])]},
    {"item": "postcard",
     "instock": [
         SON([("warehouse", "B"), ("qty", 15)]),
         SON([("warehouse", "C"), ("qty", 35)])]}])
根据子文档进行匹配
# instock数组的某个元素能匹配指定的文档
cursor = db.inventory.find({
    'instock': SON([('warehouse', 'A'), ('qty', 5)])
})

"""
[{
	u 'item': u 'journal',
	u '_id': ObjectId('5c8c5f4abddaf049404c5e89'),
	u 'instock': [{   // 该元素能够匹配指定的文档条件
		u 'warehouse': u 'A',
		u 'qty': 5
	}, {
		u 'warehouse': u 'C',
		u 'qty': 15
	}]
}]
"""

根据文档进行匹配是严格匹配,包括文档的顺序,因此如下查询无法匹配任何结果:

cursor = db.inventory.find({
    'instock': SON([('qty', 5), ('warehouse', 'A')])
})
对子文档的 某个字段指定单个查询条件
对所有子文档内的某个字段指定一个查询条件
# 查询instock数组内至少包含一个子文档,其满足qty字段小于等于10的所有文档
cursor = db.inventory.find({
    'instock.qty': {'$lte': 10}
})
"""
[{
	u 'item': u 'journal',
	u '_id': ObjectId('5c8c5f4abddaf049404c5e89'),
	u 'instock': [{
		u 'warehouse': u 'A',
		u 'qty': 5
	}, {
		u 'warehouse': u 'C',
		u 'qty': 15
	}]
}, {
	u 'item': u 'notebook',
	u '_id': ObjectId('5c8c5f4abddaf049404c5e8a'),
	u 'instock': [{
		u 'warehouse': u 'C',
		u 'qty': 5
	}]
}, {
	u 'item': u 'planner',
	u '_id': ObjectId('5c8c5f4abddaf049404c5e8c'),
	u 'instock': [{
		u 'warehouse': u 'A',
		u 'qty': 40
	}, {
		u 'warehouse': u 'B',
		u 'qty': 5
	}]
}]
"""
使用数组索引来指定子文档的某个字段进行查询
# 查询instock数组的第一个子文档的qty字段满足大于20的所有文档
cursor = db.inventory.find({
    'instock.0.qty': {'$gt': 20}
})
"""
[{
	u 'item': u 'paper',
	u '_id': ObjectId('5c8c5f4abddaf049404c5e8b'),
	u 'instock': [{
		u 'warehouse': u 'A',
		u 'qty': 60
	}, {
		u 'warehouse': u 'B',
		u 'qty': 15
	}]
}, {
	u 'item': u 'planner',
	u '_id': ObjectId('5c8c5f4abddaf049404c5e8c'),
	u 'instock': [{
		u 'warehouse': u 'A',
		u 'qty': 40
	}, {
		u 'warehouse': u 'B',
		u 'qty': 5
	}]
}]
"""
对子文档指定多个字段查询条件

当对子文档指定多个字段查询条件时,你可以指定要么单个子文档满足这些条件,或者任意子文档的组合满足条件(类似上面的数组内元素指定多个条件)

单个子文档满足多个字段的查询条件

使用$elemMatch运算符对包含子文档的数组指定多个条件,至少有一个子文档能满足这些条件即可

# 查询instock数组至少包含一个子文档同时满足qty等于5,warehouse等于4的所有文档
cursor = db.inventory.find({
    'instock': {'$elemMatch': {'qty': 5, 'warehouse': 'A'}}
})

"""
[{
	u 'item': u 'journal',
	u '_id': ObjectId('5c8c5f4abddaf049404c5e89'),
	u 'instock': [{
		u 'warehouse': u 'A',
		u 'qty': 5
	}, {
		u 'warehouse': u 'C',
		u 'qty': 15
	}]
}]
"""
# 查询instock数组至少包含一个子文档,其qty大于30,小于等于50的所有文档
cursor = db.inventory.find(
    {'instock': {'$elemMatch': {'qty': {'$gt': 30, '$lte': 50}}}}
)
"""
[{
	u 'item': u 'planner',
	u '_id': ObjectId('5c8c5f4abddaf049404c5e8c'),
	u 'instock': [{
		u 'warehouse': u 'A',
		u 'qty': 40
	}, {
		u 'warehouse': u 'B',
		u 'qty': 5
	}]
}, {
	u 'item': u 'postcard',
	u '_id': ObjectId('5c8c5f4abddaf049404c5e8d'),
	u 'instock': [{
		u 'warehouse': u 'B',
		u 'qty': 15
	}, {
		u 'warehouse': u 'C',
		u 'qty': 35
	}]
}]
"""
子文档组合满足复合查询条件

如果复合查询不使用$elemMatch运算符,那么查询将选择那些数组包含任何能满足条件的组合。比如:

# 查询instock下的某些子文档的qty字段大于30,且某些子文档的qty字段小于等于50,(或同时满足)的所有文档
# (若instock下的所有文档的组合都不能满足以上条件,则无法被过滤出来)
cursor = db.inventory.find(
    {'instock.qty': {'$gt': 30, '$lte': 50}}
)
"""
[{
	u 'item': u 'paper',
	u '_id': ObjectId('5c8c5f4abddaf049404c5e8b'),
	u 'instock': [{
		u 'warehouse': u 'A',
		u 'qty': 60
	}, {
		u 'warehouse': u 'B',
		u 'qty': 15
	}]
}, {
	u 'item': u 'planner',
	u '_id': ObjectId('5c8c5f4abddaf049404c5e8c'),
	u 'instock': [{
		u 'warehouse': u 'A',
		u 'qty': 40
	}, {
		u 'warehouse': u 'B',
		u 'qty': 5
	}]
}, {
	u 'item': u 'postcard',
	u '_id': ObjectId('5c8c5f4abddaf049404c5e8d'),
	u 'instock': [{
		u 'warehouse': u 'B',
		u 'qty': 15
	}, {
		u 'warehouse': u 'C',
		u 'qty': 35
	}]
}]
"""
# 查询instock下的某些子文档的qty字段等于5,且某些子文档的warehouse等于A,(或同时满足)的所有文档
cursor = db.inventory.find({
    'instock.qty': 5, 'instock.warehouse': 'A'
})
"""
[{
	u 'item': u 'journal',
	u '_id': ObjectId('5c8c5f4abddaf049404c5e89'),
	u 'instock': [{
		u 'warehouse': u 'A',
		u 'qty': 5
	}, {
		u 'warehouse': u 'C',
		u 'qty': 15
	}]
}, {
	u 'item': u 'planner',
	u '_id': ObjectId('5c8c5f4abddaf049404c5e8c'),
	u 'instock': [{
		u 'warehouse': u 'A',
		u 'qty': 40
	}, {
		u 'warehouse': u 'B',
		u 'qty': 5
	}]
}]
"""

返回字段

默认情况下,MongoDB的查询返回匹配文档的所有字段,如果想限制返回数据的大小,可以使用projection文档(投影文档)来指定返回的字段。

准备数据:

import pymongo

client = pymongo.MongoClient('mongodb://localhost:27017/')
db = client.test5

db.inventory.insert_many([
    {"item": "journal",
     "status": "A",
     "size": {"h": 14, "w": 21, "uom": "cm"},
     "instock": [{"warehouse": "A", "qty": 5}]},
    {"item": "notebook",
     "status": "A",
     "size": {"h": 8.5, "w": 11, "uom": "in"},
     "instock": [{"warehouse": "C", "qty": 5}]},
    {"item": "paper",
     "status": "D",
     "size": {"h": 8.5, "w": 11, "uom": "in"},
     "instock": [{"warehouse": "A", "qty": 60}]},
    {"item": "planner",
     "status": "D",
     "size": {"h": 22.85, "w": 30, "uom": "cm"},
     "instock": [{"warehouse": "A", "qty": 40}]},
    {"item": "postcard",
     "status": "A",
     "size": {"h": 10, "w": 15.25, "uom": "cm"},
     "instock": [
         {"warehouse": "B", "qty": 15},
         {"warehouse": "C", "qty": 35}]}])

我们看以下查询:

cursor = db.inventory.find({"status": "A"})

该查询相当于SQL的:

SELECT * FROM inventory WHERE status = 'A'
只返回指定字段和 _id 字段

在投影文档中,将需要的字段设置为1,就可以指定查询后返回的字段。比如:

cursor = db.inventory.find(  # find的第二个参数是投影文档
    {'status': 'A'}, {'item': 1, 'status': 1}
)

"""
[{
	u 'status': u 'A',
	u 'item': u 'journal',
	u '_id': ObjectId('5c8ca21dbddaf046e0efdd49')
}, {
	u 'status': u 'A',
	u 'item': u 'notebook',
	u '_id': ObjectId('5c8ca21dbddaf046e0efdd4a')
}, {
	u 'status': u 'A',
	u 'item': u 'postcard',
	u '_id': ObjectId('5c8ca21dbddaf046e0efdd4d')
}]
"""

上述查询相当于SQL:

SELECT _id, item, status from inventory WHERE status = "A"
丢弃 _id 字段

_id字段会默认返回,如果不需要,可以在投影文档中指定 _id: 0

cursor = db.inventory.find(
    {'status': 'A'}, {'item': 1, 'status': 1, '_id': 0}
)
返回所有字段,排除某些字段

将要排除的字段在投影文档中设置为0即可

cursor = db.inventory.find(
    {'status': 'A'}, {'status': 0, 'instock': 0}
)

"""
[{
	u 'item': u 'journal',
	u '_id': ObjectId('5c8ca21dbddaf046e0efdd49'),
	u 'size': {
		u 'h': 14,
		u 'w': 21,
		u 'uom': u 'cm'
	}
}, {
	u 'item': u 'notebook',
	u '_id': ObjectId('5c8ca21dbddaf046e0efdd4a'),
	u 'size': {
		u 'h': 8.5,
		u 'w': 11,
		u 'uom': u 'in'
	}
}, {
	u 'item': u 'postcard',
	u '_id': ObjectId('5c8ca21dbddaf046e0efdd4d'),
	u 'size': {
		u 'h': 10,
		u 'w': 15.25,
		u 'uom': u 'cm'
	}
}]
"""

注意

除了_id字段,projection中不能既有指定字段,又有排除字段。下面的查询会报错:

cursor = db.inventory.find(
    {'status': 'A'}, {'status': 0, 'instock': 0, 'item': 1}
)

"""
pymongo.errors.OperationFailure: Projection cannot have a mix of inclusion and exclusion.
"""
返回子文档中的指定字段

通过.点标记指定嵌套字段并设置为1即可,比如

# 查询所有文档,并且只返回size子文档中的uom字段
cursor = db.inventory.find(
    {}, {'item': 1, 'status': 1, 'size.uom': 1}
)

"""
[{
	u 'status': u 'A',
	u 'item': u 'journal',
	u '_id': ObjectId('5c8ca21dbddaf046e0efdd49'),
	u 'size': {
		u 'uom': u 'cm'
	}
}, 
// ...
{
	u 'status': u 'A',
	u 'item': u 'postcard',
	u '_id': ObjectId('5c8ca21dbddaf046e0efdd4d'),
	u 'size': {
		u 'uom': u 'cm'
	}
}]
"""
丢弃子文档中的指定字段

通过.点标记指定嵌套字段并设置为0即可,比如

# 查询satus等于D的所有文档,且子文档不返回uom字段
cursor = db.inventory.find(
    {'status': 'D'}, {'size.uom': 0}
)

"""
[{
	u 'status': u 'D',
	u 'item': u 'paper',
	u '_id': ObjectId('5c8ca21dbddaf046e0efdd4b'),
	u 'instock': [{
		u 'warehouse': u 'A',
		u 'qty': 60
	}],
	u 'size': {
		u 'h': 8.5,
		u 'w': 11
	}
}, {
	u 'status': u 'D',
	u 'item': u 'planner',
	u '_id': ObjectId('5c8ca21dbddaf046e0efdd4c'),
	u 'instock': [{
		u 'warehouse': u 'A',
		u 'qty': 40
	}],
	u 'size': {
		u 'h': 22.85,
		u 'w': 30
	}
}]
"""
投射数组内的子文档指定字段

使用.标记来投影数组内子文档的字段即可,比如

cursor = db.inventory.find(
    {'status': 'D'}, {'item': 1, 'status': 1, 'instock.qty': 1}
)

"""
[{
	u 'status': u 'D',
	u 'item': u 'paper',
	u '_id': ObjectId('5c8ca21dbddaf046e0efdd4b'),
	u 'instock': [{
		u 'qty': 60
	}]
}, {
	u 'status': u 'D',
	u 'item': u 'planner',
	u '_id': ObjectId('5c8ca21dbddaf046e0efdd4c'),
	u 'instock': [{
		u 'qty': 40
	}]
}]
"""
投射指定的数组元素

MongoDB为包含数组的字段提供了一些运算符,来操作数组:

$elemMatch, $slice, $

下面使用切片运算符$slice来返回数组中的最后一个元素:

cursor = db.inventory.find(
    {'status': 'A'},
    {'item': 1, 'status': 1, 'instock': {'$slice': -1}}
)

"""
[{
	u 'status': u 'A',
	u 'item': u 'journal',
	u '_id': ObjectId('5c8ca21dbddaf046e0efdd49'),
	u 'instock': [{
		u 'warehouse': u 'A',
		u 'qty': 5
	}]
}, {
	u 'status': u 'A',
	u 'item': u 'notebook',
	u '_id': ObjectId('5c8ca21dbddaf046e0efdd4a'),
	u 'instock': [{
		u 'warehouse': u 'C',
		u 'qty': 5
	}]
}, {
	u 'status': u 'A',
	u 'item': u 'postcard',
	u '_id': ObjectId('5c8ca21dbddaf046e0efdd4d'),
	u 'instock': [{
		u 'warehouse': u 'C',
		u 'qty': 35
	}]
}]
"""

查询Null或缺失字段

MongoDB中的不同查询运算符对null值的处理也不同。

准备数据:

import pymongo

client = pymongo.MongoClient('mongodb://localhost:27017/')
db = client.test6

# 插入两个文档,一个item为空值,一个item字段缺失
db.inventory.insert_many([{"_id": 1, "item": None}, {"_id": 2}])
相等性过滤

{'item': None}查询将匹配要么item字段为空值,要么不包含item字段的文档:

cursor = db.inventory.find(
    {'item': None}
)

"""
[{
	u 'item': None,
	u '_id': 1
}, {
	u '_id': 2
}]
"""
类型检查

{'item': {'$type': 10}}查询只匹配item为空值的文档。空值的类型码是10

cursor = db.inventory.find(
    {'item': {'$type': 10}}
)

"""
[{u'item': None, u'_id': 1}]
"""
存在检查

{'item': {'$exists': False}}查询只匹配不包含item字段的文档

cursor = db.inventory.find(
    {'item': {'$exists': False}}
)

"""
[{u'_id': 2}]
"""

更新文档

更新文档主要使用如下几个方法:

  • db.collection.update_one() 最多更新一个文档(即使匹配出多个)
  • db.collection.update_many() 更新所有匹配的文档
  • db.collection.replace_one()最多替换一个文档(即使匹配出过个)
  • db.collection.update()
    • 如果匹配处单个文档,则进行更新或替换
    • 如果匹配出多个文档,则更新这些文档

准备数据:

db.inventory.insert_many([
    {"item": "canvas",
     "qty": 100,
     "size": {"h": 28, "w": 35.5, "uom": "cm"},
     "status": "A"},
    {"item": "journal",
     "qty": 25,
     "size": {"h": 14, "w": 21, "uom": "cm"},
     "status": "A"},
    {"item": "mat",
     "qty": 85,
     "size": {"h": 27.9, "w": 35.5, "uom": "cm"},
     "status": "A"},
    {"item": "mousepad",
     "qty": 25,
     "size": {"h": 19, "w": 22.85, "uom": "cm"},
     "status": "P"},
    {"item": "notebook",
     "qty": 50,
     "size": {"h": 8.5, "w": 11, "uom": "in"},
     "status": "P"},
    {"item": "paper",
     "qty": 100,
     "size": {"h": 8.5, "w": 11, "uom": "in"},
     "status": "D"},
    {"item": "planner",
     "qty": 75,
     "size": {"h": 22.85, "w": 30, "uom": "cm"},
     "status": "D"},
    {"item": "postcard",
     "qty": 45,
     "size": {"h": 10, "w": 15.25, "uom": "cm"},
     "status": "A"},
    {"item": "sketchbook",
     "qty": 80,
     "size": {"h": 14, "w": 21, "uom": "cm"},
     "status": "A"},
    {"item": "sketch pad",
     "qty": 95,
     "size": {"h": 22.85, "w": 30.5, "uom": "cm"},
     "status": "A"}])

更新集合中的文档

如果要修改字段的值,可以使用更新运算符,比如$set,基本格式如下:

{
    <更新运算符>: {<字段1>: <1>, ...},
    <更新运算符>: {<字段2>: <2>, ...},
}
更新单个文档:update_one()
res = db.inventory.update_one(
    {'item': 'paper'},  # filter
    {                   # update
        '$set': {
            'size.uom': 'cm',
            'status': 'P'
        },
        '$currentDate': {
            'lastModified': True  # 新增lastModified字段,值是当前时间
        }
    }
)


print(res)  # <pymongo.results.UpdateResult object at 0x0000024A22FFB3C8>
print(res.matched_count)  # 1
print(res.modified_count)  # 1

cursor = db.inventory.find({'item': 'paper'})
docs = [doc for doc in cursor]
print(docs)
"""
[{
	'_id': ObjectId('5c8f8dcfbddaf03aa4b6a409'),
	'item': 'paper',
	'qty': 100,
	'size': {
		'h': 8.5,
		'w': 11,
		'uom': 'cm'
	},
	'status': 'P',
	'lastModified': datetime.datetime(2019, 3, 18, 12, 36, 45, 391000)
}]
"""
更新多个文档:update_many()
# 更新qty小于50的文档
res = db.inventory.update_many(
    {'qty': {'$lt': 50}},  # filter
    {  # update
        '$set': {'size.uom': 'in', 'status': 'P'},
        '$currentDate': {'lastModified': True}
     }
)

print(res)  # <pymongo.results.UpdateResult object at 0x0000014249CF7488>
print(res.matched_count)  # 3
print(res.modified_count)  # 3

cursor = db.inventory.find({'qty': {'$lt': 50}})
docs = [doc for doc in cursor]
print(docs)
"""
[{
	'_id': ObjectId('5c8f8dcfbddaf03aa4b6a405'),
	'item': 'journal',
	'qty': 25,
	'size': {
		'h': 14,
		'w': 21,
		'uom': 'in'
	},
	'status': 'P',
	'lastModified': datetime.datetime(2019, 3, 19, 12, 26, 33, 410000)
}, {
	'_id': ObjectId('5c8f8dcfbddaf03aa4b6a407'),
	'item': 'mousepad',
	'qty': 25,
	'size': {
		'h': 19,
		'w': 22.85,
		'uom': 'in'
	},
	'status': 'P',
	'lastModified': datetime.datetime(2019, 3, 19, 12, 26, 33, 413000)
}, {
	'_id': ObjectId('5c8f8dcfbddaf03aa4b6a40b'),
	'item': 'postcard',
	'qty': 45,
	'size': {
		'h': 10,
		'w': 15.25,
		'uom': 'in'
	},
	'status': 'P',
	'lastModified': datetime.datetime(2019, 3, 19, 12, 26, 33, 414000)
}]
"""

替换文档:replace_one()

如果要替换整个文档的内容(除了_id字段),只需传入一个新的文档,作为replace_one()的第二个参数即可。替换的文档只能包含键值对,不能巴博涵其他表达式,比如更新运算符。替换的文档可以和原文的的字段不同,由于_id不可变,你可以省略_id字段(当然,如果你非要在替换文档中包含_id字段,那么必须给出与原文档一样的值)

# 替换满足筛选条件的第一个文档
res = db.inventory.replace_one(
    {'item': 'paper'},  # filter
    {'item': 'paper',   # replacement_doc
     'instock': [
         {"warehouse": "A", "qty": 60},
         {"warehouse": "B", "qty": 40}
     ]}
)

print(res)  # <pymongo.results.UpdateResult object at 0x000001AD62170448>
print(res.matched_count)  # 1
print(res.modified_count)  # 1

cursor = db.inventory.find({'item': 'paper'})
docs = [doc for doc in cursor]
print(docs)
"""
[{
	'_id': ObjectId('5c8f8dcfbddaf03aa4b6a409'),
	'item': 'paper',
	'instock': [{
		'warehouse': 'A',
		'qty': 60
	}, {
		'warehouse': 'B',
		'qty': 40
	}]
}]
"""

更新行为

原子性

MongoDB的所有写操作在单个文档上都是原子性的。

_id字段

一旦设置,该字段的值不可修改,也不能被替换。

字段顺序

MongoDB保持写操作中文档的顺序,除了以下情况:

  • _id字段总是文档中的第一个字段
  • 如果更新包含对字段进行命名的操作,可能导致文档的字段重新排序
upsert选项

调用update_one(), update_many(), replace_one()这些方法时,如果指定upsert=True,那么当匹配的文档不存在时,将创建新的文档并插入。

删除文档

删除文档的方法:

  • db.collection.deleteOne() 最多删除一个文档(即便匹配出多个结果)
  • db.collection.deleteMany() 删除匹配的所有文档
  • db.collection.remove() 删除匹配的单个或多个文档

准备数据:

import pymongo

client = pymongo.MongoClient('mongodb://localhost:27017/')
db = client.test8

db.inventory.insert_many([
    {"item": "journal",
     "qty": 25,
     "size": {"h": 14, "w": 21, "uom": "cm"},
     "status": "A"},
    {"item": "notebook",
     "qty": 50,
     "size": {"h": 8.5, "w": 11, "uom": "in"},
     "status": "P"},
    {"item": "paper",
     "qty": 100,
     "size": {"h": 8.5, "w": 11, "uom": "in"},
     "status": "D"},
    {"item": "planner",
     "qty": 75,
     "size": {"h": 22.85, "w": 30, "uom": "cm"},
     "status": "D"},
    {"item": "postcard",
     "qty": 45,
     "size": {"h": 10, "w": 15.25, "uom": "cm"},
     "status": "A"}])

删除所有文档

如果要删除所有文档,只需给delete_many()方法的filter传入一个空文档即可

res = db.inventory.count_documents({})  # 查看所有文档数
print(res) # 5                            
                                      
res = db.inventory.delete_many({})    
print(res.deleted_count)   # 5            

删除满足条件的所有文档

只需给delete_many()方法的filter传入过滤条件即可,比如

# 删除status是A点所有文档
res = db.inventory.delete_many({'status': 'A'})
print(res.deleted_count)  # 2

只删除满足条件的一个文档

count = db.inventory.count_documents({'status': 'D'})
cursor = db.inventory.find({'status': 'D'})
print(count) # 2
print([item for item in cursor])
"""
[{
	'_id': ObjectId('5c922bb5bddaf04be0e84076'),
	'item': 'paper',
	'qty': 100,
	'size': {
		'h': 8.5,
		'w': 11,
		'uom': 'in'
	},
	'status': 'D'
}, {
	'_id': ObjectId('5c922bb5bddaf04be0e84077'),
	'item': 'planner',
	'qty': 75,
	'size': {
		'h': 22.85,
		'w': 30,
		'uom': 'cm'
	},
	'status': 'D'
}]
"""

db.inventory.delete_one({'status': 'D'})
count = db.inventory.count_documents({'status': 'D'}) 
cursor = db.inventory.find({'status': 'D'})           
print(count)                                          
print([item for item in cursor]) # 满足条件的第一个文档已被删除
"""
[{
	'_id': ObjectId('5c922bb5bddaf04be0e84077'),
	'item': 'planner',
	'qty': 75,
	'size': {
		'h': 22.85,
		'w': 30,
		'uom': 'cm'
	},
	'status': 'D'
}]
"""

删除行为

索引

删除操作不会删除索引,即使从表中删除所有文档。

原子性

单个文档的级别上是原子性操作。

批量写操作:

批量写操作只对单个集合有效。insert_many()能进行批量插入,另外,bulk_write()方法支持如下的多种写操作:

  • insert_one
  • update_one
  • update_many
  • replace_one
  • delete_one
  • delete_many

将每种操作放在列表中,然后传给bulk_write()

下面我们来试一下,准备数据:

import pymongo

client = pymongo.MongoClient('mongodb://localhost:27017/')
db = client.test8

res = db.characters.insert_many([
    {"_id": 1, "char": "Brisbane", "class": "monk", "lvl": 4},
    {"_id": 2, "char": "Eldon", "class": "alchemist", "lvl": 3},
    {"_id": 3, "char": "Meldane", "class": "ranger", "lvl": 3}
])

print(res.inserted_ids) # [1, 2, 3]

使用bulk_write()对characters集合执行多个操作:

import pymongo
from pymongo import InsertOne, UpdateOne, DeleteOne, ReplaceOne  # 导入各操作对象

client = pymongo.MongoClient('mongodb://localhost:27017/')
db = client.test8

try:
    res = db.characters.bulk_write([
        InsertOne({'_id': 4, 'char': 'Dithras', 'class': 'barbarian', 'lvl': 4}),
        InsertOne({'_id': 5, 'char': 'Taeln', 'class': 'fighter', 'lvl': 3}),
        UpdateOne({'char': 'Eldon'},
                  {'$set': {'status': 'Critical Injury'}}, upsert=True),
        UpdateOne({'char': 'Ayhan'},
                  {'$set': {'status': 'God hit'}}, upsert=True),
        ReplaceOne({'char': 'Meldane'},
                   {"char": "Tanys", "class": "oracle", "lvl": 4}, upsert=True),
        DeleteOne({'char': 'Brisbane'}),
    ])
    print(res.inserted_count)  # 2
    print(res.modified_count)  # 2
    print(res.upserted_count)  # 1
    print(res.deleted_count)  # 1
    # 以上打印方式比较繁琐,也可以这样直接打印本次批量操作的详细信息:
    print(res.bulk_api_result)
except Exception as e:
    print(e)
    
cursor = db.characters.find({})
print([item for item in cursor])
"""
[{
	'_id': 2,
	'char': 'Eldon',
	'class': 'alchemist',
	'lvl': 3,
	'status': 'Critical Injury'
}, {
	'_id': 3,
	'char': 'Tanys',
	'class': 'oracle',
	'lvl': 4
}, {
	'_id': 4,
	'char': 'Dithras',
	'class': 'barbarian',
	'lvl': 4
}, {
	'_id': 5,
	'char': 'Taeln',
	'class': 'fighter',
	'lvl': 3
}, {
	'_id': ObjectId('5c92fc403744b8cc2b23f3c3'),
	'char': 'Ayhan',
	'status': 'God hit'
}]
"""

分片集合的批量插入策略

暂不讨论。

索引

添加索引可以加速特定的查询,也可以为文档在查询和存储增加额外的功能。下面演示下如何用PyMongo创建unique索引:

db.user.create_index([
    ('user_id', pymongo.ASCENDING)
], unique=True)

print(list(db.user.index_information()))
"""
['_id_', 'user_id_1']

_id_ 是MongoDB自动创建的索引
user_id_1 是我们刚刚创建的索引
"""

创建了唯一索引约束后,如果插入数据的user_id字段的值在集合中已经存在,会插入失败。

聚合

准备数据:

import pymongo

client = pymongo.MongoClient(host='localhost', port=27017)
db = client.test9

db.things.insert_many([
    {'x': 1, 'tags': ['dog', 'cat']},
    {'x': 2, 'tags': ['cat']},
    {'x': 2, 'tags': ['mouse', 'cat', 'dog']},
    {'x': 3, 'tags': []},
])

Pipeline聚合

下面执行一个简答的聚合操作,统计集合内tags数组内每个元素的出现次数。要实现这个操作,需要传入3个操作给pipeline,首先展开tags数组,然后按元素进行分组并加总,最后根据加总数进行排序即可。

注意,由于python的字典是无序的,如果要求精确排序(比如$sort),就得使用SON或者collections.OrderedDict对象:

import pymongo
from bson.son import SON
import pprint

client = pymongo.MongoClient(host='localhost', port=27017)
db = client.test9

pipeline = [
    {'$unwind': '$tags'},
    {'$group': {'_id': '$tags', 'count': {'$sum': 1}}},  # 以元素作为_id字段的值,以元素个数作为count字段的值,1表示正加总(-1则进行负加总)
    {'$sort': SON([('count', -1), ('_id', -1)])}  # 先按count, 再按_id排序,-1表示倒序
]
pprint.pprint(list(db.things.aggregate(pipeline)))
"""
[{'_id': 'cat', 'count': 3},
 {'_id': 'dog', 'count': 2},
 {'_id': 'mouse', 'count': 1}]
"""

# 如果颠倒排序顺序为: {'$sort': SON([('_id', -1), ('count', -1)])},结果如下:
"""
[{'_id': 'mouse', 'count': 1},
 {'_id': 'dog', 'count': 2},
 {'_id': 'cat', 'count': 3}]
"""

# 如果想查看该操作的详细信息,可以使用command()方法:
res = db.command('aggregate', 'things', pipeline=pipeline, explain=True)
pprint.pprint(res)
"""
{'ok': 1.0,
 'stages': [{'$cursor': {'fields': {'_id': 0, 'tags': 1},
                         'query': {},
                         'queryPlanner': {'indexFilterSet': False,
                                          'namespace': 'test9.things',
                                          'parsedQuery': {},
                                          'plannerVersion': 1,
                                          'rejectedPlans': [],
                                          'winningPlan': {'direction': 'forward',
                                                          'stage': 'COLLSCAN'}}}},
            {'$unwind': {'path': '$tags'}},
            {'$group': {'_id': '$tags', 'count': {'$sum': {'$const': 1}}}},
            {'$sort': {'sortKey': {'_id': -1, 'count': -1}}}]}
"""

聚合框架还可以提供投影能力来重塑返回的数据。利用投影和聚合,你可以在结果中增加计算字段,创建虚拟子对象,或者提取子字段为根级字段。更多查看:聚合框架

Map / Reduce 聚合

另一种实现聚合的方式是使用map reduce,下面我们通过定义map和reduce函数来实现上面的聚合计算。

map函数循环数组,对其中的每个元素触发一个键值对儿(key, 1)


高级 Map / Reduce

写入重试

写操作时,如果发生网络错误,是否支持重试。在pymongo中,可以在实例化客户端时指定,比如:

client = pymongo.MongoClient(
    host='localhost',
    port=27017,
    retryWrites=True  # 默认是False,具体可以查看源码中的doc
)

文本搜索

MongoDB支持通过$text运算符对文本字段作索引,以方便文本搜索。不过嘛,不支持中文,洗洗睡。。。

运算符列表:

名称描述
$or逻辑或
$eq等于
$gt大于
$gte大于等于
$in在数组中
$lt小于
$lte小于等于
$ne不等于
$nin不在数组中
$elemMatch数组元素满足
$all数组包含元素
$slice数组切片
$type字段值类型检查
$exists字段是否存在判断
$set更新字段的值
$currentDate设定某字段的值为当前日期时间
$inc增加某字段的值
$push为数组字段添加新的元素

mongoengine

待完善。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 下面是一个使用 Python 和 PyMongo 库连接和操作 MongoDB 数据库的类的示例: ```python import pymongo class MongoDB: def __init__(self, host='localhost', port=27017, username=None, password=None): # 建立连接 self.client = pymongo.MongoClient(host=host, port=port) # 如果有用户名和密码,则进行身份验证 if username and password: self.client.admin.authenticate(username, password) def get_database(self, db_name): # 获取数据库 return self.client[db_name] def get_collection(self, db_name, coll_name): # 获取数据库的集合 return self.client[db_name][coll_name] def insert_one(self, db_name, coll_name, doc): # 插入单条文档 return self.client[db_name][coll_name].insert_one(doc) def insert_many(self, db_name, coll_name, docs): # 插入多条文档 return self.client[db_name][coll_name].insert_many(docs) def update_one(self, db_name, coll_name, filter, update): # 更新单条文档 return self.client[db_name][coll_name].update_one(filter, update) def update_many(self, db_name, coll_name, filter, update): # 更新多条文档 return self.client[db_name][coll_name].update_many(filter, update) def delete_one(self, db_name, coll_name, filter): # 删除单条文档 return self.client[db_name][coll_name].delete_one(filter) def delete_many(self, db_name, coll_name, filter): # 删除多条文档 return self.client[db_name][coll_name].delete_many(filter) def find_one(self, db_name, coll_name, filter): # 查询单条文档 return self.client[db_name][coll_name].find_one(filter) def find(self, db_name, coll_name, filter, projection=None, sort=None, limit=0, skip=0): # 查询多条文档 cursor = self.client[db_name][coll_name]. ### 回答2: 使用Python编写一个可以进行Mongo数据库的增删改查操作的类,包含分页查询功能,代码如下: ```python from pymongo import MongoClient class MongoCRUD: def __init__(self, host, port, database, collection): self.client = MongoClient(host, port) self.db = self.client[database] self.collection = self.db[collection] def create(self, data): """ 创建文档 """ self.collection.insert_one(data) def read(self, query={}, page=None, limit=None): """ 查询文档 """ if page is not None and limit is not None: skip = (page - 1) * limit result = self.collection.find(query).skip(skip).limit(limit) else: result = self.collection.find(query) return list(result) def update(self, query, update_data): """ 更新文档 """ self.collection.update_many(query, {'$set': update_data}) def delete(self, query): """ 删除文档 """ self.collection.delete_many(query) ``` 使用示例: ```python mongo = MongoCRUD('localhost', 27017, 'mydatabase', 'mycollection') # 创建文档 data = {'name': 'Alice', 'age': 25} mongo.create(data) # 查询文档 result = mongo.read({'age': {'$lte': 30}}, page=1, limit=10) for doc in result: print(doc) # 更新文档 query = {'name': 'Alice'} update_data = {'age': 26} mongo.update(query, update_data) # 删除文档 query = {'name': 'Alice'} mongo.delete(query) ``` 以上代码实现了一个可以进行Mongo数据库的增删改查操作的类,并包含了分页查询功能。在初始化类时,需要传入MongoDB的主机地址、端口号、数据库名称和集合名称。然后通过调用类的方法实现文档的增删改查操作。在查询时,可以通过`page`和`limit`参数指定查询的页码和每页显示的数量,实现分页查询功能。 ### 回答3: 在Python中可以使用pymongo库进行MongoDB数据库的增删改查操作,并结合分页功能。下面是一个示例的类,实现了MONGO数据库的增删改查以及分页查询的功能。 ```python from pymongo import MongoClient from math import ceil class MongoCRUD: def __init__(self, db_name, collection_name): self.client = MongoClient() # 连接MongoDB self.db = self.client[db_name] # 选择数据库 self.collection = self.db[collection_name] # 选择集合 # 插入数据 def insert_data(self, data): result = self.collection.insert_one(data) return result.inserted_id # 删除数据 def delete_data(self, query): result = self.collection.delete_many(query) return result.deleted_count # 更新数据 def update_data(self, query, update): result = self.collection.update_many(query, {"$set": update}) return result.modified_count # 查询数据 def find_data(self, query, page_size, page_num): total = self.collection.count_documents(query) # 获取总数据量 total_pages = int(ceil(total/page_size)) # 计算总页数 skip = page_size * (page_num - 1) # 计算需要跳过的数据量 data = list(self.collection.find(query).skip(skip).limit(page_size)) # 分页查询数据 return data, total_pages # 示例用法 if __name__ == "__main__": db_name = "test_database" collection_name = "test_collection" crud = MongoCRUD(db_name, collection_name) # 插入数据 data = {"name": "Alice", "age": 25} inserted_id = crud.insert_data(data) print("插入数据的ID:", inserted_id) # 更新数据 query = {"name": "Alice"} update = {"age": 26} modified_count = crud.update_data(query, update) print("更新数据的数量:", modified_count) # 删除数据 query = {"name": "Alice"} deleted_count = crud.delete_data(query) print("删除数据的数量:", deleted_count) # 查询数据 query = {} page_size = 10 # 每页数据量 page_num = 2 # 当前页码 data, total_pages = crud.find_data(query, page_size, page_num) print("查询结果:", data) print("总页数:", total_pages) ``` 这个类封装了MongoDB的增删改查操作,并提供了分页查询的功能。通过调用相应的方法,可以实现对Mongo数据库的数据进行增加、删除、更新和查询等操作,并且支持分页查询,可以指定每页数据量和当前页码。在示例用法中,我们演示了如何使用该类进行基本的数据操作。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值