跳至主要内容
版本: v6 - 稳定版

子查询

假设您有两个模型:PostReaction,它们之间建立了一对多关系,因此一个帖子可以有多个反应。

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
}
]