前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >MongoDB中如何返回数组对象中第一个对象

MongoDB中如何返回数组对象中第一个对象

作者头像
徐靖
发布2022-09-22 11:46:16
12.7K0
发布2022-09-22 11:46:16
举报
文章被收录于专栏:DB说

背景

在使用MongoDB数据库时,为了减少关联操作,我们通常采用嵌套模型,数组对象是比较常见,例如商品的评论、关注好友等,通常返回前面N条或者第一条之类来减少网络流量(所有历史消息意义可能不大).另外就是单个有16M的限制,此时可能采用连接方式,将部分信息存储在另外一个集合中。最近刚好遇到一个find需求,针对结果集的数组只返回第一条元素。我们知道MongoDB针对数组操作符非常丰富。接下来主要介绍,elemMatch,

对比

相同点

1、elemMatch、

2、如果projection中包括其他列信息,则返回其他列+数组第一个元素.

3、都不支持用于在view上进行find投影操作

不同点

1、$操作符根据查询语句中的条件且必须包括数组条件,将集合中每个文档的第一个匹配数组元素投影到集合中。

2、elemMatch这允许您根据查询中没有的条件,需要在elemMatch中显示数组条件指定,可以是单个字段也可以是组合字段。

3、slice可以直接返回数组中第一个元素(注意不是满足数组条件的第一个元素,只是返回记录数组的第一个元素,如果查询条件是包括数组条件,此时用slice会导致错误结果,建议使用或者elemMatch 或者filter+slice来代替,非数组条件时可以使用)

简述:都是根据条件返回数组中第一个满足条件的元素.区别在是根据查询中条件来,而elemMatch是需要显示指定一个条件,

构造数据

代码语言:javascript
复制
db.xiaoxu.find().pretty();
{
        "_id" : 1,
        "zipcode" : "63109",
        "students" : [
                {
                        "name" : "john",
                        "school" : 102,
                        "age" : 10
                },
                {
                        "name" : "jess",
                        "school" : 102,
                        "age" : 11
                },
                {
                        "name" : "jeff",
                        "school" : 108,
                        "age" : 15
                }
        ],
        "grades" : [
                70,
                87,
                90
        ],
        "details" : {
                "colors" : [
                        "blue",
                        "red"
                ],
                "sizes" : [
                        "S",
                        "M",
                        "L"
                ]
        }
}
{
        "_id" : 2,
        "zipcode" : "63110",
        "students" : [
                {
                        "name" : "ajax",
                        "school" : 100,
                        "age" : 7
                },
                {
                        "name" : "achilles",
                        "school" : 100,
                        "age" : 8
                }
        ],
        "grades" : [
                90,
                88,
                92
        ],
        "details" : {
                "colors" : [
                        "black",
                        "yellow"
                ],
                "sizes" : [
                        "M",
                        "XXL",
                        "XL"
                ]
        }
}
{
        "_id" : 3,
        "zipcode" : "63109",
        "students" : [
                {
                        "name" : "ajax",
                        "school" : 100,
                        "age" : 7
                },
                {
                        "name" : "achilles",
                        "school" : 100,
                        "age" : 8
                }
        ],
        "grades" : [
                85,
                100,
                90
        ],
        "details" : {
                "colors" : [
                        "gilt",
                        "pink"
                ],
                "sizes" : [
                        "XL",
                        "XXL",
                        "XXXL"
                ]
        }
}
{
        "_id" : 4,
        "zipcode" : "63109",
        "students" : [
                {
                        "name" : "barney",
                        "school" : 102,
                        "age" : 7
                },
                {
                        "name" : "ruth",
                        "school" : 102,
                        "age" : 16
                }
        ],
        "grades" : [
                79,
                85,
                80
        ],
        "details" : {
                "colors" : [
                        "vermeil",
                        "cherry"
                ],
                "sizes" : [
                        "S",
                        "XL",
                        "XXL"
                ]
        }
}

返回满足查询条件数组的第一条记录

要求】:查询大于10岁的第一个学生信息(select * from students where age>10 limit 1)

1、首先查看所有满足条件,其中_id等1和4的2条记录满足,其中students还包括不大于10岁的学生信息,因为数组满足查询条件,把相关学生信息都返回,接下来我们只需要返回一个学生信息。

