MongoDB 聚合是一种强大的数据处理工具,可以根据指定的条件对集合中的文档进行分组、筛选、计算和转换,以生成复杂的数据汇总结果。聚合操作通常通过聚合管道来实现,其中包含一个或多个阶段,每个阶段都可以执行不同的数据处理操作。本文主要介绍MongoDB 聚合分组等操作,以及去除数据集中重复数据的方法。

1、聚合管道介绍

MongoDB 聚合管道是一种数据处理框架,可以对 MongoDB 中的文档进行复杂的数据处理和转换操作,以生成所需的数据汇总结果。聚合管道由一个或多个阶段组成,每个阶段都代表了一种数据处理操作,这些操作按顺序依次执行,最终生成最终的聚合结果。

聚合管道的基本原理是将输入文档作为输入,并通过一系列阶段逐步转换和处理这些文档,最终得到输出结果。每个阶段都可以对文档进行筛选、分组、投影、排序、限制等操作,从而实现复杂的数据聚合和分析功能。

1)常用管道命令

阶段

描述

​ ​$project​​

重塑文档流。​ ​$project​​ 可以重命名、添加或删除字段,以及创建计算值和子文档。

​ ​$match​​

过滤文档流,并只允许匹配的文档传递到下一个管道阶段。​ ​$match​​ 使用标准的 MongoDB 查询。

​ ​$limit​​

限制聚合管道中文档的数量。

​ ​$skip​​

跳过管道中指定数量的文档,并返回剩余的文档。

​ ​$unwind​​

将一组文档拆分成单个文档流。

​ ​$group​​

将文档分组,以便根据文档集合计算聚合值。

$lookup

用于引入其它集合的数据 (表关联查询)

​ ​$sort​​

将所有输入文档按排序顺序返回。

​ ​$geoNear​​

基于与地理位置点的接近程度,返回有序的文档流。

SQL

MongoDB

表达式

WHERE

$match

条件筛选

GROUP BY

$group

分组聚合

HAVING

$match

分组后条件筛选

SELECT

$project

投影

ORDER BY

$sort

排序

LIMIT

$limit

限制数量

SUM()

$sum

求和

COUNT()

$sum

统计数量

join

$lookup

联表查询

2)常用表达式

处理输入文档并输出

语法:表达式:‘$列名’

表达式操作符

描述

$addToSet

将文档指定字段的值去重

$max

文档指定字段的最大值

$min

文档指定字段的最小值

$sum

文档指定字段求和

$avg

文档指定字段求平均

$gt

大于给定值

$lt

小于给定值

$eq

等于给定值

2、MongoDB 聚合操作使用示例

文档结构:

{
    "_id" : ObjectId("5f14d39e6d3c96242c09ce9b"),
    "Id" : 5,
    "title" : "Python 数据类型字典(Dictionary)",
    "relateId" : 31,
    "relateTitle" : "Java数据类型(基本类型和引用类型)",
    "scores" : 0.442981194961,
    "isActive" : true,
    "createTime" : ISODate("2020-07-20T15:13:34.555+08:00"),
    "updateTime" : ISODate("2020-07-20T15:13:34.555+08:00"),
    "createUser" : "levi",
    "updateUser" : "levi"
}

1)$project

修改文档的结构,可以用来重命名、增加或删除文档中的字段;

例如,

db.getCollection('ArticleRelation').aggregate([ { $project:{title:1, relateTitle:1 } } ])

输出:

/* 1 */
{
    "_id" : ObjectId("5f14d39e6d3c96242c09ce9b"),
    "title" : "Python 数据类型字典(Dictionary)",
    "relateTitle" : "Java数据类型(基本类型和引用类型)"
}

/* 2 */
{
    "_id" : ObjectId("5f14d4816d3c96242c09cede"),
    "title" : "Js(Javascript)中的apply方法的使用",
    "relateTitle" : "Js(Javascript)中的call方法的使用"
}

