子查询
假设您有两个模型:Post
和 Reaction
,它们之间建立了一对多关系,因此一个帖子可以有多个反应。
const Post = sequelize.define(
'post',
{
content: DataTypes.STRING,
},
{ timestamps: false },
);
const Reaction = sequelize.define(
'reaction',
{
type: DataTypes.STRING,
},
{ timestamps: false },
);
Post.hasMany(Reaction);
Reaction.belongsTo(Post);
注意:我们已禁用时间戳,以便在后续示例中获得更短的查询。
让我们用一些数据填充我们的表。
async function makePostWithReactions(content, reactionTypes) {
const post = await Post.create({ content });
await Reaction.bulkCreate(reactionTypes.map(type => ({ type, postId: post.id })));
return post;
}
await makePostWithReactions('Hello World', [
'Like',
'Angry',
'Laugh',
'Like',
'Like',
'Angry',
'Sad',
'Like',
]);
await makePostWithReactions('My Second Post', ['Laugh', 'Laugh', 'Like', 'Laugh']);
现在,我们已准备好展示子查询强大功能的示例。
假设我们希望通过 SQL 计算每个帖子的 laughReactionsCount
。我们可以使用子查询来实现,例如以下内容。
SELECT
*,
(
SELECT COUNT(*)
FROM reactions AS reaction
WHERE
reaction.postId = post.id
AND
reaction.type = "Laugh"
) AS laughReactionsCount
FROM posts AS post
如果我们通过 Sequelize 运行上面的原始 SQL 查询,我们将得到以下结果。
[
{
"id": 1,
"content": "Hello World",
"laughReactionsCount": 1
},
{
"id": 2,
"content": "My Second Post",
"laughReactionsCount": 3
}
]
那么我们如何在 Sequelize 的帮助下实现这一点,而无需手动编写整个原始查询呢?
答案是:将查找器方法(如 findAll
)的 attributes
选项与 sequelize.literal
实用程序函数结合使用,该函数允许您直接在查询中插入任意内容,而无需任何自动转义。
这意味着 Sequelize 将帮助您处理主要的、更大的查询,但您仍然需要自己编写子查询。
Post.findAll({
attributes: {
include: [
[
// Note the wrapping parentheses in the call below!
sequelize.literal(`(
SELECT COUNT(*)
FROM reactions AS reaction
WHERE
reaction.postId = post.id
AND
reaction.type = "Laugh"
)`),
'laughReactionsCount',
],
],
},
});
重要说明:由于 sequelize.literal
会在查询中插入未转义的任意内容,因此它需要格外注意,因为它可能是(主要)安全漏洞的来源。它不应在用户生成的内容上使用。但是,这里我们使用的是带有固定字符串的 sequelize.literal
,该字符串由我们(编码人员)精心编写。这是可以的,因为我们知道自己在做什么。
以上代码将产生以下输出。
[
{
"id": 1,
"content": "Hello World",
"laughReactionsCount": 1
},
{
"id": 2,
"content": "My Second Post",
"laughReactionsCount": 3
}
]
成功!
使用子查询进行复杂排序
这个想法可以用来实现复杂的排序,例如根据帖子获得的“笑脸”反应数量来排序。
Post.findAll({
attributes: {
include: [
[
sequelize.literal(`(
SELECT COUNT(*)
FROM reactions AS reaction
WHERE
reaction.postId = post.id
AND
reaction.type = "Laugh"
)`),
'laughReactionsCount',
],
],
},
order: [[sequelize.literal('laughReactionsCount'), 'DESC']],
});
结果
[
{
"id": 2,
"content": "My Second Post",
"laughReactionsCount": 3
},
{
"id": 1,
"content": "Hello World",
"laughReactionsCount": 1
}
]