需求背景

有一个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

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