我有两个表:书籍和文章,它们之间有多对多的关系。 加入表是BookArticles。
模型/ books.js
module.exports = function(sequelize, DataTypes) {
return Food = sequelize.define("Book", {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
allowNull: false,
autoIncrement: true,
unique: true
}
});
}
模型/ articles.js
module.exports = function(sequelize, DataTypes) {
return Food = sequelize.define("Article", {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
allowNull: false,
autoIncrement: true,
unique: true
}
});
}
模型/ bookArticles.js
module.exports = function(sequelize, DataTypes) {
return Food = sequelize.define("BookArticles", {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
allowNull: false,
autoIncrement: true,
unique: true
},
bookId: {
type: DataTypes.INTEGER,
references: 'Book',
referencesKey: 'id',
allowNull: false
},
ArticleId: {
type: DataTypes.INTEGER,
references: 'Article',
referencesKey: 'id',
allowNull: false
},
});
}
和models / index.js
m.BookArticles.belongsTo(m.Book);
m.Book.hasMany(m.Article, {through: m.BookArticles});
m.BookArticles.belongsTo(m.Article);
m.Article.hasMany(m.Books, {through: m.BookArticles});
但我无法获得书籍文章
我怎么能得到它?
答案 0 :(得分:173)
Update 17 Feb 15
:
1.新的v2使用2x .belongsToMany()
代表N:M。
在理解所有这些关联方面存在许多问题。
一般来说,我认为我们对创建的表格以及关联获得的方法感到困惑。
下面的文字是我写的标准化我希望我的团队如何处理所有这些内容。至于命名约定,如果你让Sequelize默认所有内容,你可以忽略它。
但是,出于多种原因,建议明确命名您的约定。
O:O,设置Parent.hasOne(Child)
和Child.belongsTo(Parent)
。
O:M,设置Parent.hasMany(Child)
和Child.belongsTo(Parent)
。
N:M *,设置Parent.belongsToMany(Child, {through: 'Parent_Child', foreignKey: 'Parent_rowId'})
和Child.belongsToMany(Parent, {through: 'Parent_Child', foreignKey: 'Child_rowId'})
。
为了理解我们为什么要做上述关联,我们首先要知道我们为每个模型获得的方法是什么。
在设置Parent.hasOne(Child)
时,parent
DAO实例可用的方法:
parent.getChild,
parent.setChild,
parent.addChild,
parent.createChild,
parent.removeChild,
parent.hasChild
在设置Parent.hasMany(Child)
时,parent
DAO实例可用的方法:
parent.getChildren,
parent.setChildren,
parent.addChild,
parent.createChild,
parent.removeChild,
parent.hasChild,
parent.hasChildren
在设置Child.belongsTo(Parent)
时,child
DAO实例可用的方法:
child.getParent,
child.setParent,
child.createParent
//belongsToMany
child.getParents,
child.setParents,
child.createParents
Parent.hasOne(Child, {foreignKey: 'Parent_childID'});
Child.belongsTo(Parent, {foreignKey: 'Parent_childID'});
请注意,我们明确地将foreignKeys定义为Parent_childID。这是因为我们希望这个PascalCase_camelCase用于TableName_keyName约定。
对于N:M关系,请执行以下操作:
Parent.belongsToMany( Child, {
as: [Relationship],
through: [Parent_Child] //this can be string or a model,
foreignKey: 'Parent_rowId'
});
Child.belongsToMany(Parent, {
as: [Relationship2],
through: [Parent_Child],
foreignKey: 'Child_rowId'
});
*New in v2
:现在必须使用“通过”。
作为标准,使用“通过”参数,我们明确定义所有可交叉名称的一致性和较少的陷阱。
上面将使用RelationshipId和Relationship2ID创建Parent_Child。
Sequelize可以自动创建foreignKieys,但我通常会定义自己的。
TableNames:PascalCase
键:camelCase
foreignkeys:TableNameInPascalCase_foreignKeyInCamelCase
示例:User_pictureId 含义:pictureId的这个键来自User表。
答案 1 :(得分:14)
删除BookArticles模型并将关系更新为:
m.Book.hasMany(m.Article, {through: 'book_articles'});
m.Article.hasMany(m.Books, {through: 'book_articles'});
答案 2 :(得分:6)
这就是我如何解决类似问题我有两个模型的用户模型
var user = sequelize.define('user', {
name: {
Sequelize.STRING(255)
},
email: {
type: Sequelize.STRING(255),
unique: true,
validate: {
isEmail: true
}
}
});
和角色模型
var Role = sequelize.define('role', {
name: {
Sequelize.ENUM('ER', 'ALL', 'DL')
},
description: {
type: Sequelize.TEXT
}
});
然后我创建了联合模型UserRole
var UserRole = sequelize.define('user_role', {
id: {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true
},
name: {
type: Sequelize.ENUM('Admin', 'Staff', 'Customer', 'Owner')
}
});
注意:您必须明确定义UserRole的ID,否则sequelize将使用这种情况下的两个外键user_id
和role_id
作为主键。
然后我按如下方式创建了属于多种关系
User.belongsToMany(Role, { as: 'Roles', through: { model: UserRole, unique: false }, foreignKey: 'user_id' });
Role.belongsToMany(User, { as: 'Users', through: { model: UserRole, unique: false }, foreignKey: 'role_id' });
答案 3 :(得分:3)
M:M
BookArticles
关系:
m.Book.belongsToMany(m.Article, {through: m.BookArticles});
m.Article.belongsToMany(m.Books, {through: m.BookArticles});
答案 4 :(得分:1)
带有断言的可运行示例
在这里,我提供了一个单源文件可运行示例,它说明了 https://sequelize.org/master/manual/assocs.html#many-to-many-relationships (archive) 部分“Foo.hasMany(Bar)
”中提到的每个自动生成的方法。
该模型是一个网站,用户可以在其中创建帖子并喜欢其他用户的帖子。
npm install sequelize@6.5.1 sqlite3@5.0.2
main.js
const assert = require('assert');
const path = require('path');
const { Sequelize, DataTypes } = require('sequelize');
const sequelize = new Sequelize({
dialect: 'sqlite',
storage: 'db.sqlite3',
});
(async () => {
// Create the tables.
const User = sequelize.define('User', {
name: { type: DataTypes.STRING },
}, {});
const Post = sequelize.define('Post', {
body: { type: DataTypes.STRING },
}, {});
User.belongsToMany(Post, {through: 'UserLikesPost'});
Post.belongsToMany(User, {through: 'UserLikesPost'});
await sequelize.sync({force: true});
// Create some users and posts.
const user0 = await User.create({name: 'user0'})
const user1 = await User.create({name: 'user1'})
const user2 = await User.create({name: 'user2'})
const post0 = await Post.create({body: 'post0'});
const post1 = await Post.create({body: 'post1'});
const post2 = await Post.create({body: 'post2'});
// Autogenerated add* methods
// Make user0 like post0
await user0.addPost(post0)
// Also works.
//await user0.addPost(post0.id)
// Make user0 and user2 like post1
await post1.addUsers([user0, user2])
// Autogenerated get* methods
// Get posts liked by a user.
const user0Likes = await user0.getPosts({order: [['body', 'ASC']]})
assert(user0Likes[0].body === 'post0');
assert(user0Likes[1].body === 'post1');
assert(user0Likes.length === 2);
const user1Likes = await user1.getPosts({order: [['body', 'ASC']]})
assert(user1Likes.length === 0);
const user2Likes = await user2.getPosts({order: [['body', 'ASC']]})
assert(user2Likes[0].body === 'post1');
assert(user2Likes.length === 1);
// Get users that like a given post.
const post0Likers = await post0.getUsers({order: [['name', 'ASC']]})
assert(post0Likers[0].name === 'user0');
assert(post0Likers.length === 1);
const post1Likers = await post1.getUsers({order: [['name', 'ASC']]})
assert(post1Likers[0].name === 'user0');
assert(post1Likers[1].name === 'user2');
assert(post1Likers.length === 2);
const post2Likers = await post2.getUsers({order: [['name', 'ASC']]})
assert(post2Likers.length === 0);
// Same as getPosts but with the user ID instead of the model object.
{
const user0Likes = await Post.findAll({
include: [{
model: User,
where: {
id: user0.id
}
}],
})
assert(user0Likes[0].body === 'post0');
assert(user0Likes[1].body === 'post1');
assert(user0Likes.length === 2);
}
// Yet another way that can be more useful in nested includes.
{
const user0Likes = (await User.findOne({
where: {id: user0.id},
include: [{
model: Post,
}],
order: [[Post, 'body', 'ASC']],
})).Posts
assert(user0Likes[0].body === 'post0');
assert(user0Likes[1].body === 'post1');
assert(user0Likes.length === 2);
}
// Autogenerated has* methods
// Check if user likes post.
assert( await user0.hasPost(post0))
assert( await user0.hasPost(post0.id)) // same
assert( await user0.hasPost(post1))
assert(!await user0.hasPost(post2))
// Check if post is liked by user.
assert( await post0.hasUser(user0))
assert(!await post0.hasUser(user1))
assert(!await post0.hasUser(user2))
// AND of multiple has checks at once.
assert( await user0.hasPosts([post0, post1]))
// false because user0 does not like post2
assert(!await user0.hasPosts([post0, post1, post2]))
// Autogenerated count* methods
// user0 likes 2 posts.
assert(await user0.countPosts() === 2)
// post0 is liked by 1 user.
assert(await post0.countUsers() === 1)
// Autogenerated remove* method
// user0 doesn't like post0 anymore.
await user0.removePost(post0)
// user0 and user 2 don't like post1 anymore.
await post1.removeUsers([user0, user2])
// Check that no-one likes anything anymore.
assert(await user0.countPosts() === 0)
assert(await post0.countUsers() === 0)
// Autogenerated create* method
// Create a new post and automatically make user0 like it.
const post3 = await user0.createPost({'body': 'post3'})
assert(await user0.hasPost(post3))
assert(await post3.hasUser(user0))
// Autogenerated set* method
// Make user0 like exactly these posts. Unlike anything else.
await user0.setPosts([post1, post2])
assert(!await user0.hasPost(post0))
assert( await user0.hasPost(post1))
assert( await user0.hasPost(post2))
assert(!await user0.hasPost(post3))
await sequelize.close();
})();
生成的SQLite表为:
UserLikesPost is the name of the relation table.
Sequelize creates it automatically for us.
On SQLite that table looks like this:
CREATE TABLE `UserLikesPost` (
`createdAt` DATETIME NOT NULL,
`updatedAt` DATETIME NOT NULL,
`UserId` INTEGER NOT NULL REFERENCES `Users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
`PostId` INTEGER NOT NULL REFERENCES `Posts` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
PRIMARY KEY (`UserId`, `PostId`)
);
自关联(又名自引用)
这说明了如何从表到自身进行多对多,例如供用户关注另一个用户。
基本上:
as:
键添加到 .belongsToMany
addFollows
方法,只有 addFollow
,为什么?const assert = require('assert');
const path = require('path');
const { Sequelize, DataTypes } = require('sequelize');
const sequelize = new Sequelize({
dialect: 'sqlite',
storage: 'db.sqlite3',
});
(async () => {
// Create the tables.
const User = sequelize.define('User', {
name: { type: DataTypes.STRING },
}, {});
User.belongsToMany(User, {through: 'UserFollowUser', as: 'Follows'});
await sequelize.sync({force: true});
// Create some users.
const user0 = await User.create({name: 'user0'})
const user1 = await User.create({name: 'user1'})
const user2 = await User.create({name: 'user2'})
const user3 = await User.create({name: 'user3'})
// Make user0 follow user1 and user2
await user0.addFollows([user1, user2])
// Make user2 and user3 follow user0
await user2.addFollow(user0)
await user3.addFollow(user0)
// Check that the follows worked.
const user0Follows = await user0.getFollows({order: [['name', 'ASC']]})
assert(user0Follows[0].name === 'user1');
assert(user0Follows[1].name === 'user2');
assert(user0Follows.length === 2);
const user1Follows = await user1.getFollows({order: [['name', 'ASC']]})
assert(user1Follows.length === 0);
const user2Follows = await user2.getFollows({order: [['name', 'ASC']]})
assert(user2Follows[0].name === 'user0');
assert(user2Follows.length === 1);
const user3Follows = await user3.getFollows({order: [['name', 'ASC']]})
assert(user3Follows[0].name === 'user0');
assert(user3Follows.length === 1);
// Same but with ID instead of object.
{
const user0Follows = (await User.findOne({
where: {id: user0.id},
include: [{model: User, as: 'Follows'}],
})).Follows
assert(user0Follows[0].name === 'user1');
assert(user0Follows[1].name === 'user2');
assert(user0Follows.length === 2);
}
// has methods
assert(!await user0.hasFollow(user0))
assert(!await user0.hasFollow(user0.id))
assert( await user0.hasFollow(user1))
assert( await user0.hasFollow(user2))
assert(!await user0.hasFollow(user3))
// Count method
assert(await user0.countFollows() === 2)
await sequelize.close();
})();
生成的SQLite表为:
CREATE TABLE IF NOT EXISTS `UserFollowUser` (
`createdAt` DATETIME NOT NULL,
`updatedAt` DATETIME NOT NULL,=
`UserId` INTEGER NOT NULL REFERENCES `Users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
`FollowId` INTEGER NOT NULL REFERENCES `Users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
PRIMARY KEY (`UserId`, `FollowId`)
);
也在:How to have a self-referencing many-to-many association in Sequelize?
具有自定义列的多对多
回到用户喜欢帖子的例子,我们也可以使用我们创建的自定义表来实现相同的结果。
这允许我们向关系添加额外的参数,例如在这里,我们添加一个分数,说明用户对帖子的喜欢程度。
const assert = require('assert');
const path = require('path');
const { Sequelize, DataTypes } = require('sequelize');
const sequelize = new Sequelize({
dialect: 'sqlite',
storage: 'db.sqlite3',
});
(async () => {
// Create the tables.
const User = sequelize.define('User', {
name: { type: DataTypes.STRING },
}, {});
const Post = sequelize.define('Post', {
body: { type: DataTypes.STRING },
}, {});
const UserLikesPost = sequelize.define('UserLikesPost', {
UserId: {
type: DataTypes.INTEGER,
references: {
model: User,
key: 'id'
}
},
PostId: {
type: DataTypes.INTEGER,
references: {
model: Post,
key: 'id'
}
},
score: {
type: DataTypes.INTEGER,
},
});
User.belongsToMany(Post, {through: UserLikesPost});
Post.belongsToMany(User, {through: UserLikesPost});
await sequelize.sync({force: true});
// Create some users and likes.
const user0 = await User.create({name: 'user0'})
const user1 = await User.create({name: 'user1'})
const user2 = await User.create({name: 'user2'})
const post0 = await Post.create({body: 'post0'});
const post1 = await Post.create({body: 'post1'});
const post2 = await Post.create({body: 'post2'});
// Make some useres like some posts.
await user0.addPost(post0, {through: {score: 1}})
await user1.addPost(post1, {through: {score: 2}})
await user1.addPost(post2, {through: {score: 3}})
// Find what user0 likes.
const user0Likes = await user0.getPosts({order: [['body', 'ASC']]})
assert(user0Likes[0].body === 'post0');
assert(user0Likes[0].UserLikesPost.score === 1);
assert(user0Likes.length === 1);
// Find what user1 likes.
const user1Likes = await user1.getPosts({order: [['body', 'ASC']]})
assert(user1Likes[0].body === 'post1');
assert(user1Likes[0].UserLikesPost.score === 2);
assert(user1Likes[1].body === 'post2');
assert(user1Likes[1].UserLikesPost.score === 3);
assert(user1Likes.length === 2);
// Where on the custom through table column.
// https://stackoverflow.com/questions/38857156/how-to-query-many-to-many-relationship-sequelize
{
const user1LikesWithScore3 = await Post.findAll({
include: [{
model: User,
where: {
id: user1.id
},
through: {where: {score: 3}},
}],
})
assert(user1LikesWithScore3[0].body === 'post2');
assert(user1LikesWithScore3[0].UserLikesPost.score === 3);
assert(user1LikesWithScore3.length === 1);
}
// TODO: this doesn't work. Possible at all in a single addUsers call?
// Make user0 and user2 like post1
// This method automatically generated.
//await post1.addUsers(
// [user0, user2],
// {through: [
// {score: 2},
// {score: 3},
// ]}
//)
await sequelize.close();
})();
这已被问到:
并且还记录在:https://sequelize.org/master/manual/advanced-many-to-many.html
如何通过 score
查询:How to query many to many relationship sequelize?
如何使用多个关联进行复杂的 JOIN 查询?
以用例为例:
<块引用>用户可以关注用户,用户可以创建帖子,查找给定用户关注的用户的所有帖子
为了解决这个问题,我们基本上只是嵌套 include:
语句,如下所示:
#!/usr/bin/env node
// Find all posts by users that a given user follows.
// https://stackoverflow.com/questions/42632943/sequelize-multiple-where-clause
const assert = require('assert');
const path = require('path');
const { Sequelize, DataTypes } = require('sequelize');
const sequelize = new Sequelize({
dialect: 'sqlite',
storage: 'db.sqlite3',
});
(async () => {
// Create the tables.
const User = sequelize.define('User', {
name: { type: DataTypes.STRING },
}, {});
const Post = sequelize.define('Post', {
body: { type: DataTypes.STRING },
}, {});
User.belongsToMany(User, {through: 'UserFollowUser', as: 'Follows'});
User.hasMany(Post);
Post.belongsTo(User);
await sequelize.sync({force: true});
// Create data.
const users = await User.bulkCreate([
{name: 'user0'},
{name: 'user1'},
{name: 'user2'},
{name: 'user3'},
])
const posts = await Post.bulkCreate([
{body: 'body00', UserId: users[0].id},
{body: 'body01', UserId: users[0].id},
{body: 'body10', UserId: users[1].id},
{body: 'body11', UserId: users[1].id},
{body: 'body20', UserId: users[2].id},
{body: 'body21', UserId: users[2].id},
{body: 'body30', UserId: users[3].id},
{body: 'body31', UserId: users[3].id},
])
await users[0].addFollows([users[1], users[2]])
// Get all posts by authors that user0 follows.
// The posts are placed inside their respetive authors under .Posts
// so we loop to gather all of them.
{
const user0Follows = (await User.findByPk(users[0].id, {
include: [
{
model: User,
as: 'Follows',
include: [
{
model: Post,
}
],
},
],
})).Follows
const postsFound = []
for (const followedUser of user0Follows) {
postsFound.push(...followedUser.Posts)
}
postsFound.sort((x, y) => { return x.body < y.body ? -1 : x.body > y.body ? 1 : 0 })
assert(postsFound[0].body === 'body10')
assert(postsFound[1].body === 'body11')
assert(postsFound[2].body === 'body20')
assert(postsFound[3].body === 'body21')
assert(postsFound.length === 4)
}
// With ordering, offset and limit.
// The posts are placed inside their respetive authors under .Posts
// The only difference is that posts that we didn't select got removed.
{
const user0Follows = (await User.findByPk(users[0].id, {
offset: 1,
limit: 2,
// TODO why is this needed? It does try to make a subquery otherwise, and then it doesn't work.
// https://selleo.com/til/posts/ddesmudzmi-offset-pagination-with-subquery-in-sequelize-
subQuery: false,
include: [
{
model: User,
as: 'Follows',
include: [
{
model: Post,
}
],
},
],
})).Follows
assert(user0Follows[0].name === 'user1')
assert(user0Follows[1].name === 'user2')
assert(user0Follows.length === 2)
const postsFound = []
for (const followedUser of user0Follows) {
postsFound.push(...followedUser.Posts)
}
postsFound.sort((x, y) => { return x.body < y.body ? -1 : x.body > y.body ? 1 : 0 })
// Note that what happens is that some of the
assert(postsFound[0].body === 'body11')
assert(postsFound[1].body === 'body20')
assert(postsFound.length === 2)
// Same as above, but now with DESC ordering.
{
const user0Follows = (await User.findByPk(users[0].id, {
order: [[
{model: User, as: 'Follows'},
Post,
'body',
'DESC'
]],
offset: 1,
limit: 2,
subQuery: false,
include: [
{
model: User,
as: 'Follows',
include: [
{
model: Post,
}
],
},
],
})).Follows
// Note how user ordering is also reversed from an ASC.
// it likely takes the use that has the first post.
assert(user0Follows[0].name === 'user2')
assert(user0Follows[1].name === 'user1')
assert(user0Follows.length === 2)
const postsFound = []
for (const followedUser of user0Follows) {
postsFound.push(...followedUser.Posts)
}
// In this very specific data case, this would not be needed.
// because user2 has the second post body and user1 has the first
// alphabetically.
postsFound.sort((x, y) => { return x.body < y.body ? 1 : x.body > y.body ? -1 : 0 })
// Note that what happens is that some of the
assert(postsFound[0].body === 'body20')
assert(postsFound[1].body === 'body11')
assert(postsFound.length === 2)
}
// Here user2 would have no post hits due to the limit,
// so it is entirely pruned from the user list as desired.
// Otherwise we would fetch a lot of unwanted user data
// in a large database.
const user0FollowsLimit2 = (await User.findByPk(users[0].id, {
limit: 2,
subQuery: false,
include: [
{
model: User,
as: 'Follows',
include: [ { model: Post } ],
},
],
})).Follows
assert(user0FollowsLimit2[0].name === 'user1')
assert(user0FollowsLimit2.length === 1)
// Get just the count of the posts authored by users followed by user0.
// attributes: [] excludes all other data from the SELECT of the querries
// to optimize things a bit.
// https://stackoverflow.com/questions/37817808/counting-associated-entries-with-sequelize
{
const user0Follows = await User.findByPk(users[0].id, {
attributes: [
[Sequelize.fn('COUNT', Sequelize.col('Follows.Posts.id')), 'count']
],
include: [
{
model: User,
as: 'Follows',
attributes: [],
through: {
attributes: []
},
include: [
{
model: Post,
attributes: [],
}
],
},
],
})
assert.strictEqual(user0Follows.dataValues.count, 4);
}
// Case in which our post-sorting is needed.
// TODO: possible to get sequelize to do this for us by returning
// a flat array directly?
// Managed with super many to many as shown below.
// It's not big deal since the LIMITed result should be small,
// but feels wasteful.
// https://stackoverflow.com/questions/41502699/return-flat-object-from-sequelize-with-association
// https://github.com/sequelize/sequelize/issues/4419
{
await Post.truncate({restartIdentity: true})
const posts = await Post.bulkCreate([
{body: 'body0', UserId: users[0].id},
{body: 'body1', UserId: users[1].id},
{body: 'body2', UserId: users[2].id},
{body: 'body3', UserId: users[3].id},
{body: 'body4', UserId: users[0].id},
{body: 'body5', UserId: users[1].id},
{body: 'body6', UserId: users[2].id},
{body: 'body7', UserId: users[3].id},
])
const user0Follows = (await User.findByPk(users[0].id, {
order: [[
{model: User, as: 'Follows'},
Post,
'body',
'DESC'
]],
subQuery: false,
include: [
{
model: User,
as: 'Follows',
include: [
{
model: Post,
}
],
},
],
})).Follows
assert(user0Follows[0].name === 'user2')
assert(user0Follows[1].name === 'user1')
assert(user0Follows.length === 2)
const postsFound = []
for (const followedUser of user0Follows) {
postsFound.push(...followedUser.Posts)
}
// We need this here, otherwise we would get all user2 posts first:
// body6, body2, body5, body1
postsFound.sort((x, y) => { return x.body < y.body ? 1 : x.body > y.body ? -1 : 0 })
assert(postsFound[0].body === 'body6')
assert(postsFound[1].body === 'body5')
assert(postsFound[2].body === 'body2')
assert(postsFound[3].body === 'body1')
assert(postsFound.length === 4)
}
}
await sequelize.close();
})();
超多对多做“关注用户发帖”查询,无需后期处理
Super many to many 表示在每个模型和直通表之间显式设置belongsTo
/hasMany
,除了每个模型的belongsToMany
。
这是我发现的唯一一种无需后期处理就能很好地进行“被关注用户发布的帖子”查询的方法。
const assert = require('assert');
const path = require('path');
const { Sequelize, DataTypes, Op } = require('sequelize');
const sequelize = new Sequelize({
dialect: 'sqlite',
storage: 'tmp.' + path.basename(__filename) + '.sqlite',
define: {
timestamps: false
},
});
(async () => {
// Create the tables.
const User = sequelize.define('User', {
name: { type: DataTypes.STRING },
});
const Post = sequelize.define('Post', {
body: { type: DataTypes.STRING },
});
const UserFollowUser = sequelize.define('UserFollowUser', {
UserId: {
type: DataTypes.INTEGER,
references: {
model: User,
key: 'id'
}
},
FollowId: {
type: DataTypes.INTEGER,
references: {
model: User,
key: 'id'
}
},
}
);
// Super many to many.
User.belongsToMany(User, {through: UserFollowUser, as: 'Follows'});
UserFollowUser.belongsTo(User)
User.hasMany(UserFollowUser)
User.hasMany(Post);
Post.belongsTo(User);
await sequelize.sync({force: true});
// Create data.
const users = await User.bulkCreate([
{name: 'user0'},
{name: 'user1'},
{name: 'user2'},
{name: 'user3'},
])
const posts = await Post.bulkCreate([
{body: 'body0', UserId: users[0].id},
{body: 'body1', UserId: users[1].id},
{body: 'body2', UserId: users[2].id},
{body: 'body3', UserId: users[3].id},
{body: 'body4', UserId: users[0].id},
{body: 'body5', UserId: users[1].id},
{body: 'body6', UserId: users[2].id},
{body: 'body7', UserId: users[3].id},
])
await users[0].addFollows([users[1], users[2]])
// Get all the posts by authors that user0 follows.
// without any post process sorting. We only managed to to this
// with a super many to many, because that allows us to specify
// a reversed order in the through table with `on`, since we need to
// match with `FollowId` and not `UserId`.
{
const postsFound = await Post.findAll({
order: [[
'body',
'DESC'
]],
include: [
{
model: User,
attributes: [],
required: true,
include: [
{
model: UserFollowUser,
on: {
FollowId: {[Op.col]: 'User.id' },
},
attributes: [],
where: {UserId: users[0].id},
}
],
},
],
})
assert.strictEqual(postsFound[0].body, 'body6')
assert.strictEqual(postsFound[1].body, 'body5')
assert.strictEqual(postsFound[2].body, 'body2')
assert.strictEqual(postsFound[3].body, 'body1')
assert.strictEqual(postsFound.length, 4)
}
await sequelize.close();
})();
相关:Return flat object from sequelize with association
别名:两个模型之间的多个多对多关联
假设现在用户可以喜欢和关注帖子。
为了建模,我们将在用户和帖子之间建立两个多对多关联。
然而,问题是到目前为止我们使用了表名 User
和 Post
作为关联的标识符。
因此,为了消除歧义,我们不得不使用 as:
参数来创建表别名。
const assert = require('assert');
const path = require('path');
const { Sequelize, DataTypes } = require('sequelize');
const sequelize = new Sequelize({
dialect: 'sqlite',
storage: 'tmp.' + path.basename(__filename) + '.sqlite',
});
(async () => {
// Create the tables.
const User = sequelize.define('User', {
name: { type: DataTypes.STRING },
}, {});
const Post = sequelize.define('Post', {
body: { type: DataTypes.STRING },
}, {});
User.belongsToMany(Post, {through: 'UserLikesPost', as: 'likedPosts'});
Post.belongsToMany(User, {through: 'UserLikesPost', as: 'likers'});
User.belongsToMany(Post, {through: 'UserFollowsPost', as: 'followedPosts'});
Post.belongsToMany(User, {through: 'UserFollowsPost', as: 'followers'});
await sequelize.sync({force: true});
// Create some users and likes.
const user0 = await User.create({name: 'user0'})
const user1 = await User.create({name: 'user1'})
const user2 = await User.create({name: 'user2'})
const post0 = await Post.create({body: 'post0'});
const post1 = await Post.create({body: 'post1'});
const post2 = await Post.create({body: 'post2'});
// Autogenerated add* methods
// Setup likes and follows.
await user0.addLikedPost(post0)
await post1.addLikers([user0, user2])
await user1.addFollowedPosts([post0, post1])
await post1.addFollower(user2)
// Autogenerated get* methods
// Get likes by a user.
const user0Likes = await user0.getLikedPosts({order: [['body', 'ASC']]})
assert(user0Likes[0].body === 'post0');
assert(user0Likes[1].body === 'post1');
assert(user0Likes.length === 2);
const user1Likes = await user1.getLikedPosts({order: [['body', 'ASC']]})
assert(user1Likes.length === 0);
const user2Likes = await user2.getLikedPosts({order: [['body', 'ASC']]})
assert(user2Likes[0].body === 'post1');
assert(user2Likes.length === 1);
// Get users that liked a given post.
const post0Likers = await post0.getLikers({order: [['name', 'ASC']]})
assert(post0Likers[0].name === 'user0');
assert(post0Likers.length === 1);
const post1Likers = await post1.getLikers({order: [['name', 'ASC']]})
assert(post1Likers[0].name === 'user0');
assert(post1Likers[1].name === 'user2');
assert(post1Likers.length === 2);
const post2Likers = await post2.getLikers({order: [['name', 'ASC']]})
assert(post2Likers.length === 0);
// Get follows by a user.
const user0Follows = await user0.getFollowedPosts({order: [['body', 'ASC']]})
assert(user0Follows.length === 0);
const user1Follows = await user1.getFollowedPosts({order: [['body', 'ASC']]})
assert(user1Follows[0].body === 'post0');
assert(user1Follows[1].body === 'post1');
assert(user1Follows.length === 2);
const user2Follows = await user2.getFollowedPosts({order: [['body', 'ASC']]})
assert(user2Follows[0].body === 'post1');
assert(user2Follows.length === 1);
// Get users that followed a given post.
const post0Followers = await post0.getFollowers({order: [['name', 'ASC']]})
assert(post0Followers[0].name === 'user1');
assert(post0Followers.length === 1);
const post1Followers = await post1.getFollowers({order: [['name', 'ASC']]})
assert(post1Followers[0].name === 'user1');
assert(post1Followers[1].name === 'user2');
assert(post1Followers.length === 2);
const post2Followers = await post2.getFollowers({order: [['name', 'ASC']]})
assert(post2Followers.length === 0);
// Same as getLikedPosts but with the user ID instead of the model object.
{
const user0Likes = await Post.findAll({
include: [{
model: User,
as: 'likers',
where: {id: user0.id},
}],
order: [['body', 'ASC']],
})
assert(user0Likes[0].body === 'post0');
assert(user0Likes[1].body === 'post1');
assert(user0Likes.length === 2);
}
// Yet another way that can be more useful in nested includes.
{
const user0Likes = (await User.findOne({
where: {id: user0.id},
include: [{
model: Post,
as: 'likedPosts',
}],
order: [[{model: Post, as: 'likedPosts'}, 'body', 'ASC']],
})).likedPosts
assert(user0Likes[0].body === 'post0');
assert(user0Likes[1].body === 'post1');
assert(user0Likes.length === 2);
}
await sequelize.close();
})();
奖励:别名多对一需要 foreignKey
不带 foreignKey
的别名多对多工作。但多对一不会:它会创建两个单独的 ID,例如UserId
和 authorId
。
这是我让它工作的唯一方法(假设每篇文章只有一个作者和一个审阅者):
User.hasMany(Post, {as: 'authoredPosts', foreignKey: 'authorId'});
Post.belongsTo(User, {as: 'author', foreignKey: 'authorId'});
User.hasMany(Post, {as: 'reviewedPosts', foreignKey: 'reviewerId'});
Post.belongsTo(User, {as: 'reviewer', foreignKey: 'reviewerId'});
如何列出所有自动生成的方法?
How to display all methods of an object? 适用于此。
在 Ubuntu 21.04、node.js v14.17.0 上测试。