MongoDB查询指定Lobby ID失败及返回玩家数据为空问题求助
MongoDB查询指定Lobby ID失败及返回玩家数据为空问题求助
看起来你在查询MongoDB里嵌套结构的Lobby数据时遇到了两个棘手的问题:要么查不到对应文档,要么查到了但玩家数据全是空的。我来帮你拆解问题根源,再给出可行的解决方案。
先说说你之前两种方法的问题
第一种方法查不到文档
你第一个CheckLobbyID函数直接用{"lobby_id": lobbyID}做过滤条件,但你的数据结构里,lobby_id是嵌套在DayX.LobbyY层级下的,顶层的Attendance文档根本没有这个字段,MongoDB当然找不到匹配的内容。第二种方法玩家数据为空
第二个方法用聚合的$or匹配到了包含目标Lobby的Attendance文档,但你尝试把整个Attendance对象解码成model.Lobby结构体——这明显不匹配啊!Attendance里是Day1到Day5的嵌套结构,而你要的是其中某一个具体的Lobby对象,字段对应不上,自然玩家数据就映射失败了。
给你一个可行的改进方案
我们可以通过聚合管道先定位到目标Lobby,再把它提取出来作为独立文档,这样就能正确解码到model.Lobby结构体里,玩家数据也能正常返回:
func CheckLobbyID(lobbyID string) (model.Lobby, error) { pipeline := mongo.Pipeline{ // 第一步:先匹配包含目标lobby_id的Attendance文档,缩小范围 {{"$match", bson.M{ "$or": []bson.M{ {"day1.lobby1.lobby_id": lobbyID}, {"day1.lobby2.lobby_id": lobbyID}, {"day1.lobby3.lobby_id": lobbyID}, {"day2.lobby1.lobby_id": lobbyID}, {"day2.lobby2.lobby_id": lobbyID}, {"day2.lobby3.lobby_id": lobbyID}, {"day3.lobby1.lobby_id": lobbyID}, {"day3.lobby2.lobby_id": lobbyID}, {"day3.lobby3.lobby_id": lobbyID}, {"day4.lobby1.lobby_id": lobbyID}, {"day4.lobby2.lobby_id": lobbyID}, {"day4.lobby3.lobby_id": lobbyID}, {"day5.lobby1.lobby_id": lobbyID}, {"day5.lobby2.lobby_id": lobbyID}, {"day5.lobby3.lobby_id": lobbyID}, }, }}}, // 第二步:把所有Day里的Lobby合并成一个数组,方便后续统一筛选 {{"$project", bson.M{ "lobbies": bson.M{ "$concatArrays": []interface{}{ []interface{}{"$day1.lobby1", "$day1.lobby2", "$day1.lobby3"}, []interface{}{"$day2.lobby1", "$day2.lobby2", "$day2.lobby3"}, []interface{}{"$day3.lobby1", "$day3.lobby2", "$day3.lobby3"}, []interface{}{"$day4.lobby1", "$day4.lobby2", "$day4.lobby3"}, []interface{}{"$day5.lobby1", "$day5.lobby2", "$day5.lobby3"}, }, }, }}}, // 第三步:把数组拆成单个文档 {{"$unwind", "$lobbies"}}, // 第四步:筛选出正好是目标lobby_id的那个Lobby {{"$match", bson.M{"lobbies.lobby_id": lobbyID}}}, // 第五步:把Lobby对象变成顶层文档,方便解码 {{"$replaceRoot", bson.M{"newRoot": "$lobbies"}}}, } ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() cursor, err := AttendanceCollection.Aggregate(ctx, pipeline) if err != nil { return model.Lobby{}, err } defer cursor.Close(ctx) var lobby model.Lobby // 读取第一个匹配的结果(因为lobby_id应该是唯一的) if cursor.Next(ctx) { err = cursor.Decode(&lobby) if err != nil { return model.Lobby{}, err } } else { return model.Lobby{}, errors.New("no Lobby with provided ID found") } // 检查游标是否有错误 if err = cursor.Err(); err != nil { return model.Lobby{}, err } return lobby, nil }
方案解释
- 第一步的
$match先过滤出包含目标Lobby的文档,避免全表扫描,提升查询效率 - 第二步把分散在各个Day里的Lobby合并成一个数组,让后续操作更统一
- 第三步
$unwind将数组拆成单个文档,这样我们就能单独处理每个Lobby - 第四步再次
$match精准定位到目标Lobby - 最后用
$replaceRoot把Lobby对象变成顶层文档,这样就能完美解码到你的model.Lobby结构体里,玩家数据也能正确映射
额外优化建议
- 添加索引提升查询速度
如果你的数据量比较大,建议给所有嵌套的lobby_id字段创建复合索引,比如:
db.attendance.createIndex({ "day1.lobby1.lobby_id": 1, "day1.lobby2.lobby_id": 1, "day1.lobby3.lobby_id": 1, "day2.lobby1.lobby_id": 1, // 依次添加剩下的day和lobby的lobby_id字段 })
这样第一步的$match会快很多。
- 优化数据结构
目前固定的Day1-Day5、Lobby1-Lobby3结构会让查询和维护都很繁琐,建议改成数组形式:
type Attendance struct { ID primitive.ObjectID `json:"_id,omitempty" bson:"_id,omitempty"` Days []Day `json:"days,omitempty" bson:"days,omitempty"` } type Day struct { DayNumber int `json:"day_number,omitempty" bson:"day_number,omitempty"` Date string `json:"date,omitempty" bson:"date,omitempty"` Lobbies []Lobby `json:"lobbies,omitempty" bson:"lobbies,omitempty"` }
这样查询时的条件可以简化成{"days.lobbies.lobby_id": lobbyID},聚合逻辑也会更简洁,后续增加天数或 Lobby 数量也不需要修改代码。
备注:内容来源于stack exchange,提问作者Lawrence Segun
相关产品推荐
相关产品推荐

