Skip to main content
Version: v6 - stable

子查询

假设你有两个模型 PostReaction,并且设置了一对多关系,因此一篇帖子有很多反应:

¥Consider you have two models, Post and Reaction, with a One-to-Many relationship set up, so that one post has many reactions:

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);

注意:我们禁用了时间戳,只是为了对下一个示例进行更短的查询。

¥Note: we have disabled timestamps just to have shorter queries for the next examples.

让我们用一些数据填充表格:

¥Let's fill our tables with some data:

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']);

现在,我们准备好通过示例来展示子查询的强大功能。

¥Now, we are ready for examples of the power of subqueries.

假设我们想通过 SQL 为每个帖子计算 laughReactionsCount。我们可以通过子查询来实现这一点,如下所示:

¥Let's say we wanted to compute via SQL a laughReactionsCount for each post. We can achieve that with a sub-query, such as the following:

SELECT
*,
(
SELECT COUNT(*)
FROM reactions AS reaction
WHERE
reaction.postId = post.id
AND
reaction.type = "Laugh"
) AS laughReactionsCount
FROM posts AS post

如果我们通过 Sequelize 运行上述原始 SQL 查询,我们会得到:

¥If we run the above raw SQL query through Sequelize, we get:

[
{
"id": 1,
"content": "Hello World",
"laughReactionsCount": 1
},
{
"id": 2,
"content": "My Second Post",
"laughReactionsCount": 3
}
]

那么,我们如何才能在 Sequelize 的更多帮助下实现这一目标,而不必手动编写整个原始查询呢?

¥So how can we achieve that with more help from Sequelize, without having to write the whole raw query by hand?

答案:通过将查找器方法(例如 findAll)的 attributes 选项与 sequelize.literal 实用函数相结合,允许你直接将任意内容插入到查询中,而无需任何自动转义。

¥The answer: by combining the attributes option of the finder methods (such as findAll) with the sequelize.literal utility function, that allows you to directly insert arbitrary content into the query without any automatic escaping.

这意味着 Sequelize 将帮助你完成主要的、更大的查询,但你仍然需要自己编写该子查询:

¥This means that Sequelize will help you with the main, larger query, but you will still have to write that sub-query by yourself:

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 和一个由我们(编码人员)精心编写的固定字符串。这没关系,因为我们知道我们在做什么。

¥Important Note: Since sequelize.literal inserts arbitrary content without escaping to the query, it deserves very special attention since it may be a source of (major) security vulnerabilities. It should not be used on user-generated content. However, here, we are using sequelize.literal with a fixed string, carefully written by us (the coders). This is ok, since we know what we are doing.

上面给出了以下输出:

¥The above gives the following output:

[
{
"id": 1,
"content": "Hello World",
"laughReactionsCount": 1
},
{
"id": 2,
"content": "My Second Post",
"laughReactionsCount": 3
}
]

成功!

¥Success!

使用子查询进行复杂排序

¥Using sub-queries for complex ordering

这个想法可用于实现复杂的排序,例如根据帖子的笑反应数量对帖子进行排序:

¥This idea can be used to enable complex ordering, such as ordering posts by the number of laugh reactions they have:

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']],
});

结果:

¥Result:

[
{
"id": 2,
"content": "My Second Post",
"laughReactionsCount": 3
},
{
"id": 1,
"content": "Hello World",
"laughReactionsCount": 1
}
]