MongoDB: How to find by subdocument ID?

2020-06-17 06:26发布

问题:

I am trying to model the concept of games where teams of players compete against each other in MongoDB.

I have two collections: players and games.

This is how a document in games looks like.

{
    "_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,
                }
            ]
        }
    ]
}

Here's the task: given a player ID I want to find all games in which this player participated.

What I've tried:

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

However, this does not return anything.

By the way, I'm using Mongoose with the following schema:

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

teamSchema = Schema
    players: [ playerSchema ]

gameSchema = Schema
    teams: [ teamSchema ]

with the following CoffeeScript query:

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

which returns no results for any player ID.

回答1:

In your document:

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

The player field in the embedded collection of players is a BSON Id (i.e. it looks something like ObjectId("4e208e070347a90001000008")), so I think you should structure your query like so:

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

Note, I've dropped the _id -- provided that works in a mongo console, then I suspect the Coffee query will be similar (drop the _id portion).



回答2:

I used your feedback to restrict this example to a home and away team. Remember that Mongo is not a relational database and using relationships is often an indication of a relational-database mindset. Duplication is the key.

Instead of references from the game to specific player documents, I have stored the name of the player. I assume this is an immutable unique index. The player has been given a games array containing references to each game he played in. This is actually quite bad as we have to query again and populate this array. It would be better to store a name here as well, but since I don't know your situation I'm not sure if a game has an immutable representable name.

This basic idea has to be improved with error checking (e.g. middleware) but I leave that up to you.

// 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) {
});