spamguy
spamguy

Reputation: 1566

Querying association tables in Sequelize

I have two tables (users and games) joined by an association table (game_players), creating a many-to-many relationship:

models.Game.belongsToMany(models.User, { through: models.GamePlayer, as: 'players' });
models.User.belongsToMany(models.Game, { through: models.GamePlayer, foreignKey: 'user_id' });

In addition to the foreign keys user_id and game_id, game_players has a few extra columns for link-specific data:

sequelize.define('game_player', {
    isReady: {
        defaultValue: false,
        type: Sequelize.BOOLEAN,
        field: 'is_ready'
    },
    isDisabled: {
        defaultValue: false,
        type: Sequelize.BOOLEAN,
        field: 'is_disabled'
    },
    powerPreferences: {
        type: Sequelize.TEXT,
        field: 'power_preferences'
    },
    power: {
        type: Sequelize.STRING(2),
        defaultValue: '?'
    }
}, {
    underscored: true
});

Suppose I want to fetch a game and eagerly load active players. This was my first effort:

db.models.Game.findAll({
    include: [{
        model: db.models.User,
        as: 'players',
        where: { 'game_player.isDisabled': false }
    }]
}).nodeify(cb);

This generates the following SQL, which throws the error Column players.game_player.isDisabled does not exist:

SELECT "game"."id", 
   "game"."name", 
   "game"."description", 
   "game"."variant", 
   "game"."status", 
   "game"."move_clock"                       AS "moveClock", 
   "game"."retreat_clock"                    AS "retreatClock", 
   "game"."adjust_clock"                     AS "adjustClock", 
   "game"."max_players"                      AS "maxPlayers", 
   "game"."created_at", 
   "game"."updated_at", 
   "game"."gm_id", 
   "game"."current_phase_id", 
   "players"."id"                            AS "players.id", 
   "players"."email"                         AS "players.email", 
   "players"."temp_email"                    AS "players.tempEmail", 
   "players"."password"                      AS "players.password", 
   "players"."password_salt"                 AS "players.passwordSalt", 
   "players"."action_count"                  AS "players.actionCount", 
   "players"."failed_action_count"           AS "players.failedActionCount", 
   "players"."created_at"                    AS "players.created_at", 
   "players"."updated_at"                    AS "players.updated_at", 
   "players.game_player"."is_ready"          AS 
   "players.game_player.isReady", 
   "players.game_player"."is_disabled"       AS 
   "players.game_player.isDisabled", 
   "players.game_player"."power_preferences" AS 
   "players.game_player.powerPreferences", 
   "players.game_player"."power"             AS "players.game_player.power", 
   "players.game_player"."created_at"        AS 
   "players.game_player.created_at", 
   "players.game_player"."updated_at"        AS 
   "players.game_player.updated_at", 
   "players.game_player"."game_id"           AS 
   "players.game_player.game_id", 
   "players.game_player"."user_id"           AS 
   "players.game_player.user_id" 
FROM   "games" AS "game" 
   INNER JOIN ("game_players" AS "players.game_player" 
               INNER JOIN "users" AS "players" 
                       ON "players"."id" = "players.game_player"."user_id") 
           ON "game"."id" = "players.game_player"."game_id" 
              AND "players"."game_player.isdisabled" = false;

Clearly Sequelize is wrapping my constraint alias with incorrect quotes: 'players'.'game_player.isdisabled' should be 'players.game_player'.isdisabled. How can I revise my Sequelize code above to correctly query this column?

Upvotes: 2

Views: 6707

Answers (3)

ytans
ytans

Reputation: 74

After searching around, I found that through.where can also be used:

db.models.Game.findAll({
    include: [{
        model: db.models.User,
        as: 'players',
        through: {  where: { isDisabled: false } }
    }]
})

Reference:
Is it possible to filter a query by the attributes in the association table with sequelize?
Eager loading with Many-to-Many relationships

Upvotes: 5

Aralizer
Aralizer

Reputation: 185

Your query should be on the join table with the 'where' condition, and then you should use the 'include' clause to include the two other models, like this:

    db.models.GamePlayer.findAll({
        where: {isDisabled: false},
        attributes: [],
        include: [models.User, models.Game]
    }).then(function(result){
        ....
    });

Upvotes: 0

spamguy
spamguy

Reputation: 1566

I got it, but only through manually browsing the repository's closed tickets and coming upon #4880.

Clauses using joined table columns that don't work out of the box can be wrapped in $. I honestly don't understand its magic, because I swear I don't see any documentation for it. Modifying my query above achieved what I wanted:

db.models.Game.findAll({
    include: [{
        model: db.models.User,
        as: 'players',
        where: { '$players.game_player.is_disabled$': false }
    }]
}).nodeify(cb);

Upvotes: 6

Related Questions