代码语言:javascript
复制
db.xiaoxu.find({"students.age":{$gt:10}}).pretty();
{
        "_id" : 1,
        "zipcode" : "63109",
        "students" : [
                {
                        "name" : "john",
                        "school" : 102,
                        "age" : 10
                },
                {
                        "name" : "jess",
                        "school" : 102,
                        "age" : 11
                },
                {
                        "name" : "jeff",
                        "school" : 108,
                        "age" : 15
                }
        ],
        "grades" : [
                70,
                87,
                90
        ],
        "details" : {
                "colors" : [
                        "blue",
                        "red"
                ],
                "sizes" : [
                        "S",
                        "M",
                        "L"
                ]
        }
}
{
        "_id" : 4,
        "zipcode" : "63109",
        "students" : [
                {
                        "name" : "barney",
                        "school" : 102,
                        "age" : 7
                },
                {
                        "name" : "ruth",
                        "school" : 102,
                        "age" : 16
                }
        ],
        "grades" : [
                79,
                85,
                80
        ],
        "details" : {
                "colors" : [
                        "vermeil",
                        "cherry"
                ],
                "sizes" : [
                        "S",
                        "XL",
                        "XXL"
                ]
        }
}

2、使用$操作来实现返回大于10岁的第一个学生信息

备注:经过验证确实是第一个大于10岁的学生信息,通过$投影操作符.4.4开始支持查询与投影是不同数组,4.4之前查询与投影数组必须是同一个数组。查询条件中只能指定一个数组查询条件,如果存在多个不同数组查询条件会出现错误的结果。

代码语言:javascript
复制
db.xiaoxu.find({"students.age":{$gt:10}},{"students.$":1}).pretty();
{
        "_id" : 1,
        "students" : [
                {
                        "name" : "jess",
                        "school" : 102,
                        "age" : 11
                }
        ]
}
{
        "_id" : 4,
        "students" : [
                {
                        "name" : "ruth",
                        "school" : 102,
                        "age" : 16
                }
        ]
}

3、使用$elemMatch操作来实现返回大于10岁的第一个学生信息

备注:$elemMatch必须显示指定数组条件,否则返回错误数据

代码语言:javascript
复制
【指定数组条件】
1、db.xiaoxu.find({"students.age":{$gt:10}},{"students":
{$elemMatch:{age:{$gt:10}}}}).pretty();
{
        "_id" : 1,
        "students" : [
                {
                        "name" : "jess",
                        "school" : 102,
                        "age" : 11
                }
        ]
}
{
        "_id" : 4,
        "students" : [
                {
                        "name" : "ruth",
                        "school" : 102,
                        "age" : 16
                }
        ]
}
【不指定条件--返回并不是大于10岁的学生信息】
db.xiaoxu.find({"students.age":{$gt:10}},{"students":{$elemMatch:{}}}).pretty();
{
        "_id" : 1,
        "students" : [
                {
                        "name" : "john",
                        "school" : 102,
                        "age" : 10
                }
        ]
}
{
        "_id" : 4,
        "students" : [
                {
                        "name" : "barney",
                        "school" : 102,
                        "age" : 7
                }
        ]
}

3、使用$slice操作来实现返回大于10岁的第一个学生信息

备注:1、slice只是返回数组中第一个元素,而不是满足数组条件的第一个元素,可以filter+

2、例如根据商品查询TOp one或者N这种评论非常适合.

代码语言:javascript
复制
1、使用$slice操作来实现返回大于10岁的第一个学生信息--结果是错误的
db.xiaoxu.find({"students.age":{$gt:10}},{"students":{$slice:1}}).
pretty();
{
        "_id" : 1,
        "zipcode" : "63109",
        "students" : [
                {
                        "name" : "john",
                        "school" : 102,
                        "age" : 10
                }
        ],
        "grades" : [
                70,
                87,
                90
        ],
        "details" : {
                "colors" : [
                        "blue",
                        "red"
                ],
                "sizes" : [
                        "S",
                        "M",
                        "L"
                ]
        }
}
{
        "_id" : 4,
        "zipcode" : "63109",
        "students" : [
                {
                        "name" : "barney",
                        "school" : 102,
                        "age" : 7
                }
        ],
        "grades" : [
                79,
                85,
                80
        ],
        "details" : {
                "colors" : [
                        "vermeil",
                        "cherry"
                ],
                "sizes" : [
                        "S",
                        "XL",
                        "XXL"
                ]
        }
}
2、【此时非要使用,可以结合$filter+$slice】
db.xiaoxu.aggregate([{$project:{students:{$slice:[{$filter:
{input:"$students",as:"st",cond:{$gt:["$$st.age",10]}}},1]}}},
{$match:{$expr:{$gt:[{$size:"$students"},0]}}}])
{ "_id" : 1, "students" : [ { "name" : "jess", "school" : 102, "age" : 11 } ] }
{ "_id" : 4, "students" : [ { "name" : "ruth", "school" : 102, "age" : 16 } ] } 