/* 3 */
{
    "_id" : ObjectId("5f14d4816d3c96242c09cedf"),
    "title" : "Js(Javascript)中的apply方法的使用",
    "relateTitle" : "Js(Javascript)中的bind方法的使用"
}

2)$match

用于过滤文档。用法类似于 find() 方法中的参数。

db.getCollection('ArticleRelation').aggregate([ 
{ $project:{title:1, relateTitle:1,isActive:1 } },
{ $match:{"isActive":{"$eq":true}}}])

输出结果:

/* 1 */
{
    "_id" : ObjectId("5f14d39e6d3c96242c09ce9b"),
    "title" : "Python 数据类型字典(Dictionary)",
    "relateTitle" : "Java数据类型(基本类型和引用类型)",
    "isActive" : true
}

/* 2 */
{
    "_id" : ObjectId("5f14d4816d3c96242c09cede"),
    "title" : "Js(Javascript)中的apply方法的使用",
    "relateTitle" : "Js(Javascript)中的call方法的使用",
    "isActive" : true
}

/* 3 */
{
    "_id" : ObjectId("5f14d4816d3c96242c09cedf"),
    "title" : "Js(Javascript)中的apply方法的使用",
    "relateTitle" : "Js(Javascript)中的bind方法的使用",
    "isActive" : true
}

3)$group

将集合中的文档进行分组,可用于统计结果

db.getCollection("ArticleRelation").aggregate(
    [
        { 
            "$group" : {
                "_id" : {
                    "Id" : "$Id", 
                    "relateId" : "$relateId"
                }, 
                    "COUNT(*)" : {
                    "$sum" : NumberInt(1)
                },
                "dups": {"$addToSet": '$_id'}
            
            }
        }, 
        { 
            "$project" : {
                "relateId" : "$_id.relateId", 
                "Id" : "$_id.Id", 
                "COUNT(*)" : "$COUNT(*)", 
                "dups":"$dups",
                "_id" : NumberInt(0)
            }
        }
    ], 
    { 
        "allowDiskUse" : true
    }
)

输出结果:

/* 1 */
{
    "relateId" : 3041,
    "Id" : 3051,
    "COUNT(*)" : 1,
    "dups" : [ 
        ObjectId("64c0ec5bf9004501a2230504")
    ]
}

/* 2 */
{
    "relateId" : 3039,
    "Id" : 3051,
    "COUNT(*)" : 1,
    "dups" : [ 
        ObjectId("64c0ec5af9004501a2230503")
    ]
}

/* 3 */
{
    "relateId" : 3035,
    "Id" : 3051,
    "COUNT(*)" : 1,
    "dups" : [ 
        ObjectId("64c0ec57f9004501a22304ff")
    ]
}

4)$sort

db.getCollection('ArticleRelation').aggregate([ 
{ $project:{title:1, createTime:1,relateTitle:1,isActive:1 } },
{ $match:{"isActive":{"$eq":true}}},
{ $sort:{"createTime":-1}}])

输出结果:

/* 1 */
{
    "_id" : ObjectId("64c0f1d1f900450c9e3a8702"),
    "title" : "Linux系统简介及各发行版之间区别",
    "relateTitle" : ".NET Core 、 .NET 5、.NET 6和.NET 7 简介及区别",
    "isActive" : true,
    "createTime" : ISODate("2023-07-26T18:13:37.258+08:00")
}

/* 2 */
{
    "_id" : ObjectId("64c0f123f900450c9e3a8701"),
    "title" : "Linux系统简介及各发行版之间区别",
    "relateTitle" : "XML和JSON格式简介、区别及解析",
    "isActive" : true,
    "createTime" : ISODate("2023-07-26T18:10:43.731+08:00")
}

/* 3 */
{
    "_id" : ObjectId("64c0ec5bf9004501a2230504"),
    "title" : "Docker 常用命令详解",
    "relateTitle" : "Docker CLI docker wait 常用命令",
    "isActive" : true,
    "createTime" : ISODate("2023-07-26T17:50:19.347+08:00")
}

