Mongodb 多文档聚合操作处理方法(单一聚合)

聚合

聚合操作处理多个文档并返回计算结果。您可以使用聚合操作来:

  • 将多个文档中的值分组在一起。

  • 对分组数据执行操作以返回单个结果。

  • 分析数据随时间的变化。

要执行聚合操作,您可以使用:

  • 聚合管道

  • 单一目的聚合方法

  • Map-reduce 函数

单一目的聚合方法

所有这些操作都会聚合来自单个集合的文档。虽然这些操作提供了对常见聚合过程的简单访问,但它们缺乏聚合管道的灵活性和功能。

常用的单一聚合操作,包括 count() 获取文档的总数量 、 distinct() 获取字段不重复的值、estimatedDocumentCount()

count

定义: db.collection.count(query, options), find()返回与集合或视图的查询匹配的文档计数 。 db.collection.count()方法不执行 find()操作,而是计算并返回与查询匹配的结果数。

  • count() 相当于构造 db.collection.find(query).count()。
  • 在分片集群上,db.collection.count() 如果 存在孤立文档或正在进行块迁移,则不使用查询谓词可能会导致计数不准确。为了避免这些情况,在分片集群上,使用以下 db.collection.aggregate()方法
  • 孤立文档: 在分片集群中,孤立文档是指某个分片上的那些文档,由于迁移失败或由于异常关闭而导致迁移清理不完整,因此也存在于其他分片上的块中。
  • 从 MongoDB 4.4 开始,孤立文档会在块迁移完成后自动清理。您不再需要运行cleanupOrphaned来删除孤立文档。
# 查询总数量 
sit_rs1:PRIMARY> db.user.count() 
6

# 此操作等效于以下操作:
sit_rs1:PRIMARY> db.user.find().count() 
6

# 分片集群,使用 aggregate 
sit_rs1:PRIMARY> db.user.aggregate( [{ $count: "myCount" }])
{ "myCount" : 6 }


# 该$count阶段相当于以下 $group+$project序列:
sit_rs1:PRIMARY> db.user.aggregate( [{ $group: { _id: null, count: { $sum: 1 } } },  { $project: { _id: 0 } }] )
{ "count" : 6 }

查询匹配的文档操作,使用 带 Query 的 Count , 计算与查询匹配的所有文档的数量, 如下

# 查询 name 为 user1 的文档数量 
sit_rs1:PRIMARY> db.user.count({"name" : "user1"})
1

# 该查询等效于以下内容:
sit_rs1:PRIMARY> db.user.find({"name" : "user1"}).count()
1

distinct

定义: db.collection.distinct(field, query, options)。 在单个集合或视图中查找指定字段的不同值,并以数组形式返回结果。

  • 在分片集群中,该distinct命令可能会返回 孤立的文档
  • 如果指定的值field是一个数组, db.collection.distinct() 将数组的每个元素视为单独的值。

插入测试数据 : item 字段为嵌入文档、number字段为数组, 如下:

