MongoDB分组取每组中一条数据
需求背景有一个mongo collection,里面存放了运送货物的司机位置信息,字段主要有_id: mongodb默认的主键字段orderId:订单idpositionTime:位置上报时的时间戳lon:经度信息lat:纬度信息现在需要支持根据一批订单id,查询它们最新位置的经纬度。需求分析根据需求背景,可以知道,查询的条件是按照订单id作为分组,每个分组内按照positionTime降序排序,
·
需求背景
有一个mongo collection,里面存放了运送货物的司机位置信息,字段主要有
_id: mongodb默认的主键字段
orderId:订单id
positionTime:位置上报时的时间戳
lon:经度信息
lat:纬度信息
现在需要支持根据一批订单id,查询它们最新位置的经纬度。
需求分析
根据需求背景,可以知道,查询的条件是按照订单id作为分组,每个分组内按照positionTime降序排序,然后将分组内的第一条记录的所有字段返回即可。
在NoSQL Booster中查询的语句:
// _id被覆盖的查询方式
db.position_collection
.aggregate([
{
// 过滤出符合条件的记录,这里用订单id列表
$match: { orderId: {$in: [26556031845626880,26550496434145792]}}},
// 按照订单id升序,定位时间降序排序
{$sort: { orderId: 1, positionTime:-1}},
{$group:{
// 根据orderId字段分组,mongodb要求必须得用_id来表示分组的字段,但是这样查询出来的document中的_id字段就变成了orderId了
_id:"$orderId",
// 如果想把表本身的_id字段返回怎么办?可以加一行 _id:{$first: "$_id"}么?答案是不可以,mongodb会报错
//返回分组内的第一条记录的orderId
orderId:{$first: "$orderId"},
//返回分组内的第一条记录的positionTime
positionTime:{$first: "$positionTime"}
}
}
])
// limit后面应该用订单数量做个限制
.limit(10)
// _id不会被覆盖的查询方式
db.position_collection
.aggregate([
{$match: { orderId: {$in: [26556031845626880,26550496434145792]}}},
{$sort: { orderId: 1, positionTime:-1}},
{$group:{
_id:"$orderId",
// 通过$$ROOT拿到原来记录的信息,存到doc中
doc:{$first: "$$ROOT"},
}},
// 从doc中还原出原来记录的信息,也包括最原始的_id
{$replaceRoot: { newRoot: "$doc" }}
])
.limit(10)
Java Demo代码
import com.mongodb.client.AggregateIterable;
import com.mongodb.client.model.Accumulators;
import com.mongodb.client.model.Aggregates;
import com.mongodb.client.model.Filters;
import org.bson.BsonDocument;
import org.bson.BsonDocumentWriter;
import org.bson.Document;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;
public class MongoDemo {
private void queryLatestPosition(String collectionName,List<Long> orderIds) {
// 因为group后面需要根据orderId分组,取定位时间最大的那条记录,group后面要求用_id来表示分组字段,但是这会存在一个问题,
// mongodb查出来的document中原来的_id字段会被设置为一个document,如果想把原来的_id字段也拿出来,就要用replaceRoot
// 但是我们公司的mongo maven依赖中用到的mongo-java-driver-3.2.2版本的Aggregates中没有replaceRoot方法可用,于是就
// 模仿Aggregates.group方法中的操作来自己搞一个。
// 关于replaceRoot解决的问题,可以参考https://stackoverflow.com/questions/52566913/how-to-group-in-mongodb-and-return-all-fields-in-result/52578475
BsonDocumentWriter bsonDocumentWriter = new BsonDocumentWriter(new BsonDocument());
bsonDocumentWriter.writeStartDocument();
bsonDocumentWriter.writeStartDocument("$replaceRoot");
bsonDocumentWriter.writeString("newRoot","$doc");
bsonDocumentWriter.writeEndDocument();
bsonDocumentWriter.writeEndDocument();
BsonDocument replaceRoot = bsonDocumentWriter.getDocument();
AggregateIterable<Document> aggregateIterable = myMongoClient.getCollection(collectionName)
.aggregate(Arrays.asList(
// 按照orderId列表过滤记录
Aggregates.match(Filters.in("orderId",orderIds)),
// 按照orderId升序、positionTime降序 排序
Aggregates.sort(new Document().append("orderId",1).append("positionTime",-1)),
// 根据orderId分组
Aggregates.group(
new Document("_id", "$orderId"),
Arrays.asList(
// 取分组第一条数据,即positionTime最大的那条,放到doc字段里,供后续的replaceRoot替换用
Accumulators.first("doc","$$ROOT"))
),
// 如果你的API中有Aggregates.replaceRoot,直接使用就可以了
replaceRoot,
Aggregates.limit(orderIds.size())
))
//设置超时时间
.maxTime(4000, TimeUnit.MILLISECONDS);
for (Document document : aggregateIterable) {
// 遍历document进行处理
}
}
}
参考资料:
1.https://stackoverflow.com/questions/52566913/how-to-group-in-mongodb-and-return-all-fields-in-result/52578475
更多推荐
已为社区贡献2条内容
所有评论(0)