Sequelize 通过手动定义的关联查找 table
Sequelize find by association through manually-defined join table
我知道描述了一个更简单的案例 here:
不幸的是,我的情况比那复杂一点。我有一个用户模型,其中 belongsToMany
个部门(反过来 belongsToMany
个用户),但是通过 userDepartment
,一个手动定义的连接 table 来实现。 我的目标是获取属于给定部门的所有用户。首先让我们看一下models/user.js
:
var user = sequelize.define("user", {
id: {
type: DataTypes.INTEGER,
field: 'emplId',
primaryKey: true,
autoIncrement: false
},
firstname: {
type: DataTypes.STRING,
field: 'firstname_preferred',
defaultValue: '',
allowNull: false
}
...
...
...
associate: function(models) {
user.belongsToMany(models.department, {
foreignKey: "emplId",
through: 'userDepartment'
});
})
}
...
return user;
现在,看看 models/department.js
:
var department = sequelize.define("department", {
id: {
type: DataTypes.INTEGER,
field: 'departmentId',
primaryKey: true,
autoIncrement: true
},
...
classMethods: {
associate: function(models) {
department.belongsToMany(models.user, {
foreignKey: "departmentId",
through: 'userDepartment',
onDelete: 'cascade'
});
}
...
return department;
最后在 models/userDepartment.js
:
var userDepartment = sequelize.define("userDepartment", {
title: {
type: DataTypes.STRING,
field: 'title',
allowNull: false,
defaultValue: ''
}
}, {
tableName: 'user_departments'
});
return userDepartment;
到目前为止一切顺利。然而,这个查询:
models.user.findAll({
where: {'departments.id': req.params.id},
include: [{model: models.department, as: models.department.tableName}]
})
失败并出现以下错误:
SequelizeDatabaseError: ER_BAD_FIELD_ERROR: Unknown column 'user.departments.id' in 'where clause'
尝试包含 userDepartment 模型会导致:
Error: userDepartment (user_departments) is not associated to user!
简而言之:我有两个具有 M:M 关系的 Sequelize 模型。它们通过手动定义的联接 table 关联(它为每个唯一关系添加职位,即用户 A 是部门 B 中的 "Manager")。尝试按部门查找用户失败,出现严重的 table 名称错误。
续集版本“^2.0.5”
花了几个小时,但我找到了我的解决方案:
models.department.find({
where: {id:req.params.id},
include: [models.user]
问题是 Sequelize 不允许您 "go out of scope" 因为它以 model_name
开始每个 where 子句。因此,例如,当部门 table 仅作为 departments.id
加入时,where 子句试图比较 user.departments.id
。由于我们正在查询部门的值(ID),因此查询单个部门和 return 他们的关联用户是最有效的。
我有一个类似的问题,但在我的情况下我无法切换表格。
我必须使用:sequelize.literal
函数。
在您的情况下,它将如下所示:
models.user.findAll({
where: sequelize.literal("departments.id = " + req.params.id),
include: [{model: models.department, as: models.department.tableName}]
})
我不喜欢它,但它有效。
对于仍在为此寻找答案的任何人,我在 Github 上找到了一个答案。
你只需这样做:
where: {
'$Table.column$' : value
}
如果您有 class 实例,您也可以使用自动生成的 instance.getOthers()
方法
这可能意味着一个额外的查询。但是如果实例已经在手边,这是最方便的语法。
假设“用户喜欢给定分数的 post”情况,我们可以得到用户喜欢的所有 post:
const user0 = await User.create({name: 'user0'})
const user0Likes = await user0.getPosts({order: [['body', 'ASC']]})
assert(user0Likes[0].body === 'post0');
assert(user0Likes[0].UserLikesPost.score === 1);
assert(user0Likes.length === 1);
完整的可运行示例:
main.js
const assert = require('assert')
const { DataTypes, Op, Sequelize } = require('sequelize')
const common = require('./common')
const sequelize = common.sequelize(__filename, process.argv[2], { 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 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'});
// Autogenerated add* methods
// 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.
// Find posts that user1 likes which have score greater than 2.
//
{
const rows = await Post.findAll({
include: [
{
model: User,
where: {id: user1.id},
through: {
where: {score: { [Op.gt]: 2 }},
},
},
],
})
assert.strictEqual(rows[0].body, 'post2');
// TODO how to get the score here as well?
//assert.strictEqual(rows[0].UserLikesPost.score, 3);
assert.strictEqual(rows.length, 1);
}
})().finally(() => { return sequelize.close() });
common.js
const path = require('path');
const { Sequelize } = require('sequelize');
function sequelize(filename, dialect, opts) {
if (dialect === undefined) {
dialect = 'l'
}
if (dialect === 'l') {
return new Sequelize(Object.assign({
dialect: 'sqlite',
storage: path.parse(filename).name + '.sqlite'
}, opts));
} else if (dialect === 'p') {
return new Sequelize('tmp', undefined, undefined, Object.assign({
dialect: 'postgres',
host: '/var/run/postgresql',
}, opts));
} else {
throw new Error('Unknown dialect')
}
}
exports.sequelize = sequelize
package.json
{
"name": "tmp",
"private": true,
"version": "1.0.0",
"dependencies": {
"pg": "8.5.1",
"pg-hstore": "2.3.3",
"sequelize": "6.5.1",
"sqlite3": "5.0.2"
}
}
在 PostgreSQL 13.4、Ubuntu 21.04.
上测试
我知道描述了一个更简单的案例 here:
不幸的是,我的情况比那复杂一点。我有一个用户模型,其中 belongsToMany
个部门(反过来 belongsToMany
个用户),但是通过 userDepartment
,一个手动定义的连接 table 来实现。 我的目标是获取属于给定部门的所有用户。首先让我们看一下models/user.js
:
var user = sequelize.define("user", {
id: {
type: DataTypes.INTEGER,
field: 'emplId',
primaryKey: true,
autoIncrement: false
},
firstname: {
type: DataTypes.STRING,
field: 'firstname_preferred',
defaultValue: '',
allowNull: false
}
...
...
...
associate: function(models) {
user.belongsToMany(models.department, {
foreignKey: "emplId",
through: 'userDepartment'
});
})
}
...
return user;
现在,看看 models/department.js
:
var department = sequelize.define("department", {
id: {
type: DataTypes.INTEGER,
field: 'departmentId',
primaryKey: true,
autoIncrement: true
},
...
classMethods: {
associate: function(models) {
department.belongsToMany(models.user, {
foreignKey: "departmentId",
through: 'userDepartment',
onDelete: 'cascade'
});
}
...
return department;
最后在 models/userDepartment.js
:
var userDepartment = sequelize.define("userDepartment", {
title: {
type: DataTypes.STRING,
field: 'title',
allowNull: false,
defaultValue: ''
}
}, {
tableName: 'user_departments'
});
return userDepartment;
到目前为止一切顺利。然而,这个查询:
models.user.findAll({
where: {'departments.id': req.params.id},
include: [{model: models.department, as: models.department.tableName}]
})
失败并出现以下错误:
SequelizeDatabaseError: ER_BAD_FIELD_ERROR: Unknown column 'user.departments.id' in 'where clause'
尝试包含 userDepartment 模型会导致:
Error: userDepartment (user_departments) is not associated to user!
简而言之:我有两个具有 M:M 关系的 Sequelize 模型。它们通过手动定义的联接 table 关联(它为每个唯一关系添加职位,即用户 A 是部门 B 中的 "Manager")。尝试按部门查找用户失败,出现严重的 table 名称错误。
续集版本“^2.0.5”
花了几个小时,但我找到了我的解决方案:
models.department.find({
where: {id:req.params.id},
include: [models.user]
问题是 Sequelize 不允许您 "go out of scope" 因为它以 model_name
开始每个 where 子句。因此,例如,当部门 table 仅作为 departments.id
加入时,where 子句试图比较 user.departments.id
。由于我们正在查询部门的值(ID),因此查询单个部门和 return 他们的关联用户是最有效的。
我有一个类似的问题,但在我的情况下我无法切换表格。
我必须使用:sequelize.literal
函数。
在您的情况下,它将如下所示:
models.user.findAll({
where: sequelize.literal("departments.id = " + req.params.id),
include: [{model: models.department, as: models.department.tableName}]
})
我不喜欢它,但它有效。
对于仍在为此寻找答案的任何人,我在 Github 上找到了一个答案。
你只需这样做:
where: {
'$Table.column$' : value
}
如果您有 class 实例,您也可以使用自动生成的 instance.getOthers()
方法
这可能意味着一个额外的查询。但是如果实例已经在手边,这是最方便的语法。
假设“用户喜欢给定分数的 post”情况,我们可以得到用户喜欢的所有 post:
const user0 = await User.create({name: 'user0'})
const user0Likes = await user0.getPosts({order: [['body', 'ASC']]})
assert(user0Likes[0].body === 'post0');
assert(user0Likes[0].UserLikesPost.score === 1);
assert(user0Likes.length === 1);
完整的可运行示例:
main.js
const assert = require('assert')
const { DataTypes, Op, Sequelize } = require('sequelize')
const common = require('./common')
const sequelize = common.sequelize(__filename, process.argv[2], { 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 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'});
// Autogenerated add* methods
// 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.
// Find posts that user1 likes which have score greater than 2.
//
{
const rows = await Post.findAll({
include: [
{
model: User,
where: {id: user1.id},
through: {
where: {score: { [Op.gt]: 2 }},
},
},
],
})
assert.strictEqual(rows[0].body, 'post2');
// TODO how to get the score here as well?
//assert.strictEqual(rows[0].UserLikesPost.score, 3);
assert.strictEqual(rows.length, 1);
}
})().finally(() => { return sequelize.close() });
common.js
const path = require('path');
const { Sequelize } = require('sequelize');
function sequelize(filename, dialect, opts) {
if (dialect === undefined) {
dialect = 'l'
}
if (dialect === 'l') {
return new Sequelize(Object.assign({
dialect: 'sqlite',
storage: path.parse(filename).name + '.sqlite'
}, opts));
} else if (dialect === 'p') {
return new Sequelize('tmp', undefined, undefined, Object.assign({
dialect: 'postgres',
host: '/var/run/postgresql',
}, opts));
} else {
throw new Error('Unknown dialect')
}
}
exports.sequelize = sequelize
package.json
{
"name": "tmp",
"private": true,
"version": "1.0.0",
"dependencies": {
"pg": "8.5.1",
"pg-hstore": "2.3.3",
"sequelize": "6.5.1",
"sqlite3": "5.0.2"
}
}
在 PostgreSQL 13.4、Ubuntu 21.04.
上测试