Sub Queries - 子查询

考虑你有两个模型,即 PostReaction,它们之间建立了一对多的关系,因此一个 post 有很多 reactions:

  1. const Post = sequelize.define('post', {
  2. content: DataTypes.STRING
  3. }, { timestamps: false });
  4. const Reaction = sequelize.define('reaction', {
  5. type: DataTypes.STRING
  6. }, { timestamps: false });
  7. Post.hasMany(Reaction);
  8. Reaction.belongsTo(Post);

注意: 我们已禁用时间戳,只是为了缩短下一个示例的查询时间.

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

  1. async function makePostWithReactions(content, reactionTypes) {
  2. const post = await Post.create({ content });
  3. await Reaction.bulkCreate(
  4. reactionTypes.map(type => ({ type, postId: post.id }))
  5. );
  6. return post;
  7. }
  8. await makePostWithReactions('Hello World', [
  9. 'Like', 'Angry', 'Laugh', 'Like', 'Like', 'Angry', 'Sad', 'Like'
  10. ]);
  11. await makePostWithReactions('My Second Post', [
  12. 'Laugh', 'Laugh', 'Like', 'Laugh'
  13. ]);

现在,我们已经准备好子查询功能的示例.

假设我们要通过 SQL 为每个帖子计算一个 laughReactionsCount . 我们可以通过子查询来实现,例如:

  1. SELECT
  2. *,
  3. (
  4. SELECT COUNT(*)
  5. FROM reactions AS reaction
  6. WHERE
  7. reaction.postId = post.id
  8. AND
  9. reaction.type = "Laugh"
  10. ) AS laughReactionsCount
  11. FROM posts AS post

如果我们通过 Sequelize 运行上面的原始 SQL 查询,我们将得到:

  1. [
  2. {
  3. "id": 1,
  4. "content": "Hello World",
  5. "laughReactionsCount": 1
  6. },
  7. {
  8. "id": 2,
  9. "content": "My Second Post",
  10. "laughReactionsCount": 3
  11. }
  12. ]

那么,如何在 Sequelize 的帮助下实现这一目标,而不必手工编写整个原始查询呢?

答案是: 通过将 finder 方法(例如,findAll )的 attributes 参数与 sequelize.literal 实用程序功能结合使用,可以直接在查询中插入任意内容,而不会自动转义.

这意味着 Sequelize 将帮助你进行较大的主要查询,但是你仍然必须自己编写该子查询:

  1. Post.findAll({
  2. attributes: {
  3. include: [
  4. [
  5. // 注意下面的调用中的括号!
  6. sequelize.literal(`(
  7. SELECT COUNT(*)
  8. FROM reactions AS reaction
  9. WHERE
  10. reaction.postId = post.id
  11. AND
  12. reaction.type = "Laugh"
  13. )`),
  14. 'laughReactionsCount'
  15. ]
  16. ]
  17. }
  18. });

*重要提示:由于 sequelize.literal 会插入任意内容而不进行转义,因此,它可能是(主要)安全漏洞的来源,因此值得特别注意. 它不应该在用户生成的内容上使用.*但是在这里,我们使用自己编写的带有固定字符串的 sequelize.literal.因为我们知道我们在做什么.

上面给出了以下输出:

  1. [
  2. {
  3. "id": 1,
  4. "content": "Hello World",
  5. "laughReactionsCount": 1
  6. },
  7. {
  8. "id": 2,
  9. "content": "My Second Post",
  10. "laughReactionsCount": 3
  11. }
  12. ]

成功!

使用子查询进行复杂排序

这个想法可用于实现复杂的排序,例如根据 post 具有的 laugh 数量来排序帖子:

  1. Post.findAll({
  2. attributes: {
  3. include: [
  4. [
  5. sequelize.literal(`(
  6. SELECT COUNT(*)
  7. FROM reactions AS reaction
  8. WHERE
  9. reaction.postId = post.id
  10. AND
  11. reaction.type = "Laugh"
  12. )`),
  13. 'laughReactionsCount'
  14. ]
  15. ]
  16. },
  17. order: [
  18. [sequelize.literal('laughReactionsCount'), 'DESC']
  19. ]
  20. });

结果:

  1. [
  2. {
  3. "id": 2,
  4. "content": "My Second Post",
  5. "laughReactionsCount": 3
  6. },
  7. {
  8. "id": 1,
  9. "content": "Hello World",
  10. "laughReactionsCount": 1
  11. }
  12. ]