高级 M:N 关联
在阅读本指南之前,请确保你已阅读 关联指南。
¥Make sure you have read the associations guide before reading this guide.
让我们从 User
和 Profile
之间的多对多关系的示例开始。
¥Let's start with an example of a Many-to-Many relationship between User
and Profile
.
const User = sequelize.define(
'user',
{
username: DataTypes.STRING,
points: DataTypes.INTEGER,
},
{ timestamps: false },
);
const Profile = sequelize.define(
'profile',
{
name: DataTypes.STRING,
},
{ timestamps: false },
);
定义多对多关系的最简单方法是:
¥The simplest way to define the Many-to-Many relationship is:
User.belongsToMany(Profile, { through: 'User_Profiles' });
Profile.belongsToMany(User, { through: 'User_Profiles' });
通过向上面的 through
传递一个字符串,我们要求 Sequelize 自动生成一个名为 User_Profiles
的模型作为直通表(也称为联结表),只有两列:userId
和 profileId
。将在这两列上建立复合唯一键。
¥By passing a string to through
above, we are asking Sequelize to automatically generate a model named User_Profiles
as the through table (also known as junction table), with only two columns: userId
and profileId
. A composite unique key will be established on these two columns.
我们还可以自己定义一个模型来用作直通表。
¥We can also define ourselves a model to be used as the through table.
const User_Profile = sequelize.define('User_Profile', {}, { timestamps: false });
User.belongsToMany(Profile, { through: User_Profile });
Profile.belongsToMany(User, { through: User_Profile });
与上面的效果完全相同。请注意,我们没有在 User_Profile
模型上定义任何属性。事实上,我们将其传递到 belongsToMany
调用中,告诉 Sequelize 自动创建两个属性 userId
和 profileId
,就像其他关联也会导致 Sequelize 自动向其中一个涉及的模型添加一列一样。
¥The above has the exact same effect. Note that we didn't define any attributes on the User_Profile
model. The fact that we passed it into a belongsToMany
call tells sequelize to create the two attributes userId
and profileId
automatically, just like other associations also cause Sequelize to automatically add a column to one of the involved models.
然而,我们自己定义模型有几个优点。例如,我们可以在 through 表上定义更多列:
¥However, defining the model by ourselves has several advantages. We can, for example, define more columns on our through table:
const User_Profile = sequelize.define(
'User_Profile',
{
selfGranted: DataTypes.BOOLEAN,
},
{ timestamps: false },
);
User.belongsToMany(Profile, { through: User_Profile });
Profile.belongsToMany(User, { through: User_Profile });
有了这个,我们现在可以在直通表中跟踪额外的信息,即 selfGranted
布尔值。例如,当调用 user.addProfile()
时,我们可以使用 through
选项传递额外列的值。
¥With this, we can now track an extra information at the through table, namely the selfGranted
boolean. For example, when calling the user.addProfile()
we can pass values for the extra columns using the through
option.
示例:
¥Example:
const amidala = await User.create({ username: 'p4dm3', points: 1000 });
const queen = await Profile.create({ name: 'Queen' });
await amidala.addProfile(queen, { through: { selfGranted: false } });
const result = await User.findOne({
where: { username: 'p4dm3' },
include: Profile,
});
console.log(result);
输出:
¥Output:
{
"id": 4,
"username": "p4dm3",
"points": 1000,
"profiles": [
{
"id": 6,
"name": "queen",
"User_Profile": {
"userId": 4,
"profileId": 6,
"selfGranted": false
}
}
]
}
你也可以在单个 create
调用中创建所有关系。
¥You can create all relationship in single create
call too.
示例:
¥Example:
const amidala = await User.create(
{
username: 'p4dm3',
points: 1000,
profiles: [
{
name: 'Queen',
User_Profile: {
selfGranted: true,
},
},
],
},
{
include: Profile,
},
);
const result = await User.findOne({
where: { username: 'p4dm3' },
include: Profile,
});
console.log(result);
输出:
¥Output:
{
"id": 1,
"username": "p4dm3",
"points": 1000,
"profiles": [
{
"id": 1,
"name": "Queen",
"User_Profile": {
"selfGranted": true,
"userId": 1,
"profileId": 1
}
}
]
}
你可能注意到 User_Profiles
表没有 id
字段。如上所述,它有一个复合唯一键。该复合唯一键的名称由 Sequelize 自动选择,但可以使用 uniqueKey
选项进行自定义:
¥You probably noticed that the User_Profiles
table does not have an id
field. As mentioned above, it has a composite unique key instead. The name of this composite unique key is chosen automatically by Sequelize but can be customized with the uniqueKey
option:
User.belongsToMany(Profile, {
through: User_Profiles,
uniqueKey: 'my_custom_unique',
});
如果需要,另一种可能性是强制直通表像其他标准表一样具有主键。为此,只需在模型中定义主键:
¥Another possibility, if desired, is to force the through table to have a primary key just like other standard tables. To do this, simply define the primary key in the model:
const User_Profile = sequelize.define(
'User_Profile',
{
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true,
allowNull: false,
},
selfGranted: DataTypes.BOOLEAN,
},
{ timestamps: false },
);
User.belongsToMany(Profile, { through: User_Profile });
Profile.belongsToMany(User, { through: User_Profile });
当然,上面仍然会创建两列 userId
和 profileId
,但模型不会在它们上设置复合唯一键,而是使用其 id
列作为主键。其他一切仍然会正常工作。
¥The above will still create two columns userId
and profileId
, of course, but instead of setting up a composite unique key on them, the model will use its id
column as primary key. Everything else will still work just fine.
通过表格与普通表格和 "超级多对多关联"
¥Through tables versus normal tables and the "Super Many-to-Many association"
现在我们将上面显示的最后一个多对多设置的用法与通常的一对多关系进行比较,以便最终我们得出 "超级多对多关系" 的概念。
¥Now we will compare the usage of the last Many-to-Many setup shown above with the usual One-to-Many relationships, so that in the end we conclude with the concept of a "Super Many-to-Many relationship".
模型回顾(稍作重命名)
¥Models recap (with minor rename)
为了使事情更容易理解,我们将 User_Profile
模型重命名为 grant
。请注意,一切的工作方式都与以前相同。我们的型号是:
¥To make things easier to follow, let's rename our User_Profile
model to grant
. Note that everything works in the same way as before. Our models are:
const User = sequelize.define(
'user',
{
username: DataTypes.STRING,
points: DataTypes.INTEGER,
},
{ timestamps: false },
);
const Profile = sequelize.define(
'profile',
{
name: DataTypes.STRING,
},
{ timestamps: false },
);
const Grant = sequelize.define(
'grant',
{
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true,
allowNull: false,
},
selfGranted: DataTypes.BOOLEAN,
},
{ timestamps: false },
);
我们使用 Grant
模型作为直通表,在 User
和 Profile
之间建立了多对多关系:
¥We established a Many-to-Many relationship between User
and Profile
using the Grant
model as the through table:
User.belongsToMany(Profile, { through: Grant });
Profile.belongsToMany(User, { through: Grant });
这会自动将列 userId
和 profileId
添加到 Grant
模型中。
¥This automatically added the columns userId
and profileId
to the Grant
model.
注意:如上所示,我们选择强制 grant
模型具有单个主键(通常称为 id
)。这对于即将定义的超级多对多关系是必要的。
¥Note: As shown above, we have chosen to force the grant
model to have a single primary key (called id
, as usual). This is necessary for the Super Many-to-Many relationship that will be defined soon.
使用一对多关系代替
¥Using One-to-Many relationships instead
如果我们不设置上面定义的多对多关系,而是执行以下操作会怎样?
¥Instead of setting up the Many-to-Many relationship defined above, what if we did the following instead?
// Setup a One-to-Many relationship between User and Grant
User.hasMany(Grant);
Grant.belongsTo(User);
// Also setup a One-to-Many relationship between Profile and Grant
Profile.hasMany(Grant);
Grant.belongsTo(Profile);
结果本质上是一样的!这是因为 User.hasMany(Grant)
和 Profile.hasMany(Grant)
会自动将 userId
和 profileId
列分别添加到 Grant
中。
¥The result is essentially the same! This is because User.hasMany(Grant)
and Profile.hasMany(Grant)
will automatically add the userId
and profileId
columns to Grant
, respectively.
这表明一个多对多关系与两个一对多关系没有太大区别。数据库中的表看起来是一样的。
¥This shows that one Many-to-Many relationship isn't very different from two One-to-Many relationships. The tables in the database look the same.
唯一的区别是当你尝试使用 Sequelize 执行预加载时。
¥The only difference is when you try to perform an eager load with Sequelize.
// With the Many-to-Many approach, you can do:
User.findAll({ include: Profile });
Profile.findAll({ include: User });
// However, you can't do:
User.findAll({ include: Grant });
Profile.findAll({ include: Grant });
Grant.findAll({ include: User });
Grant.findAll({ include: Profile });
// On the other hand, with the double One-to-Many approach, you can do:
User.findAll({ include: Grant });
Profile.findAll({ include: Grant });
Grant.findAll({ include: User });
Grant.findAll({ include: Profile });
// However, you can't do:
User.findAll({ include: Profile });
Profile.findAll({ include: User });
// Although you can emulate those with nested includes, as follows:
User.findAll({
include: {
model: Grant,
include: Profile,
},
}); // This emulates the `User.findAll({ include: Profile })`, however
// the resulting object structure is a bit different. The original
// structure has the form `user.profiles[].grant`, while the emulated
// structure has the form `user.grants[].profiles[]`.
两全其美的:超级多对多关系
¥The best of both worlds: the Super Many-to-Many relationship
我们可以简单地将上面显示的两种方法结合起来!
¥We can simply combine both approaches shown above!
// The Super Many-to-Many relationship
User.belongsToMany(Profile, { through: Grant });
Profile.belongsToMany(User, { through: Grant });
User.hasMany(Grant);
Grant.belongsTo(User);
Profile.hasMany(Grant);
Grant.belongsTo(Profile);
这样,我们就可以进行各种预加载:
¥This way, we can do all kinds of eager loading:
// All these work:
User.findAll({ include: Profile });
Profile.findAll({ include: User });
User.findAll({ include: Grant });
Profile.findAll({ include: Grant });
Grant.findAll({ include: User });
Grant.findAll({ include: Profile });
我们甚至可以执行各种深度嵌套包括:
¥We can even perform all kinds of deeply nested includes:
User.findAll({
include: [
{
model: Grant,
include: [User, Profile],
},
{
model: Profile,
include: {
model: User,
include: {
model: Grant,
include: [User, Profile],
},
},
},
],
});
别名和自定义键名
¥Aliases and custom key names
与其他关系类似,可以为多对多关系定义别名。
¥Similarly to the other relationships, aliases can be defined for Many-to-Many relationships.
在继续之前,请回忆一下 关联指南 上的 belongsTo
的别名示例。请注意,在这种情况下,定义关联会影响包含的完成方式(即传递关联名称)和 Sequelize 为外键选择的名称(在该示例中,leaderId
是在 Ship
模型上创建的)。
¥Before proceeding, please recall the aliasing example for belongsTo
on the associations guide. Note that, in that case, defining an association impacts both the way includes are done (i.e. passing the association name) and the name Sequelize chooses for the foreign key (in that example, leaderId
was created on the Ship
model).
为 belongsToMany
关联定义别名也会影响包含的执行方式:
¥Defining an alias for a belongsToMany
association also impacts the way includes are performed:
Product.belongsToMany(Category, {
as: 'groups',
through: 'product_categories',
});
Category.belongsToMany(Product, { as: 'items', through: 'product_categories' });
// [...]
await Product.findAll({ include: Category }); // This doesn't work
await Product.findAll({
// This works, passing the alias
include: {
model: Category,
as: 'groups',
},
});
await Product.findAll({ include: 'groups' }); // This also works
然而,这里定义别名与外键名称无关。在 through 表中创建的两个外键的名称仍然由 Sequelize 根据关联模型的名称构造。通过检查上例中的 through 表生成的 SQL 可以很容易地看出这一点:
¥However, defining an alias here has nothing to do with the foreign key names. The names of both foreign keys created in the through table are still constructed by Sequelize based on the name of the models being associated. This can readily be seen by inspecting the generated SQL for the through table in the example above:
CREATE TABLE IF NOT EXISTS `product_categories` (
`createdAt` DATETIME NOT NULL,
`updatedAt` DATETIME NOT NULL,
`productId` INTEGER NOT NULL REFERENCES `products` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
`categoryId` INTEGER NOT NULL REFERENCES `categories` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
PRIMARY KEY (`productId`, `categoryId`)
);
我们可以看到外键是 productId
和 categoryId
。要更改这些名称,Sequelize 分别接受选项 foreignKey
和 otherKey
(即 foreignKey
定义直通关系中源模型的键,otherKey
定义目标模型的键):
¥We can see that the foreign keys are productId
and categoryId
. To change these names, Sequelize accepts the options foreignKey
and otherKey
respectively (i.e., the foreignKey
defines the key for the source model in the through relation, and otherKey
defines it for the target model):
Product.belongsToMany(Category, {
through: 'product_categories',
foreignKey: 'objectId', // replaces `productId`
otherKey: 'typeId', // replaces `categoryId`
});
Category.belongsToMany(Product, {
through: 'product_categories',
foreignKey: 'typeId', // replaces `categoryId`
otherKey: 'objectId', // replaces `productId`
});
生成的 SQL:
¥Generated SQL:
CREATE TABLE IF NOT EXISTS `product_categories` (
`createdAt` DATETIME NOT NULL,
`updatedAt` DATETIME NOT NULL,
`objectId` INTEGER NOT NULL REFERENCES `products` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
`typeId` INTEGER NOT NULL REFERENCES `categories` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
PRIMARY KEY (`objectId`, `typeId`)
);
如上所示,当你使用两个 belongsToMany
调用定义多对多关系时(这是标准方式),你应该在两个调用中适当地提供 foreignKey
和 otherKey
选项。如果仅在其中一个调用中传递这些选项,Sequelize 行为将不可靠。
¥As shown above, when you define a Many-to-Many relationship with two belongsToMany
calls (which is the standard way), you should provide the foreignKey
and otherKey
options appropriately in both calls. If you pass these options in only one of the calls, the Sequelize behavior will be unreliable.
自引用
¥Self-references
Sequelize 直观地支持自引用多对多关系:
¥Sequelize supports self-referential Many-to-Many relationships, intuitively:
Person.belongsToMany(Person, { as: 'Children', through: 'PersonChildren' });
// This will create the table PersonChildren which stores the ids of the objects.
从直通表中指定属性
¥Specifying attributes from the through table
默认情况下,当预加载多对多关系时,Sequelize 将返回以下结构的数据(基于本指南中的第一个示例):
¥By default, when eager loading a many-to-many relationship, Sequelize will return data in the following structure (based on the first example in this guide):
// User.findOne({ include: Profile })
{
"id": 4,
"username": "p4dm3",
"points": 1000,
"profiles": [
{
"id": 6,
"name": "queen",
"grant": {
"userId": 4,
"profileId": 6,
"selfGranted": false
}
}
]
}
请注意,外部对象是 User
,它有一个名为 profiles
的字段,它是一个 Profile
数组,这样每个 Profile
都带有一个名为 grant
的额外字段,它是 Grant
实例。这是 Sequelize 在从多对多关系进行预加载时创建的默认结构。
¥Notice that the outer object is an User
, which has a field called profiles
, which is a Profile
array, such that each Profile
comes with an extra field called grant
which is a Grant
instance. This is the default structure created by Sequelize when eager loading from a Many-to-Many relationship.
但是,如果你只需要直通表的部分属性,则可以在 attributes
选项中提供一个包含所需属性的数组。例如,如果你只需要 through 表中的 selfGranted
属性:
¥However, if you want only some of the attributes of the through table, you can provide an array with the attributes you want in the attributes
option. For example, if you only want the selfGranted
attribute from the through table:
User.findOne({
include: {
model: Profile,
through: {
attributes: ['selfGranted'],
},
},
});
输出:
¥Output:
{
"id": 4,
"username": "p4dm3",
"points": 1000,
"profiles": [
{
"id": 6,
"name": "queen",
"grant": {
"selfGranted": false
}
}
]
}
如果你根本不需要嵌套的 grant
字段,请使用 attributes: []
:
¥If you don't want the nested grant
field at all, use attributes: []
:
User.findOne({
include: {
model: Profile,
through: {
attributes: [],
},
},
});
输出:
¥Output:
{
"id": 4,
"username": "p4dm3",
"points": 1000,
"profiles": [
{
"id": 6,
"name": "queen"
}
]
}
如果你使用 mixins(例如 user.getProfiles()
)而不是 finder 方法(例如 User.findAll()
),则必须使用 joinTableAttributes
选项:
¥If you are using mixins (such as user.getProfiles()
) instead of finder methods (such as User.findAll()
), you have to use the joinTableAttributes
option instead:
someUser.getProfiles({ joinTableAttributes: ['selfGranted'] });
输出:
¥Output:
[
{
"id": 6,
"name": "queen",
"grant": {
"selfGranted": false
}
}
]
多对多对多关系及其他关系
¥Many-to-many-to-many relationships and beyond
假设你正在尝试为游戏锦标赛建模。有球员,也有球队。团队进行游戏。然而,球员可以在锦标赛中途更换球队(但不能在比赛中途)。因此,给定一场特定的比赛,有某些球队参加该比赛,并且每个球队都有一组球员(针对该比赛)。
¥Consider you are trying to model a game championship. There are players and teams. Teams play games. However, players can change teams in the middle of the championship (but not in the middle of a game). So, given one specific game, there are certain teams participating in that game, and each of these teams has a set of players (for that game).
因此,我们首先定义三个相关模型:
¥So we start by defining the three relevant models:
const Player = sequelize.define('Player', { username: DataTypes.STRING });
const Team = sequelize.define('Team', { name: DataTypes.STRING });
const Game = sequelize.define('Game', { name: DataTypes.STRING });
现在,问题是:如何关联它们?
¥Now, the question is: how to associate them?
首先,我们注意到:
¥First, we note that:
-
一场比赛有许多与之相关的球队(正在玩该比赛的球队);
¥One game has many teams associated to it (the ones that are playing that game);
-
一支球队可能参加了很多场比赛。
¥One team may have participated in many games.
上述观察表明,我们需要 Game 和 Team 之间存在多对多关系。让我们使用本指南前面解释的超级多对多关系:
¥The above observations show that we need a Many-to-Many relationship between Game and Team. Let's use the Super Many-to-Many relationship as explained earlier in this guide:
// Super Many-to-Many relationship between Game and Team
const GameTeam = sequelize.define('GameTeam', {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true,
allowNull: false,
},
});
Team.belongsToMany(Game, { through: GameTeam });
Game.belongsToMany(Team, { through: GameTeam });
GameTeam.belongsTo(Game);
GameTeam.belongsTo(Team);
Game.hasMany(GameTeam);
Team.hasMany(GameTeam);
关于球员的部分比较棘手。我们注意到,组成团队的玩家集不仅取决于团队(显然),还取决于正在考虑的游戏。因此,我们不希望玩家和团队之间存在多对多关系。我们也不希望玩家和游戏之间存在多对多关系。我们不需要将玩家与任何这些模型关联,而是需要玩家与 "团队游戏配对约束" 之类的东西之间的关联,因为是一对(团队加游戏)定义了哪些玩家属于那里。所以我们要寻找的正是连接模型 GameTeam 本身!而且,我们注意到,由于给定的游戏团队对指定了许多玩家,而另一方面,同一玩家可以参与许多游戏团队对,因此我们需要玩家和 GameTeam 之间的多对多关系!
¥The part about players is trickier. We note that the set of players that form a team depends not only on the team (obviously), but also on which game is being considered. Therefore, we don't want a Many-to-Many relationship between Player and Team. We also don't want a Many-to-Many relationship between Player and Game. Instead of associating a Player to any of those models, what we need is an association between a Player and something like a "team-game pair constraint", since it is the pair (team plus game) that defines which players belong there. So what we are looking for turns out to be precisely the junction model, GameTeam, itself! And, we note that, since a given game-team pair specifies many players, and on the other hand that the same player can participate of many game-team pairs, we need a Many-to-Many relationship between Player and GameTeam!
为了提供最大的灵活性,让我们再次使用超级多对多关系构造:
¥To provide the greatest flexibility, let's use the Super Many-to-Many relationship construction here again:
// Super Many-to-Many relationship between Player and GameTeam
const PlayerGameTeam = sequelize.define('PlayerGameTeam', {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true,
allowNull: false,
},
});
Player.belongsToMany(GameTeam, { through: PlayerGameTeam });
GameTeam.belongsToMany(Player, { through: PlayerGameTeam });
PlayerGameTeam.belongsTo(Player);
PlayerGameTeam.belongsTo(GameTeam);
Player.hasMany(PlayerGameTeam);
GameTeam.hasMany(PlayerGameTeam);
上述关联正是达到了我们想要的目的。这是一个完整的可运行示例:
¥The above associations achieve precisely what we want. Here is a full runnable example of this:
const { Sequelize, Op, Model, DataTypes } = require('sequelize');
const sequelize = new Sequelize('sqlite::memory:', {
define: { timestamps: false }, // Just for less clutter in this example
});
const Player = sequelize.define('Player', { username: DataTypes.STRING });
const Team = sequelize.define('Team', { name: DataTypes.STRING });
const Game = sequelize.define('Game', { name: DataTypes.STRING });
// We apply a Super Many-to-Many relationship between Game and Team
const GameTeam = sequelize.define('GameTeam', {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true,
allowNull: false,
},
});
Team.belongsToMany(Game, { through: GameTeam });
Game.belongsToMany(Team, { through: GameTeam });
GameTeam.belongsTo(Game);
GameTeam.belongsTo(Team);
Game.hasMany(GameTeam);
Team.hasMany(GameTeam);
// We apply a Super Many-to-Many relationship between Player and GameTeam
const PlayerGameTeam = sequelize.define('PlayerGameTeam', {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true,
allowNull: false,
},
});
Player.belongsToMany(GameTeam, { through: PlayerGameTeam });
GameTeam.belongsToMany(Player, { through: PlayerGameTeam });
PlayerGameTeam.belongsTo(Player);
PlayerGameTeam.belongsTo(GameTeam);
Player.hasMany(PlayerGameTeam);
GameTeam.hasMany(PlayerGameTeam);
(async () => {
await sequelize.sync();
await Player.bulkCreate([
{ username: 's0me0ne' },
{ username: 'empty' },
{ username: 'greenhead' },
{ username: 'not_spock' },
{ username: 'bowl_of_petunias' },
]);
await Game.bulkCreate([
{ name: 'The Big Clash' },
{ name: 'Winter Showdown' },
{ name: 'Summer Beatdown' },
]);
await Team.bulkCreate([
{ name: 'The Martians' },
{ name: 'The Earthlings' },
{ name: 'The Plutonians' },
]);
// Let's start defining which teams were in which games. This can be done
// in several ways, such as calling `.setTeams` on each game. However, for
// brevity, we will use direct `create` calls instead, referring directly
// to the IDs we want. We know that IDs are given in order starting from 1.
await GameTeam.bulkCreate([
{ GameId: 1, TeamId: 1 }, // this GameTeam will get id 1
{ GameId: 1, TeamId: 2 }, // this GameTeam will get id 2
{ GameId: 2, TeamId: 1 }, // this GameTeam will get id 3
{ GameId: 2, TeamId: 3 }, // this GameTeam will get id 4
{ GameId: 3, TeamId: 2 }, // this GameTeam will get id 5
{ GameId: 3, TeamId: 3 }, // this GameTeam will get id 6
]);
// Now let's specify players.
// For brevity, let's do it only for the second game (Winter Showdown).
// Let's say that that s0me0ne and greenhead played for The Martians, while
// not_spock and bowl_of_petunias played for The Plutonians:
await PlayerGameTeam.bulkCreate([
// In 'Winter Showdown' (i.e. GameTeamIds 3 and 4):
{ PlayerId: 1, GameTeamId: 3 }, // s0me0ne played for The Martians
{ PlayerId: 3, GameTeamId: 3 }, // greenhead played for The Martians
{ PlayerId: 4, GameTeamId: 4 }, // not_spock played for The Plutonians
{ PlayerId: 5, GameTeamId: 4 }, // bowl_of_petunias played for The Plutonians
]);
// Now we can make queries!
const game = await Game.findOne({
where: {
name: 'Winter Showdown',
},
include: {
model: GameTeam,
include: [
{
model: Player,
through: { attributes: [] }, // Hide unwanted `PlayerGameTeam` nested object from results
},
Team,
],
},
});
console.log(`Found game: "${game.name}"`);
for (let i = 0; i < game.GameTeams.length; i++) {
const team = game.GameTeams[i].Team;
const players = game.GameTeams[i].Players;
console.log(`- Team "${team.name}" played game "${game.name}" with the following players:`);
console.log(players.map(p => `--- ${p.username}`).join('\n'));
}
})();
输出:
¥Output:
Found game: "Winter Showdown"
- Team "The Martians" played game "Winter Showdown" with the following players:
--- s0me0ne
--- greenhead
- Team "The Plutonians" played game "Winter Showdown" with the following players:
--- not_spock
--- bowl_of_petunias
这就是我们如何利用超级多对多关系技术在 Sequelize 中实现三个模型之间的多对多对多关系!
¥So this is how we can achieve a many-to-many-to-many relationship between three models in Sequelize, by taking advantage of the Super Many-to-Many relationship technique!
这个想法可以递归地应用于更复杂的多对多对...对多关系(尽管在某些时候查询可能会变得很慢)。
¥This idea can be applied recursively for even more complex, many-to-many-to-...-to-many relationships (although at some point queries might become slow).