【背景】
在使用MongoDB数据库时,为了减少关联操作,我们通常采用嵌套模型,数组对象是比较常见,例如商品的评论、关注好友等,通常返回前面N条或者第一条之类来减少网络流量(所有历史消息意义可能不大).另外就是单个有16M的限制,此时可能采用连接方式,将部分信息存储在另外一个集合中。最近刚好遇到一个find需求,针对结果集的数组只返回第一条元素。我们知道MongoDB针对数组操作符非常丰富。接下来主要介绍$,$elemMatch,$slice三个在project中来限制数组.主要包括相同点以及存在差异点。
【对比】
【相同点】
1、$、$elemMatch、$slice都可以返回数组中第一个元素.
2、如果projection中包括其他列信息,则返回其他列+数组第一个元素.
3、都不支持用于在view上进行find投影操作
【不同点】
1、$操作符根据查询语句中的条件且必须包括数组条件,将集合中每个文档的第一个匹配数组元素投影到集合中。
2、$elemMatch这允许您根据查询中没有的条件,需要在$elemMatch中显示数组条件指定,可以是单个字段也可以是组合字段。
3、$slice可以直接返回数组中第一个元素(注意不是满足数组条件的第一个元素,只是返回记录数组的第一个元素,如果查询条件是包括数组条件,此时用$slice会导致错误结果,建议使用$或者$elemMatch 或者$filter+$slice来代替,非数组条件时可以使用)
简述:都是根据条件返回数组中第一个满足条件的元素.区别在$是根据查询中条件来,而$elemMatch是需要显示指定一个条件,$slice无需要依赖查询条件。
【构造数据】
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岁的学生信息,因为数组满足查询条件,把相关学生信息都返回,接下来我们只需要返回一个学生信息。
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之前查询与投影数组必须是同一个数组。查询条件中只能指定一个数组查询条件,如果存在多个不同数组查询条件会出现错误的结果。
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必须显示指定数组条件,否则返回错误数据
【指定数组条件】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+$slice来替代,如果查询是普通条件,此时可以直接返回第一条记录。
2、例如根据商品查询TOp one或者N这种评论非常适合.
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之前版本忽略$后面的路径。从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
【针对多数组查询条件--导致意外结果】
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个操作符的学习与总结,欢迎大家一起来交流.




