sequelize的进阶使用(助力成为优秀全栈)

当在使用sequelize时,我们发现中文文档中很多进阶的用法只是简单列出来,还有大部分的使用方法其实需要数据库知识的支撑才能实现 下列我们就列出一些隐藏的用法解决开发中会遇到的问题,尽量让想要的数据一次就能输出出来,尽量不出现循环查询以及多次查询实现目标的问题。

表结构以及模拟数据的生成

用于本篇文章重点是在于如何去查 使用大量的sequelize提供的方法 所以我们本次不做关联的操作

javascript 复制代码
const Record = sequelize.define(
  "record",
  {
    id: { type: Sequelize.INTEGER, primaryKey: true, autoIncrement: true },
    name: { type: STRING, allowNull: false, comment: "学生名称" },
    subject: { type: STRING, allowNull: false, comment: "科目" },
    seconds: { type: INTEGER, allowNull: false, seconds: "用时" },
    score: { type: FLOAT, allowNull: false, comment: "分数" },
    class_name: { type: STRING, allowNull: false, comment: "班级名称" },
    school_name: { type: STRING, allowNull: false, comment: "学校名称" },
    createdAt: {
      type: DATE,
      defaultValue: NOW,
      comment: "创建时间",
      get() {
        return moment(this.getDataValue("createdAt")).format("YYYY-MM-DD HH:mm:ss");
      },
    },
  },
  { createdAt: true }
);

模拟数据的生成 共2个学校 每班30个学生 每个学生每个科目三次成绩

javascript 复制代码
const SCHOOL_NAME = ["深职院", "深信息"];
const CLASS_NAME = ["1班", "2班", "3班"];
const SUBJECTS = ["语文", "数学", "英语"];
async function mockData() {
  let count = await Record.count();
  if (count) return;
  let bulk = [];
  const randomFrom = (min, max) => {
    return Math.floor(Math.random() * (max - min + 1)) + min;
  };
  /** 模拟数据 2*3*30*3*3=1620条 */
  for (let school_name of SCHOOL_NAME) {
    for (let class_name of CLASS_NAME) {
      for (let j = 0; j < 30; j++) {
        let name = randomName(randomFrom(0, 1));
        for (let subject of SUBJECTS) {
          for (let i = 0; i < 3; i++) {
            bulk.push({
              name,
              subject,
              seconds: randomFrom(10, 100),
              score: randomFrom(0, 100),
              class_name,
              school_name,
            });
          }
        }
      }
    }
  }
  await Record.bulkCreate(bulk);
}

例子以及思路

常用的FN函数使用

