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