sit_rs1:PRIMARY> db.user.drop()
sit_rs1:PRIMARY> db.user.insertMany([
...  { name: "user1", age: 10, item: {"A": 111, "B": 211}, number: [ { "n1":2, "n2":6 }, { "n1":3, "n2":3 }, { "n1":5, "n2":6 } ] },
...  { name: "user2", age: 20, item: {"A": 112, "B": 212}, number: [ { "n1":1, "n2":3 }, { "n1":4, "n2":5 }, { "n1":7, "n2":6 } ] },
...  { name: "user3", age: 30, item: {"A": 113, "B": 213}, number: [ { "n1":1, "n2":8 }, { "n1":2, "n2":6 }, { "n1":2, "n2":6 } ] },
...  { name: "user4", age: 45, item: {"A": 111, "B": 211}, number: [ { "n1":9, "n2":6 }, { "n1":2, "n2":4 }, { "n1":3, "n2":6 } ] },
...  { name: "user5", age: 55, item: {"A": 112, "B": 215}, number: [ { "n1":7, "n2":8 }, { "n1":8, "n2":4 }, { "n1":4, "n2":6 } ] },
...  { name: "user6", age: 55, item: {"A": 115, "B": 212}, number: [ { "n1":5, "n2":5 }, { "n1":8, "n2":5 }, { "n1":7, "n2":6 } ] },
...  { name: "user7", age: 45, item: {"A": 116, "B": 216}, number: [ { "n1":6, "n2":6 }, { "n1":8, "n2":5 }, { "n1":7, "n2":6 } ] },
...  { name: "user8", age: 60, item: {"A": 113, "B": 211}, number: [ { "n1":5, "n2":4 }, { "n1":8, "n2":5 }, { "n1":7, "n2":6 } ] },
...  { name: "user9", age: 75, item: {"A": 118, "B": 218}, number: [ { "n1":1, "n2":3 }, { "n1":8, "n2":5 }, { "n1":7, "n2":6 } ] }
... ])
{
        "acknowledged" : true,
        "insertedIds" : [
                ObjectId("64b8d2404b442dde59447cf0"),
                ObjectId("64b8d2404b442dde59447cf1"),
                ObjectId("64b8d2404b442dde59447cf2"),
                ObjectId("64b8d2404b442dde59447cf3"),
                ObjectId("64b8d2404b442dde59447cf4"),
                ObjectId("64b8d2404b442dde59447cf5"),
                ObjectId("64b8d2404b442dde59447cf6"),
                ObjectId("64b8d2404b442dde59447cf7"),
                ObjectId("64b8d2404b442dde59447cf8")
        ]
}

sit_rs1:PRIMARY> db.user.find()
{ "_id" : ObjectId("64b8d2404b442dde59447cf1"), "name" : "user2", "age" : 20, "item" : { "A" : 112, "B" : 212 }, "number" : [ { "n1" : 1, "n2" : 3 }, { "n1" : 4, "n2" : 5 }, { "n1" : 7, "n2" : 6 } ] }
{ "_id" : ObjectId("64b8d2404b442dde59447cf4"), "name" : "user5", "age" : 55, "item" : { "A" : 112, "B" : 215 }, "number" : [ { "n1" : 7, "n2" : 8 }, { "n1" : 8, "n2" : 4 }, { "n1" : 4, "n2" : 6 } ] }
{ "_id" : ObjectId("64b8d2404b442dde59447cf2"), "name" : "user3", "age" : 30, "item" : { "A" : 113, "B" : 213 }, "number" : [ { "n1" : 1, "n2" : 8 }, { "n1" : 2, "n2" : 6 }, { "n1" : 2, "n2" : 6 } ] }
{ "_id" : ObjectId("64b8d2404b442dde59447cf6"), "name" : "user7", "age" : 45, "item" : { "A" : 116, "B" : 216 }, "number" : [ { "n1" : 6, "n2" : 6 }, { "n1" : 8, "n2" : 5 }, { "n1" : 7, "n2" : 6 } ] }
{ "_id" : ObjectId("64b8d2404b442dde59447cf0"), "name" : "user1", "age" : 10, "item" : { "A" : 111, "B" : 211 }, "number" : [ { "n1" : 2, "n2" : 6 }, { "n1" : 3, "n2" : 3 }, { "n1" : 5, "n2" : 6 } ] }
{ "_id" : ObjectId("64b8d2404b442dde59447cf3"), "name" : "user4", "age" : 45, "item" : { "A" : 111, "B" : 211 }, "number" : [ { "n1" : 9, "n2" : 6 }, { "n1" : 2, "n2" : 4 }, { "n1" : 3, "n2" : 6 } ] }
{ "_id" : ObjectId("64b8d2404b442dde59447cf8"), "name" : "user9", "age" : 75, "item" : { "A" : 118, "B" : 218 }, "number" : [ { "n1" : 1, "n2" : 3 }, { "n1" : 8, "n2" : 5 }, { "n1" : 7, "n2" : 6 } ] }
{ "_id" : ObjectId("64b8d2404b442dde59447cf5"), "name" : "user6", "age" : 55, "item" : { "A" : 115, "B" : 212 }, "number" : [ { "n1" : 5, "n2" : 5 }, { "n1" : 8, "n2" : 5 }, { "n1" : 7, "n2" : 6 } ] }
{ "_id" : ObjectId("64b8d2404b442dde59447cf7"), "name" : "user8", "age" : 60, "item" : { "A" : 113, "B" : 211 }, "number" : [ { "n1" : 5, "n2" : 4 }, { "n1" : 8, "n2" : 5 }, { "n1" : 7, "n2" : 6 } ] }

