我有以下表格:
文章 - 用户 - 标签 - 粉丝 - Suscribes
文章属于User(文章表中的fk:userId)
文章可以有很多标签。这是生成的tagarticle表:
以下是关注者表:
和Suscribes表:
用户可以关注许多用户并使用国家/地区(payId),标签或文章(用于通知)。
如何查询关注用户的所有文章以及特定用户的已删除国家/地区标签?
答案 0 :(得分:1)
我假设您询问Sequelize进行查询的方式。 我不确定我是否正确理解你的问题。您正在寻找两个问题:
让我先从模型之间的关联开始。
// in User model definition
User.belongsToMany(User, { as: 'Followers', through: 'Followers', foreignKey: 'userId', otherKey: 'followId' });
User.hasMany(Subscribe, { foreignKey: 'userId' });
User.hasMany(Article, { foreignKey: 'userId' });
通过使用上述关联,我们现在可以查询所有关注用户的文章
models.User.findByPrimary(1, {
include: [
{
model: models.User,
as: 'Followers',
include: [ models.Article ]
}
]
}).then(function(user){
// here you have user with his followers and their articles
});
以上查询将生成类似于
的结果{
id: 1,
Followers: [
{
id: 4,
Articles: [
{
id: 1,
title: 'article title' // some example field of Article model
}
]
}
]
}
如果要查询特定用户订阅的国家/标签/文章,则必须在Subscribe
模型
// in Subscribe model definition
Subscribe.belongsTo(Tag, { foreignKey: 'tagId' });
Subscribe.belongsTo(Article, { foreignKey: 'articleId' });
Subscribe.belongsTo(Country, { foreignKey: 'payId' });
现在我们拥有执行您要求的第二个查询所需的所有关联
models.User.findByPrimary(1, {
include: [
{
model: models.Subscribe,
include: [ models.Tag, models.Country, models.Article ]
}
]
}).then(function(user){
// here you get user with his subscriptions
});
在此示例中,您将通过user.Subscribes
访问所有订阅的用户,该订阅将具有嵌套属性Tag
,Country
和Article
。如果用户订阅Tag
,则Country
和Article
在这种情况下将为NULL
。
答案 1 :(得分:1)
通过断言获取关注用户的文章的最小可运行示例
https://stackoverflow.com/a/42634024/895245 是正确的,这是它的一个可运行版本,还涵盖了一些其他相关功能,如限制和排序。感兴趣的更多示例:How to implement many to many association in sequelize 测试:
npm install sequelize@6.5.1 sqlite3@5.0.2
来源:
#!/usr/bin/env node
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: 'body11', 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)
// Case in which our post-sorting is needed.
// TODO: possible to get sequelize to do this for us by returning
// a flat array directly?
// 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();
})();