Mongodb 关联表查询

前言

最近学习使用mongodb中间使用关联表查询问题遇到一些问题记录下来分享给大家。

mongodb关联查询

之前使用SQL语法来查询oracle、sqlserver、mysql表之间的关联,但是到mongodb之后完全无从下手,写法完全不一样,于是到网上查询mongodb关联表查询的写法,于是参考代码自己试着写了下,但是发现有好多问题,比如我有两个表user和apple,

user表:

idnameagecreateDt
ObjectId(1xxxx1)zhangsan32
ObjectId(2xxxx2)lishi23
ObjectId(3xxxx3)wangwu19
ObjectId(4xxxx4)liuliu28

apple表:

iduiddevicecreateDt
ObjectId(axxxxa)1xxxx110
ObjectId(bxxxxb)2xxxx22
ObjectId(cxxxxc)3xxxx37
ObjectId(dxxxxd)4xxxx41

user实体类:

@Setter
@Getter
public class User {
    private String id;
    private String name;
    private Integer age;
    private Date createDt;
}

apple实体类:

@Setter
@Getter
public class Apple {
    private String id;
    private String uid;
    private Integer device;
    private Date createDt;
}

user和apple之间是一对一关系,现在我的查询要求是:按device数量从大到小排序查询用户数据。

查询结果应该是:

idnameagecreateDt
ObjectId(1xxxx1)zhangsan32
ObjectId(3xxxx3)wangwu19
ObjectId(2xxxx2)lishi23
ObjectId(4xxxx4)liuliu28

我们分别使用user表和apple表做主表来查询,那么使用mongodb语法我们试着来写一下。

以user表做主表查询

// 1、添加_id字段类型转换
AddFieldsOperation addFieldsOperation = AddFieldsOperation.addField("_id").withValue(ConvertOperators.ToString.toString("$_id")).build();

// 2、按参数顺序:被关联表apple,主表user.id,被关联表apple.uid,别名
LookupOperation lookupOperation = Aggregation.lookup("apple", "_id", "uid", "apple_as");

// 3、查询哪些字段,类似sql里面的 select 选择器
ProjectionOperation project = Aggregation.project("name","age","createDt").and("apple_as.device").as("device");

// 4、按照apple.device数量排序
// 注意:不能使用apple_as.device,并且 device字段必须出现在project里面,否则查询失败
SortOperation sort = Aggregation.sort(Sort.Direction.DESC, "device");

// 5、添加取List中
List<AggregationOperation> operationList = Lists.newArrayList();
operationList.add(addFieldsOperation);
operationList.add(lookupOperation);
operationList.add(project);
operationList.add(sort);
Aggregation agg = Aggregation.newAggregation(operationList);

// 因为返回的是User实体类,所以不会出现device字段,你可以使用Map.class来返回想要的字段
AggregationResults<User> aggregationResults = mongoTemplate.aggregate(agg, "user", User.class);
// 6、返回关联查询结果
List<User> dataList = aggregationResults.getMappedResults();

注意:我们在查询之前要把主表的id转换成String类型,因为被关联表的uid就是String类型,否则查询失败

以apple表做主表查询

// 1、添加uid字段类型转换
AddFieldsOperation addFieldsOperation = AddFieldsOperation.addField("uid").withValue(ConvertOperators.ToObjectId.toObjectId("$uid")).build();

// 2、按参数顺序:被关联表user,主表apple.uid,被关联表user.id,别名
LookupOperation lookupOperation = Aggregation.lookup("user", "uid", "_id", "user_as");

// 3、查询哪些字段,类似sql里面的 select 选择器
// 注意:因为user是被关联表,所以要使用user_as别名的方式来获取用户信息字段
ProjectionOperation project = Aggregation.project("user_as.name","user_as.age","user_as.createDt","device");

// 4、按照apple.device数量排序
SortOperation sort = Aggregation.sort(Sort.Direction.DESC, "device");

// 5、添加取List中
List<AggregationOperation> operationList = Lists.newArrayList();
operationList.add(addFieldsOperation);
operationList.add(lookupOperation);
operationList.add(project);
operationList.add(sort);
Aggregation agg = Aggregation.newAggregation(operationList);

// 因为返回的是User实体类,所以不会出现device字段,你可以使用Map.class来返回想要的字段
AggregationResults<User> aggregationResults = mongoTemplate.aggregate(agg, "apple", User.class);
// 6、返回关联查询结果
List<User> dataList = aggregationResults.getMappedResults();

注意:我们在查询之前要把主表的uid转换ObjectId类型,因为被关联表的_id就是ObjectId类型,否则查询失败

2022-01-14更新

三表关联

List<AggregationOperation> operationList = Lists.newArrayList();
AddFieldsOperation add1FieldsOperation = AddFieldsOperation.addField("categoryId").withValue(ConvertOperators.ToObjectId.toObjectId("$categoryId")).build();

LookupOperation lookupOperation1 = Aggregation.lookup("urlCategory", "categoryId", "_id", "category_as");
LookupOperation lookupOperation2 = Aggregation.lookup("user", "uid", "uid", "user_as");