javascript 复制代码
  // fn("SUM") fn("COUNT") fn("AVG") fn("MIN") fn("MAX") fn("concat") fn("DATE_FORMAT")
  // 1.fn用法一:统计班级每个学生的考试次数 通过fn函数操作
  let student_test_count = await Record.findAll({
    attributes: ["name", "class_name", "school_name", [seq.fn("COUNT", seq.col("id")), "count"]],
    group: ["name", "class_name", "school_name"],
    raw: true,
  });
  //  [{ name: '黄美巧', class_name: '1班', school_name: '深职院', count: 9 },...]
  //   console.log(student_test_count);
  
  // 2.fn用法二:统计两个学校考核用时 通过fn函数操作
  let school_times = await Record.findAll({
    attributes: ["school_name", [seq.fn("SUM", seq.col("seconds")), "seconds"]],
    group: ["school_name"],
    raw: true,
  });

  //   [
  //     { school_name: "深职院", seconds: "45656" },
  //     { school_name: "深信息", seconds: "44474" },
  //   ];
  //   console.log(school_times);
  
  // 3.我们知道其实findOne联动fn中的COUNT SUM AVG等函数可以直接统计某个字段
  // 但是fn的findOne和findAll有区别的 如果存在group的情况 要用findAll 不然数据会缺失
  let school_time_one = await Record.findOne({
    attributes: ["school_name", [seq.fn("SUM", seq.col("seconds")), "seconds"]],
    group: ["school_name"],
    raw: true,
  });
  // { school_name: '深职院', seconds: '45656' }
  //   console.log(school_time_one);

  // 4.fn搭配seq.where 获取深职院每个学生每个科目的考试次数 一次性统计出来
  let student_subject_count = await Record.findAll({
    where: {
      school_name: "深职院",
    },
    attributes: ["name", ...SUBJECTS.map((subject) => [seq.fn("SUM", seq.where(seq.col("subject"), subject)), subject])],
    group: ["name"],
    raw: true,
  });
  //[ { name: '黄美巧', '语文': '3', '数学': '3', '英语': '3' },...]
  //   console.log(student_subject_count);
  
  // 5.获取某个班级科目的分数 并且通过concat 函数拼接字符串 输出
  let class_score = await Record.findAll({
    where: {
      school_name: "深职院",
      class_name: "1班",
    },
    attributes: [
      [seq.fn("CONCAT", seq.col("school_name"), "-", seq.col("class_name")), "full_name"],
      [seq.fn("MAX", seq.col("score")), "max_score"],
      [seq.fn("AVG", seq.col("score")), "avg_score"],
      [seq.fn("MIN", seq.col("score")), "min_score"],
      [seq.fn("COUNT", seq.col("score")), "count"],
      "subject",
    ],
    group: ["school_name", "class_name", "subject"],
    raw: true,
  });
  //   [
  //     {
  //       full_name: "深职院-1班",
  //       max_score: 100,
  //       avg_score: 54.25555555555555,
  //       min_score: 0,
  //       count: 90,
  //       subject: "语文",
  //     },...
  //   ];
  //   console.log(class_score);

  // 11. 去重操作 获取所有人的名称 但是不重复 可以使用distinct函数
  // 一般distinct 必须要放在首位 除非是使用函数的情况下就不需要放在首位了
  // distinct 好文 https://blog.csdn.net/baomingshu/article/details/137016186
  let student_distinct = await Record.findAll({
    raw: true,
    attributes: [[seq.fn("DISTINCT", seq.col("name")), "name"]],
  });
  // [ { name: '黄美巧' },...]
  //   console.log(student_distinct);

literal与where的使用

javascript 复制代码
//   1.随机获取数据与where的进阶用法 查找某年某月某天的数据 不用between的操作符 可以用DATE_FORMAT函数来操作
//   已知生成了1620条模拟数据 创建都是2025-04-15的 所以我们要更改一些时间 更改200条 来测试
//   为了保证数据随机的更改 我们通过先find操作条数 然后再update操作 因为update不能直接限制修改多少条
    let target = await Record.findAll({
      order: seq.literal("rand()"),
      limit: 200,
      attributes: ["id"],
      raw: true,
    });
    // console.log(target);
    await Record.update({ createdAt: "2025-04-14 18:00:00" }, { where: { id: target.map((item) => item.id) }, transaction });
    const time_data = await Record.count({
      where: {
        [Op.and]: [seq.where(seq.fn("DATE_FORMAT", seq.col("createdAt"), "%Y-%m-%d"), "2025-04-14")],
      },
      transaction,
    });
  200
    console.log(time_data);

 // 2.获取年份数据
  let time_compare = await Record.count({
    where: seq.where(seq.fn("YEAR", sequelize.col("createdAt")), 2025),
    transaction,
  });
  // 1620
  console.log(time_compare);
  
  // 3.子查询 查出学生的每个科目考试最高分数 这部分就要自己手写了
  // 关键点 seq.literal中的select必须要左右括号 并且 from 的表名要加 as xx 不然没法和原表比较
  // 最好要加上索引 比如我们比较的name部分
  // explain 生成的语句 发现使用了子查询 但是没有使用索引 type全为all 走了全表
  let student_subject_max = await Record.findOne({
    attributes: [
      "name",
      ...SUBJECTS.map((subject) => [
        seq.literal(`(SELECT MAX(score) FROM record as r WHERE r.name = record.name AND subject='${subject}')`),
        subject,
      ]),
    ],
    raw: true,
  });
  // { name: '黄美巧', '语文': 100, '数学': 16, '英语': 96 }
  //   console.log(student_subject_max);

  // 4.通过case when 来判断返回数据
  // 比如我们想判断成绩是否及格 及格的话返回 及格 不及格的话返回 不及格
  // 可以使用case when 来实现 case when允许多个条件 这里我就写一个条件
  let student_score_level = await Record.findAll({
    raw: true,
    attributes: ["*", [seq.literal(`CASE WHEN score >= 60 THEN '及格' ELSE '不及格' END`), "level"]],
  });
  //   {
  //     id: 1,
  //     name: '黄美巧',
  //     subject: '语文',
  //     seconds: 81,
  //     score: 10,
  //     class_name: '1班',
  //     school_name: '深职院',
  //     createdAt: 2025-04-15T02:57:31.000Z,
  //     updatedAt: 2025-04-15T02:57:31.000Z,
  //     level: '不及格'
  //   }
  console.log(student_score_level[0]);

