排序
使用sort()方法对数据进行排序,可以通过参数指定排序的字段,并可以指定排序方式(默认升序)
db = client['first_demo']
col = db['sites']
for doc in col.find({"alexa": {"$gt": '100'}}).sort('name', pymongo.DESCENDING):
print(doc)
索引
索引通常能够极大的提高查询的效率,如果没有索引,MongoDB在读取数据时必须扫描集合中的每个文件并选取那些符合查询条件的记录。
索引是特殊的数据结构,索引存储在一个易于遍历读取的数据集合中,索引是对数据库表中一列或多列的值进行排序的一种结构。
使用create_index
来创建索引,参数为单个的(key, direction)
,或者它们的列表[(key, direction), (key2, direction2)]
col = db['profile']
mylist = [
{'user_id': 200, 'name': 'Tom'},
{'user_id': 201, 'name': 'Tommy'},
{'user_id': 202, 'name': 'Drew'},
{'user_id': 213, 'name': 'Zodiac'},
{'user_id': 220, 'name': 'Luke'},
{'user_id': 230, 'name': 'David'},
{'user_id': 204, 'name': 'James'},
{'user_id': 206, 'name': 'Paul'},
]
col.insert_many(mylist)
for doc in col.find():
print(doc)
col.create_index([('name', pymongo.ASCENDING)], unique=True)
print(col.index_information())
# {'_id_': {'v': 2, 'key': [('_id', 1)], 'ns': 'first_demo.profile'}, 'name_1': {'v': 2, 'unique': True, 'key': [('name', 1)], 'ns': 'first_demo.profile'}}
index_information会返回当前集合的索引信息,形式为一个字典,key为索引的名称(在_id
上的索引是MongoDB自动创建的, 在name
上的索引是用户创建的),value是一个关于每个索引的信息的字典。
索引会阻止视图插入相同user_id的文档:
>>> user_profiles = [
... {'user_id': 211, 'name': 'Luke'},
... {'user_id': 212, 'name': 'Ziltoid'}]
>>> result = db.profiles.insert_many(user_profiles)
>>> new_profile = {'user_id': 213, 'name': 'Drew'}
>>> duplicate_profile = {'user_id': 212, 'name': 'Tommy'}
>>> result = db.profiles.insert_one(new_profile) # This is fine.
>>> result = db.profiles.insert_one(duplicate_profile)
Traceback (most recent call last):
DuplicateKeyError: E11000 duplicate key error index: test_database.profiles.$user_id_1 dup key: { : 212 }
聚合
MongoDB中聚合(aggregate)操作处理数据记录并返回计算结果。 聚合操作将来自多个文档的值组合在一起,并且可以对分组数据执行各种操作以返回单个结果。 MongoDB提供了三种执行聚合的方法:聚合管道,map-reduce函数和单用途聚合方法。
基本语法:
db.COLLECTION_NAME.aggregate(AGGREGATE_OPERATION)
数据:
{
"_id" : ObjectId("5c047e878c70ac29935dbb92"),
"cust_id" : "A122",
"amount" : 123,
"status" : "A"
}
{
"_id" : ObjectId("5c047e878c70ac29935dbb93"),
"cust_id" : "A122",
"amount" : 500,
"status" : "A"
}
{
"_id" : ObjectId("5c047e878c70ac29935dbb94"),
"cust_id" : "B112",
"amount" : 300,
"status" : "A"
}
{
"_id" : ObjectId("5c047e878c70ac29935dbb95"),
"cust_id" : "B112",
"amount" : 200,
"status" : "B"
}
使用aggregate()计算每个customer的amount和值:
> db.orders.aggregate([{$group : {_id: "$cust_id", total : {$sum: '$amount'}}}])
{ "_id" : "B112", "total" : 500 }
{ "_id" : "A122", "total" : 623 }
类似sql语句
select cust_id, count(amount) from mycol group by cust_id
聚合表达式:
- $sum:计算和值;
- $avg:计算平均值
- $min:获取集合中所有文档对应值得最小值
- $max:获取集合中所有文档对应值得最大值
- $push:在结果文档中插入值到一个数组中
- $addToSet:在结果文档中插入值到一个数组中,但不创建副本
- $first:根据资源文档的排序获取第一个文档数据
- $last:根据资源文档的排序获取最后一个文档数据
聚合管道
管道在Unix和Linux中一般用于将当前命令的输出结果作为下一个命令的参数。MongoDB聚合管道由阶段(stages)
组成。 每个阶段在文档通过管道时转换文档。 管道阶段不需要为每个输入文档生成一个输出文档; 例如,某些阶段可以生成新文档或过滤掉文档。 管道阶段可以在管道中多次出现。。官方示例:
几个常用的stages:
MongoDB Aggregation Operators | 含义 | SQL |
---|---|---|
$project | 修改输入文档的结构。可以用来重命名、增加或删除域,也可以用于创建计算结果以及嵌套文档 | SELECT |
$match | 用于过滤数据,只输出符合条件的文档。$match使用MongoDB的标准查询操作 | WHERE, HAVING |
$limit | 用来限制MongoDB聚合管道返回的文档数 | LIMIT |
$skip | 在聚合管道中跳过指定数量的文档,并返回余下的文档 | |
$unwind | 将文档中的某一个数组类型字段拆分成多条,每条包含数组中的一个值 | |
$group | 将集合中的文档分组,可用于统计结果 | GROUP BY |
$sort | 将输入文档排序后输出 | ORDER BY |
$sum | 求和 | COUNT |
具体查看Aggregation Pipeline Stages
- $project实例:
> db.orders.aggregate({$project:{cust_id: 1, status: 1}})
{ "_id" : ObjectId("5c047e878c70ac29935dbb92"), "cust_id" : "A122", "status" : "A" }
{ "_id" : ObjectId("5c047e878c70ac29935dbb93"), "cust_id" : "A122", "status" : "A" }
{ "_id" : ObjectId("5c047e878c70ac29935dbb94"), "cust_id" : "B112", "status" : "A" }
{ "_id" : ObjectId("5c047e878c70ac29935dbb95"), "cust_id" : "B112", "status" : "B" }
>
结果中就只还有_id,cust_id和status三个字段了,默认情况下_id字段是被包含的,如果要想不包含_id话可以使用_id: 0
- $match实例:
> db.orders.aggregate([{$match: {status : "A"}},
{$group: {_id:"$cust_id",total:{$sum:"$amount"}}}])
{ "_id" : "B112", "total" : 300 }
{ "_id" : "A122", "total" : 623 }
$ match 用于获取status为‘A’的记录,然后将符合条件的记录送到下一阶段$group管道操作符进行处理。
- $skip实例
> db.orders.aggregate({$skip: 2})
{ "_id" : ObjectId("5c047e878c70ac29935dbb94"), "cust_id" : "B112", "amount" : 300, "status" : "A" }
{ "_id" : ObjectId("5c047e878c70ac29935dbb95"), "cust_id" : "B112", "amount" : 200, "status" : "B" }
前2个文档被跳过了。
python 实例:
import pprint
from bson.son import SON
pipleline = [
{'$match': {'status': 'A'}},
{'$group': {'_id': '$cust_id', 'total': {'$sum': '$amount'}}},
{'$sort': SON([('total', 1)])}
]
ret = col.aggregate(pipleline)
pprint.pprint(list(ret))
# [{'_id': 'B112', 'total': 300}, {'_id': 'A122', 'total': 623}]
由于python词典没有顺序,所以当需要显式地排序(例如$sort
)时,要使用collections.OrderedDict 或者 SON。
Map-Reduce
Map-reduce是一种数据处理范例,用于将大量数据压缩为有用的聚合结果。 对于map-reduce操作,MongoDB提供了mapReduce数据库命令。
在此map-reduce操作中,MongoDB将映射阶段应用于每个输入文档(即集合中与查询条件匹配的文档)。 map函数发出键值对。 对于具有多个值的key,MongoDB应用reduce阶段,该阶段收集并压缩聚合数据。然后MongoDB将结果存储在一个集合中。reduce函数的输出可以选择性地通过finalize函数以进一步压缩或处理聚合的结果。
python实现:
from bson.code import Code
mapper = Code("""
function () {
emit(this.cust_id, this.amount);
}
""")
reducer = Code("""
function (key, values) {
return Array.sum(values)
}
""")
result = col.map_reduce(mapper, reducer, 'myresult')
for doc in result.find():
pprint.pprint(doc)
# {'_id': 'A122', 'value': 623.0}
# {'_id': 'B112', 'value': 500.0}
在数据库中查询’myresult’
> db.myresult.find()
{ "_id" : "A122", "value" : 623 }
{ "_id" : "B112", "value" : 500 }
>
使用full_response=True参数可以获取更详细的信息:
result = col.map_reduce(mapper, reducer, 'myresult', full_response=True)
print(result)
result结果
{'result': 'myresult', 'timeMillis': 40, 'counts': {'input': 4, 'emit': 4, 'reduce': 2, 'output': 2}, 'ok': 1.0}
还可以使用query参数限制被mapper映射的文档:
query = {'status': 'A'}
result = col.map_reduce(mapper, reducer, 'myresult_1', query=query)
for doc in result.find():
pprint.pprint(doc)
指定条件status为‘A’ ,结果:
{'_id': 'A122', 'value': 623.0}
{'_id': 'B112', 'value': 300.0}
单一用途的聚合函数
MongoDB 提供s db.collection.estimatedDocumentCount(), db.collection.count() 和 db.collection.distinct() 这3个单一用途的聚合函数,它们的操作对象都是单一集合
print(f"counts: {col.count()}")
print(f"distinct: {col.distinct('cust_id')}")
print(f"estimate num: {col.estimated_document_count()}")
# counts: 4
# distinct: ['A122', 'B112']
# estimate num: 4