MatchOperation match = Aggregation.match(Criteria.where("uid").in(uids).and("isDelete").is(false));
ProjectionOperation project = Aggregation.project("user_as.userName","user_as.avatar","uid", "title", "url", "domain", "favicon", "isAttention", "isCopy", "createdDt")
        .and("category_as.name").as("categoryName")
        .andExpression("{ $dateToString: {date: '$createdDt', format: '%Y-%m-%d %H:%M:%S', timezone: '+08:00'}}").as("date")
        .and(ConvertOperators.Convert.convertValueOf("categoryId").to("string")).as("category_id");
// sort里面的clickCount字段必须从Project中取,否则 失败
SortOperation sort = Aggregation.sort(Sort.Direction.DESC, "createdDt");
FacetOperation facet = Aggregation.facet(
        count().as("count")).as("total")
        .and(skip(pageSize * (pageNum - 1)), limit(pageSize)).as("rows");
UnwindOperation unwindOperation1 = Aggregation.unwind("category_as");
UnwindOperation unwindOperation2 = Aggregation.unwind("user_as");
operationList.add(add1FieldsOperation);
operationList.add(lookupOperation1);
operationList.add(lookupOperation2);
operationList.add(unwindOperation1);
operationList.add(unwindOperation2);
//因为match里面含有url_as,所以lookupOperation要在match上面
operationList.add(match);
operationList.add(project);
operationList.add(sort);
operationList.add(facet);

Aggregation agg = Aggregation.newAggregation(operationList);
// collectionName:主表
AggregationResults<HashMap> aggregationResults = mongoTemplate.aggregate(agg, "url", HashMap.class);
return aggregationResults.getMappedResults();

2022-12-22更新

三表关联 一对多(多的这边进行条件筛选)

 // 1、添加uid字段类型转换
        AddFieldsOperation addFieldsOperation1 = AddFieldsOperation.addField("objectId").withValue(ConvertOperators.ToObjectId.toObjectId("$objectId")).build();
        AddFieldsOperation addFieldsOperation2 = AddFieldsOperation.addField("objectId").withValue(ConvertOperators.ToString.toString("$objectId")).build();
        // 2、按参数顺序:被关联表url,主表userEvent.objectId,被关联表url.id,别名
        LookupOperation lookupOperation1 = Aggregation.lookup("url", "objectId", "_id", "url_as");
        LookupOperation lookupOperation2 = Aggregation.lookup("userLikeUrl", "objectId", "urlId", "userLikeUrl_as");
        LookupOperation lookupOperation3 = Aggregation.lookup("userCopyUrl", "objectId", "fromUrlId", "userCopyUrl_as");

        MatchOperation matchOperation1 = Aggregation.match(Criteria.where("url_as.uid").in(uids).and("url_as.isDelete").is(false));

        ProjectionOperation projectionOperation1 = Aggregation.project("uid", "userName", "userAvatar", "action", "objectType", "objectOthers",
                "createdDt", "url_as", "userCopyUrl_as")
                .andExpression("toString(objectId)").as("objectId")
                // 先筛选出userLikeUrl_as.valid=1的列表到userLikeUrl_as_list中
                .and(filter("userLikeUrl_as").as("item")
                        .by(ComparisonOperators.Eq.valueOf("item.valid").equalToValue(1))).as("userLikeUrl_as_list");

        ProjectionOperation projectionOperation2 = Aggregation.project("uid", "userName", "userAvatar", "action", "objectType", "objectOthers",
                "createdDt", "url_as", "objectId", "userCopyUrl_as")
                // 再对userLikeUrl_as_list统计数量到userLikeUrl_count
                .andExpression("{$size: '$userLikeUrl_as_list'}").as("userLikeUrl_count")
                .andExpression("{$size: '$userCopyUrl_as'}").as("userCopyUrl_count");


        // 4、按照apple.device数量排序
        SortOperation sort = Aggregation.sort(Sort.Direction.DESC, "createdDt");

        FacetOperation facet = Aggregation.facet(count().as("count")).as("total").and(skip(pageSize * (pageNum - 1)), limit(pageSize)).as("rows");
        UnwindOperation unwindOperation1 = Aggregation.unwind("url_as");

        List<AggregationOperation> operationList = Lists.newArrayList();
        operationList.add(lookupOperation3);
        operationList.add(lookupOperation2);

        operationList.add(addFieldsOperation1);
        operationList.add(lookupOperation1);
        operationList.add(matchOperation1);

        operationList.add(unwindOperation1);

        operationList.add(projectionOperation1);
        operationList.add(projectionOperation2);

        operationList.add(sort);
        operationList.add(facet);
        Aggregation agg = Aggregation.newAggregation(operationList);
        // 因为返回的是User实体类,所以不会出现device字段,你可以使用Map.class来返回想要的字段
        AggregationResults<HashMap> aggregationResults = mongoTemplate.aggregate(agg, "userEvent", HashMap.class);
        return aggregationResults.getMappedResults();

