MongoDB:如何通过子文档ID查找?

10

我正在尝试在MongoDB中建模“游戏”概念,其中“团队”由“玩家”竞争对手。

我有两个集合:playersgames

下面是 games 集合中文档的结构。

{
    "_id": { "$oid": "1" },
    "teams": [
        {
            "players": [
                {
                    "player": { "$oid": "2" },
                    "score": 500,
                },
                {
                    "player": { "$oid": "3" },
                    "score": 550,
                }
            ]
        },
        {
            "players": [
                {
                    "player": { "$oid": "4" },
                    "score": 500,
                },
                {
                    "player": { "$oid": "5" },
                    "score": 550,
                }
            ]
        }
    ]
}

这是任务:给定一个玩家ID,我想找到此玩家参加的所有比赛。

我尝试了以下方法:

db.games.find( { "teams.players.player._id": "2" } )

然而,这并没有返回任何内容。

顺便说一下,我正在使用以下模式的Mongoose:

playerSchema = Schema
    player: { type: Schema.ObjectId, ref: 'Player' }
    score: { type: Number }

teamSchema = Schema
    players: [ playerSchema ]

gameSchema = Schema
    teams: [ teamSchema ]

使用以下 CoffeeScript 查询:

Game.find 'teams.players.player._id': playerId

无论是哪个球员ID,都没有返回任何结果。


如果你们认为这个模式可以更好地设计,我也很愿意得到一些指点! - Philipp Jardas
你需要两个团队还是更多?我可以给出一个不错的例子来处理两个团队,但是更多团队会更加繁琐,并且需要额外的字段来进行搜索。 - Deathspike
只有两个团队。谢谢! - Philipp Jardas
2个回答

12

在您的文档中:

"players": [
            {
                "player": { "$oid": "4" },
                "score": 500,
            },
            {
                "player": { "$oid": "5" },
                "score": 550,
            }
        ]

嵌入式players集合中的player字段是BSON Id(即它看起来像ObjectId("4e208e070347a90001000008")),因此我认为您应该按以下方式构建查询:

db.games.find( { "teams.players.player": ObjectId("2") } )

注意,我已经删除了_id -- 假设在Mongo控制台中能够正常工作,那么我猜咖啡查询也会类似(删除_id部分)。


1
非常完美,谢谢!使用mongoose更容易,因为它会自动将字符串转换为对象id: Game.find 'teams.players.player': playerId - Philipp Jardas

2

我使用了你的反馈,将这个例子限制在主队和客队之间。请记住,Mongo不是关系型数据库,使用关系往往表明有关系型数据库的思维方式。重复是关键。

我没有使用对特定球员文档的引用,而是存储了球员的姓名。我假设这是一个不可变的唯一索引。球员被分配了一个包含对他参加的每场比赛的引用的游戏数组。实际上,这是相当糟糕的,因为我们必须再次进行查询并填充这个数组。最好在这里也存储一个名称,但由于我不知道你的情况,我不确定游戏是否有一个不可变的可表示的名称。

这个基本想法必须通过错误检查(例如中间件)得到改进,但我把这留给你自己来完成。

// Schemas
var playerSchema = new Schema({
    name: {type: String, index: true, unique: true},
    games: {type: [Schema.ObjectId], ref: 'Game'}
});

var gameSchema = new Schema({
    homeTeam: [{player: {name: String}, score: Number}]
    awayTeam: [{player: {name: String}, score: Number}]
});

// Models
var Player = mongoose.model('Player', playerSchema);
var Team = mongoose.model('Team', teamSchema);
var Game = mongoose.model('Game', gameSchema);

// Middleware
gameSchema.post('save', function (game) {
    var addMatchToPlayer = function(name) {
        Player.findOne({name: name}, function(err, player) {
            if (!err && player && player.games.indexOf(game._id) === -1) {
                player.games.push(game._id);
                player.save();
            }
        });
    }
    for (var i = 0; i < game.homeTeam.length; i++) {
        addMatchToPlayer(game.homeTeam[i].name);
    }
    for (var i = 0; i < game.awayTeam.length; i++) {
        addMatchToPlayer(game.awayTeam[i].name);
    }
});

// Queries
Player.findOne({name: 'Roel van Uden'}).populate('games').exec(function (err, player) {
});

非常感谢你的回答!你说得对,我确实已经太长时间在处理关系型数据了。但还有两件事情让我困扰:1. 使用对象ID作为对其他文档的引用有什么问题吗?名称可能不够不变,但ID是保证唯一的。2. 我不喜欢游戏和玩家之间的关系会被复制的想法。我真的很担心这样会导致不一致性。我是不是太过于SQL了? - Philipp Jardas
  1. 这是昂贵的。MongoDB没有JOIN,因此模拟一个至少需要两次访问数据库。在有数万亿条记录的情况下,SQL会变得缓慢。MongoDB之所以能扩展,是因为您可以嵌入相关数据,因此只需拉取一个文档,否则它将碰到相同的瓶颈。
  2. 是的,因为Mongo不了解您的数据,所以它不能知道关系,并且没有验证和FK完整性的概念。如果您仍然担心不一致性,请不要从SQL转移,因为SQL对于这些功能非常出色。 NoSQL为惊人的速度牺牲了很多便利性。
- Deathspike
Roel,谢谢你的解释。我会考虑这些方面。你有什么阅读建议吗? - Philipp Jardas

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接