原始查询搭配窗口函数 实现先排序后分组再排序

javascript 复制代码
  // 原始查询 针对无法使用sequelize的函数或者定制化的情况
  // DQL 的编写顺序  SELECT * FROM Table as t GROUP BY t.`name` ORDER BY t.`score` DESC LIMIT 索引,条数;
  // 比如说这种情况 要先排序后分组 的情况 但是我们看到上面的语句 是先分组后排序的 所以我们需要使用原始查询来实现
  // 我想拿到每个学生 最新提交的科目成绩

  // 普通查询
  let student_latest_score = await Record.findAll({
    group: ["name"],
    order: [["id", "asc"]],
    raw: true,
    attributes: ["id", "name", "score"],
  });
  //  [{ id: 1, name: '黄美巧', score: 10 },...]
  // 会发现 拿到的id是最早的 先group再排序了 所以我们需要使用原始查询来实现
  //   console.log(student_latest_score);
  //  最新的数据应该是{id:9,name:'黄美巧',score:60}

  // mysql8.0以上才支持:利用窗口函数先排序再分组 拿到最新的成绩 就可以拿到每个人的最新成绩了
  // 窗口函数解释好文 https://blog.csdn.net/frostlulu/article/details/130729113
  let query = await sequelize.query(
    `SELECT
	*
    FROM
	(
		SELECT
			*, ROW_NUMBER () OVER (
				PARTITION BY NAME
				ORDER BY
					id DESC
			) AS rn
		FROM
			record
	) AS r
    GROUP BY
	r. NAME order by r.id asc`
  );
  // 输出每个人最新的成绩 并且按照id升序排序
  // [{id:9,name:'黄美巧',score:60},...]
  // console.log(query);

还在不断摸索更多的使用方式途中,了解更多的mysql知识才会有更多的用法,sequelize留给的开放度还是挺高的,针对最后一种的这种原始语句的操作,对官方提过了issue 还有没别的解决方案,他们回答后续会推出和TypeORM一样的QueryBuilder的语句构建方式。

相关推荐
豌豆花下猫2 小时前
Python 3.14 新特性盘点,更新了些什么?
后端·python·ai
caihuayuan52 小时前
Vue生命周期&脚手架工程&Element-UI
java·大数据·spring boot·后端·课程设计
明月与玄武5 小时前
Spring Boot中的拦截器!
java·spring boot·后端
菲兹园长5 小时前
SpringBoot统一功能处理
java·spring boot·后端
muxue1785 小时前
go语言封装、继承与多态:
开发语言·后端·golang
开心码农1号6 小时前
Go语言中 源文件开头的 // +build 注释的用法
开发语言·后端·golang
北极象6 小时前
Go主要里程碑版本及其新增特性
开发语言·后端·golang
哎哟喂_!6 小时前
深入理解 Node.js 模块化(CommonJS):原理、用法与避坑指南
node.js
lyrhhhhhhhh6 小时前
Spring框架(1)
java·后端·spring
阿里小阿希6 小时前
解决 pnpm dev 运行报错的坎坷历程
前端·node.js