3、查询zipcode等于13000里面第一个学生信息--这个符合预期
 db.xiaoxu.find({zipcode:"13000"},{"students":{$slice:1}}).pretty();
{
        "_id" : 1,
        "zipcode" : "13000",
        "students" : [
                {
                        "name" : "john",
                        "school" : 102,
                        "age" : 10
                }
        ],
        "grades" : [
                70,
                87,
                90
        ],
        "details" : {
                "colors" : [
                        "blue",
                        "red"
                ],
                "sizes" : [
                        "S",
                        "M",
                        "L"
                ]
        }
}

注意事项

1、project里面只能使用一个,查询条件中只能使用一个数组查询条件,

存在多个不同数组时,会导致意外的行为,针对一个数组里面多个列需要使用$elemMatch

2、slice,从4,4版本开始,不支持在slice包括在表达式里面。在4.4之前版本中,首先返回匹配查询的结果数组中第一个元素,slice被忽略,4.4直接报错,同理从4.4开始支持在路径末尾,如果路径中间位置报错,4.4之前版本忽略

3、$slice针对数组与数组嵌套列同时出现在投影时,提示路径冲突.4.4之前版本支持,4.4需要使用aggregate中2次project操作.

【之前在中文社区中回答的问题】

https://mongoing.com/anspress/question/mongo-%e6%95%b0%e7%bb%84%e5%88%86%e9%a1%b5%e5%b9%b6%e8%bf%94%e5%9b%9e%e6%95%b0%e7%bb%84%e4%b8%ad%e6%8c%87%e5%ae%9a%e7%9a%84%e5%ad%97%e6%ae%b5

【针对多数组查询条件--导致意外结果】

代码语言:javascript
复制
db.xiaoxu.find({"students.school":102}).pretty();
{
        "_id" : 1,
        "zipcode" : "13000",
        "students" : [
                {
                        "name" : "john",
                        "school" : 102,
                        "age" : 10
                },
                {
                        "name" : "jess",
                        "school" : 102,
                        "age" : 11
                },
                {
                        "name" : "jeff",
                        "school" : 108,
                        "age" : 15
                }
        ],
        "grades" : [
                70,
                87,
                90
        ],
        "details" : {
                "colors" : [
                        "blue",
                        "red"
                ],
                "sizes" : [
                        "S",
                        "M",
                        "L"
                ]
        }
}
{
        "_id" : 4,
        "zipcode" : "63109",
        "students" : [
                {
                        "name" : "barney",
                        "school" : 102,
                        "age" : 7
                },
                {
                        "name" : "ruth",
                        "school" : 102,
                        "age" : 16
                }
        ],
        "grades" : [
                79,
                85,
                80
        ],
        "details" : {
                "colors" : [
                        "vermeil",
                        "cherry"
                ],
                "sizes" : [
                        "S",
                        "XL",
                        "XXL"
                ]
        }
}

【返回students数组中第二条记录,而不是第一条记录】
mongos> db.xiaoxu.find({"students.school":102,
"details.colors":"red"},{"students.$":1}).pretty();
{
"_id" : 1,
"students" : [
{
"name" : "jess",
"school" : 102,
"age" : 11
}
]
}
【可以使用elemMatch来解决】
db.xiaoxu.find({"details.colors":"red"},{"students":
{$elemMatch:{school:102}}}).pretty();
{
        "_id" : 1,
        "students" : [
                {
                        "name" : "john",
                        "school" : 102,
                        "age" : 10
                }
        ]
}

总结:以上就是对3个操作符的学习与总结,欢迎大家一起来交流.

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-02-22,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 DB说 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
云数据库 MongoDB
腾讯云数据库 MongoDB(TencentDB for MongoDB)是腾讯云基于全球广受欢迎的 MongoDB 打造的高性能 NoSQL 数据库,100%完全兼容 MongoDB 协议,支持跨文档事务,提供稳定丰富的监控管理,弹性可扩展、自动容灾,适用于文档型数据库场景,您无需自建灾备体系及控制管理系统。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档