5)$limit 和 $skip

$limit限制结果的数量,$skip跳过文档的数量。

db.getCollection('ArticleRelation').aggregate([ 
{ $project:{title:1, createTime:1,relateTitle:1,isActive:1 } },
{ $match:{"isActive":{"$eq":true}}},
{ $sort:{"createTime":-1}},
{ "$skip":1 },
{ "$limit":2 } ])

输出结果:

/* 1 */
{
    "_id" : ObjectId("64c0f123f900450c9e3a8701"),
    "title" : "Linux系统简介及各发行版之间区别",
    "relateTitle" : "XML和JSON格式简介、区别及解析",
    "isActive" : true,
    "createTime" : ISODate("2023-07-26T18:10:43.731+08:00")
}

/* 2 */
{
    "_id" : ObjectId("64c0ec5bf9004501a2230504"),
    "title" : "Docker 常用命令详解",
    "relateTitle" : "Docker CLI docker wait 常用命令",
    "isActive" : true,
    "createTime" : ISODate("2023-07-26T17:50:19.347+08:00")
}

6)$lookup

用于表关联

db.getCollection('ArticleRelation').aggregate([ 
{"$lookup": { 
    from: "Articles", 
    localField: "Id", 
    foreignField: "Id", 
    as: "articles" 
  } 
 } 
])

输出结果:

/* 1 */
{
    "_id" : ObjectId("5f14d39e6d3c96242c09ce9b"),
    "Id" : 5,
    "title" : "Python 数据类型字典(Dictionary)",
    "relateId" : 31,
    "relateTitle" : "Java数据类型(基本类型和引用类型)",
    "scores" : 0.442981194961,
    "isActive" : true,
    "createTime" : ISODate("2020-07-20T15:13:34.555+08:00"),
    "updateTime" : ISODate("2020-07-20T15:13:34.555+08:00"),
    "createUser" : "levi",
    "updateUser" : "levi",
    "articles" : [ 
        {
            "_id" : ObjectId("5ba0bf50f900453f2c303d76"),
            "Id" : 5,
            "title" : "Python 数据类型字典(Dictionary)",
            "keyword" : "python字典,python dic(dictionary),",
            "content" :"Python 数据类型字典(Dictionary)",
            "is_active" : true,
            "create_time" : ISODate("2018-09-18T17:03:12.000+08:00"),
            "update_time" : ISODate("2018-11-05T16:07:54.345+08:00"),
            "create_user" : "levi",
            "update_user" : "levi",
        }
    ]
}

3、MongoDB 删除重复数据

var duplicates = [];
var data = db.getCollection("ArticleRelation").aggregate(
    [
        { 
            "$group" : {
                "_id" : {
                    "Id" : "$Id", 
                    "relateId" : "$relateId"
                }, 
                    "COUNT(*)" : {
                    "$sum" : NumberInt(1)
                },
                "dups": {"$addToSet": '$_id'}
            
            }
        }, 
        { 
            "$project" : {
                "relateId" : "$_id.relateId", 
                "Id" : "$_id.Id", 
                "COUNT(*)" : "$COUNT(*)", 
                "dups":"$dups",
                "_id" : NumberInt(0)
            }
        }, 
        { 
            "$match" : {
                "COUNT(*)" : {
                    "$gt" : NumberLong(1)
                }
            }
        }
    ], 
    { 
        "allowDiskUse" : true
    }
).forEach(function(doc) {
    
    doc.dups.shift();//删除重复数据中的第一条 
    doc.dups.forEach( function(dupId){ 
        duplicates.push(dupId);   // 获取所有需要删除的主键_id
        }
    )

    });

printjson(duplicates);
//删除数据
db.getCollection("ArticleRelation").remove({_id:{$in:duplicates}})

推荐文档