查询 user 表中 age 字段的不同值, distinct 返回数组元素,如果需要统计其字段,请使用 length , 如下

# 查询全表,返回9行数据 
sit_rs1:PRIMARY> db.user.find({}, {age:1})
{ "_id" : ObjectId("64b8d2404b442dde59447cf1"), "age" : 20 }
{ "_id" : ObjectId("64b8d2404b442dde59447cf4"), "age" : 55 }
{ "_id" : ObjectId("64b8d2404b442dde59447cf2"), "age" : 30 }
{ "_id" : ObjectId("64b8d2404b442dde59447cf6"), "age" : 45 }
{ "_id" : ObjectId("64b8d2404b442dde59447cf0"), "age" : 10 }
{ "_id" : ObjectId("64b8d2404b442dde59447cf3"), "age" : 45 }
{ "_id" : ObjectId("64b8d2404b442dde59447cf8"), "age" : 75 }
{ "_id" : ObjectId("64b8d2404b442dde59447cf5"), "age" : 55 }
{ "_id" : ObjectId("64b8d2404b442dde59447cf7"), "age" : 60 }

# 查询 user 表中 age 字段的不同值,其中重复的45、55只出现一次
sit_rs1:PRIMARY> db.user.distinct("age")
[ 10, 20, 30, 45, 55, 60, 75 ]

# 统计distinct返回的元素个数
sit_rs1:PRIMARY> db.user.distinct("age").length
7

返回数组字段的不同值, 以下示例返回集合中所有文档中 number 数组字段的不同值,如下:

# 查询 
sit_rs1:PRIMARY> db.user.distinct("number")
[
        {
                "n1" : 1,
                "n2" : 3
        },
        {
                "n1" : 1,
                "n2" : 8
        },
        {
                "n1" : 2,
                "n2" : 4
        },
        {
                "n1" : 2,
                "n2" : 6
        },
        {
                "n1" : 3,
                "n2" : 3
        },
        {
                "n1" : 3,
                "n2" : 6
        },
        {
                "n1" : 4,
                "n2" : 5
        },
        {
                "n1" : 4,
                "n2" : 6
        },
        {
                "n1" : 5,
                "n2" : 4
        },
        {
                "n1" : 5,
                "n2" : 5
        },
        {
                "n1" : 5,
                "n2" : 6
        },
        {
                "n1" : 6,
                "n2" : 6
        },
        {
                "n1" : 7,
                "n2" : 6
        },
        {
                "n1" : 7,
                "n2" : 8
        },
        {
                "n1" : 8,
                "n2" : 4
        },
        {
                "n1" : 8,
                "n2" : 5
        },
        {
                "n1" : 9,
                "n2" : 6
        }
]

另外,还可以指定查询条件,从查询条件中返回字段的不同值 , 如下:

# 查询 age 在 20、55、45 的文档 
sit_rs1:PRIMARY> db.user.find({"age":{$in: [20, 55, 45]}})
{ "_id" : ObjectId("64b8d2404b442dde59447cf1"), "name" : "user2", "age" : 20, "item" : { "A" : 112, "B" : 212 }, "number" : [ { "n1" : 1, "n2" : 3 }, { "n1" : 4, "n2" : 5 }, { "n1" : 7, "n2" : 6 } ] }
{ "_id" : ObjectId("64b8d2404b442dde59447cf4"), "name" : "user5", "age" : 55, "item" : { "A" : 112, "B" : 215 }, "number" : [ { "n1" : 7, "n2" : 8 }, { "n1" : 8, "n2" : 4 }, { "n1" : 4, "n2" : 6 } ] }
{ "_id" : ObjectId("64b8d2404b442dde59447cf6"), "name" : "user7", "age" : 45, "item" : { "A" : 116, "B" : 216 }, "number" : [ { "n1" : 6, "n2" : 6 }, { "n1" : 8, "n2" : 5 }, { "n1" : 7, "n2" : 6 } ] }
{ "_id" : ObjectId("64b8d2404b442dde59447cf3"), "name" : "user4", "age" : 45, "item" : { "A" : 111, "B" : 211 }, "number" : [ { "n1" : 9, "n2" : 6 }, { "n1" : 2, "n2" : 4 }, { "n1" : 3, "n2" : 6 } ] }
{ "_id" : ObjectId("64b8d2404b442dde59447cf5"), "name" : "user6", "age" : 55, "item" : { "A" : 115, "B" : 212 }, "number" : [ { "n1" : 5, "n2" : 5 }, { "n1" : 8, "n2" : 5 }, { "n1" : 7, "n2" : 6 } ] }

# 条例 age 查询条件的文档中,返回 item.A 中的不同值,这里的 112 只返回一条。
sit_rs1:PRIMARY> db.user.distinct("item.A", {"age":{$in: [20, 55, 45]}})
[ 111, 112, 115, 116 ]

使用排序规则,比如我们统计不同值时,是否要区分字每大小写等。如下:

  • strength: 2 二级比较。排序规则执行次要差异的比较,例如变音符号。也就是说,排序规则执行基本字符(主要差异)和变音符号(次要差异)的比较。基本字符之间的差异优先于次要差异。
  • strength: 3 三级比较。排序规则执行最多三级差异的比较,例如大小写和字母变体。也就是说,排序规则执行基本字符(主要差异)、变音符号(次要差异)以及大小写和变体(第三次差异)的比较。基本字符之间的差异优先于次要差异,次要差异又优先于第三次差异。
    这是默认级别。
sit_rs1:PRIMARY> db.myColl.insertMany([
... {_id: 1, color: "réd", letter: "A" },
... { _id: 2, color: "red", letter: "a" },
... { _id: 3, color: "rEd", letter: "a" },
... ])
{ "acknowledged" : true, "insertedIds" : [ 1, 2, 3 ] }

 
sit_rs1:PRIMARY> db.myColl.find()
{ "_id" : 1, "color" : "réd", "letter" : "A" }
{ "_id" : 2, "color" : "red", "letter" : "a" }
{ "_id" : 3, "color" : "rEd", "letter" : "a" }

# 二级比较:   执行基本字符(主要差异)和变音符号(次要差异)的比较
sit_rs1:PRIMARY> db.myColl.distinct( "color", {}, { collation: { locale: "zh", strength: 2 } } )
[ "réd", "red" ]

# 三级比较【默认级别】: 基本字符(主要差异)、变音符号(次要差异)以及大小写和变体(第三次差异)的比较
sit_rs1:PRIMARY> db.myColl.distinct( "color", {}, { collation: { locale: "zh", strength: 3 } } )
[ "réd", "red", "rEd" ]

estimatedDocumentCount

定义: db.collection.estimatedDocumentCount(options) , options 选修的。影响计数行为的额外选项。

  • 返回集合或视图中所有文档的计数。
  • estimatedDocumentCount() 不采用查询过滤器,而是使用元数据返回集合的计数。
  • db.collection.estimatedDocumentCount() 可能不准确。
  • 在分片集群上,生成的计数将无法正确过滤掉 孤立文档。

下面的例子使用 db.collection.estimatedDocumentCount() 检索 user 集合中所有文档的计数:

sit_rs1:PRIMARY> db.user.estimatedDocumentCount({})
9


sit_rs1:PRIMARY> db.myColl.estimatedDocumentCount(); 
3

总结

  • 通过一些单一目的聚合如 count, distinct 可以很方便统计数量,查找不同值,但也要注意他们的一些行为,比如在分片集群的使用限制、以及在事务中执行计数操作。
  • 要在事务内执行计数操作,请使用 $count 聚合阶段或 $group(带有 $sum表达式)聚合阶段。

你可能感兴趣的:(MongoDB,mongodb,数据库)