注意看上面的projectionOperation1projectionOperation2,并且lookupOperation2中有userLikeUrl_as集合数据,然后我们在projectionOperation1中添加筛选条件过滤出userLikeUrl_as_list列表,如下所示:

.and(filter("userLikeUrl_as").as("item")
                        .by(ComparisonOperators.Eq.valueOf("item.valid").equalToValue(1))).as("userLikeUrl_as_list");

过滤条件是:item.valid=1的数据,接着我们再projectionOperation2中聚合数据统计userLikeUrl_as_list集合中的数量

 .andExpression("{$size: '$userLikeUrl_as_list'}").as("userLikeUrl_count")

总结

1、mongodb关联查询一定要注意ObjectId类型和String类型之间的转换,否则不成功
ObjectId转换String方法一:

.andExpression("toString(objectId)").as("objectId")

ObjectId转换String方法二:

.and(ConvertOperators.Convert.convertValueOf("categoryId").to("string")).as("category_id")

ObjectId转换String方法三:

AddFieldsOperation addFieldsOperation2 = AddFieldsOperation.addField("objectId").withValue(ConvertOperators.ToString.toString("$objectId")).build();

String转换ObjectId方法一:

AddFieldsOperation addFieldsOperation1 = AddFieldsOperation.addField("objectId").withValue(ConvertOperators.ToObjectId.toObjectId("$objectId")).build();

2、project中出现的字段会影响 sort排序
3、operationList的添加前后顺序也会影响查询
4、如果查询失败要多试几遍,到底是哪一步出现的影响,我也是不断的试错才总结出来的。
5、如果有条件的话可以加如下代码:

AggregationOperation match = Aggregation.match(Criteria.where("uid").is(uid));
LimitOperation limit = Aggregation.limit(10);

operationList.add(match);
operationList.add(limit);

引用

MongoTemplate聚合(一)$lookup

Mongo学习笔记(三) 通过Aggregation和lookup进行多级关联查询

Springboot整合MongoDB系列(五)---LookupOperation关联查询

mongoTemplate关联查询

Spring Data MongoDB: Projections and Aggregations

Java使用MongoTemplate操作MangoDB,实现根据时间等条件组合查询,解决ISODate的问题


全栈工程师进阶
日常学习总结与分享,包括:前端、后台与运维,讲解的知识点包括:javascript、vuejs、reactjs、springb...

Awbeci

2.9k 声望
192 粉丝
0 条评论
推荐阅读
Next.js-页面重复渲染引出的水合问题
我从2020年开始一直使用next.js做为我的前端SSR框架,使用@reduxjs/toolkit做为全局状态管理器,使用next-redux-wrapper协助next.js连接和合并redux中store数据并且保持不变,否则会导致数据重复渲染性能问题,但...

Awbeci1阅读 389评论 2

(学习到实践)七、mongodb测试,php+nginx负载均衡的部署
从测试容器中匹配搜索得到 mongod.conf.orig,设置可以启动,网上查找配置项反不能启动,原因是配置是yaml格式!好像听说过。百度查询得到:官方配置说明,网站卡得出奇。

沧浪水阅读 2.7k

MongoDB 4.4 扩展为副本集(qbit)
前言有一台单实例 MongoDB 服务器已经运行半年了,需要将其扩展为副本集3 台服务器的操作系统均为 Ubuntu 20.043 台服务器的 MongoDB 版本均为 4.4扩展示意图操作步骤备份 mongo0 上的数据修改配置文件 /etc/mong...

qbit阅读 2.3k

centos7安装mongodb
准备工作:1、在root目录下创建文件夹software {代码...} 2、进入software文件 {代码...} 以下是mongodb的具体安装步骤和文件配置1. 下载mongodb3.6.3版本 {代码...} 2. 解压文件 {代码...} 3. 把解压后文件移动...

sourcenode阅读 2.2k

Mac环境下安装MongoDB数据库
一、下载安装1.1 下载MongoDB首先,从MongoDB官网下载自己想要使用的版本。解压缩下载的压缩包重命名为mongodb,将mongodb文件夹复制到/usr/local目录下。当然,除了上面的安装方式外,我们还可以使用Mac OSX的br...

xiangzhihong1阅读 403

bug solved | zsh: command not found: mongo (Mac M1/M2 )
退回MongoDB 5 解决了,,,后来发现把5安装包的/usr/local/mongodb/bin目录下的mongo文件复制到6中同样也能解决。所以最终方法是:把5安装包的/usr/local/mongodb/bin目录下的mongo文件复制到6中

LiberHome2阅读 639评论 1

NineData 核心技术揭秘
NineData 是一个多云的数据管理平台,所以多云和多源是我们要解决的非常重要的问题,上图的最上层是我们支持的数据库类型,从设计上来说,我们要支持市面上所有主流的数据库,既支持MySQL、PostgreSQL、SQLServer...

NineData阅读 664

封面图

Awbeci

2.9k 声望
192 粉丝
宣传栏