sequelize进阶技巧2(多对多下的查询、视图、 方法劫持)

这次我们又整理了几个关于sequelize的使用技巧给大家,本次方向重点是多对多下的操作,还有sequelize的隐藏操作,整理不易希望大家多多点赞。

定义表结构

javascript 复制代码
const { Sequelize, INTEGER, STRING, FLOAT, DATE, NOW, QueryTypes, Model } = require("sequelize");
const sequelize = new Sequelize({
  dialect: "mysql",
  database: "csdn", //你的数据库名称
  username: "root",
  password: "root", //你的数据库密码
  host: "localhost",
  port: "3306",
  timezone: "+08:00", // 由于orm用的UTC时间,这里必须加上东八区,否则取出来的时间相差8小时
  define: {
    // 使用自定义的表名
    freezeTableName: true,
    // 自动生成时间戳 -小驼峰式
    timestamps: true,
    // 表名小驼峰
    underscored: false,
  },
  attributeBehavior: "escape",
  logging: true,
});

// 这次我们用兴趣班举例子 一个兴趣班可以有很多个学生 一个学生可以报很多兴趣班
const InterestClasses = sequelize.define(
  "interest_classes",
  {
    id: { type: INTEGER, primaryKey: true, autoIncrement: true },
    name: { type: STRING, allowNull: false, comment: "兴趣班名称" },
    money: { type: FLOAT, allowNull: false, comment: "费用" },
  },
  { createdAt: false, updatedAt: false }
);

const Student = sequelize.define(
  "student",
  {
    id: { type: INTEGER, primaryKey: true, autoIncrement: true },
    name: { type: STRING, allowNull: false, comment: "学生名称" },
  },
  { createdAt: false, updatedAt: false }
);


// 连接表
const StudentInterestClass = sequelize.define(
  "student_interest_class",
  {
    id: { type: INTEGER, primaryKey: true, autoIncrement: true },
    student_id: {
      type: INTEGER,
      allowNull: false,
      comment: "学生id",
      // 这种写法会直接创建外键 做表删除或重置操作的时候会报错
      //  references: { model: Student, key: "id" }
    },
    interest_class_id: { type: INTEGER, allowNull: false, comment: "兴趣班id" },
  },
  { createdAt: false, updatedAt: false }
);

// 完美的多对多定义关系 以及中间表的一对多关系 constraints:false 去除约束 防止表创建的时候初始化外键 导致后续数据不好清除 但是外键也是提升查询速度的 由于我们数据量少 就不创建了
InterestClasses.belongsToMany(Student, { through: StudentInterestClass, constraints: false, foreignKey: "interest_class_id" });
Student.belongsToMany(InterestClasses, { through: StudentInterestClass, constraints: false, foreignKey: "student_id" });
InterestClasses.hasMany(StudentInterestClass, { foreignKey: "interest_class_id", constraints: false });
Student.hasMany(StudentInterestClass, { foreignKey: "student_id", constraints: false });
StudentInterestClass.belongsTo(Student, { foreignKey: "student_id", constraints: false });
StudentInterestClass.belongsTo(InterestClasses, { foreignKey: "interest_class_id", constraints: false });

// 初始化表
await sequelize.sync({ alter: true });

技巧1:批量创建学生数据时 直接关联已有数据项 创建到连接表中

javascript 复制代码
	// 事务创建
 	const transaction = await sequelize.transaction();
 	// 数据清除
    await sequelize.truncate({ force: true, transaction });
	// 随机数
    const randomFrom = (min, max) => {
      return Math.floor(Math.random() * (max - min + 1)) + min;
    };
	// 科目
    let classes = ["语文", "数学", "英语"];
    let creates = await InterestClasses.bulkCreate(
      classes.map((item, index) => ({ id: index + 1, name: item, money: randomFrom(1000, 2000) })),
      { transaction }
    );
    // 创建出来的科目
    creates = JSON.parse(JSON.stringify(creates));

    // 传入数组 随机获取数组里面的1,length-1个元素
    function randomArray(array) {
      let length = array.length;
      let randomLength = randomFrom(1, length - 1);
      let result = [];
      // 不拿到重复项
      while (result.length < randomLength) {
        let randomIndex = randomFrom(0, length - 1);
        let item = array[randomIndex];
        if (!result.includes(item)) {
          result.push(item);
        }
      }
      return result;
    }

    let students = [];
    for (let i = 0; i < 10; i++) {
      let interest_classes = randomArray(creates).map((item) => {
        // 这里涉及到了关联创建 create的地方必须要include关联的模型
        // interest_classes 我们定义的表  student_interest_class 连接表名 和我们上面define定义的一致
        return { ...item, interest_classes: [{ student_interest_class: item.id }] };
      });
      students.push({ name: randomName(), interest_classes });
    }
    //   console.log(students);
  //小技巧:在bulkCreate中传入include可以直接将关联表的数据插入到数据库中 但是要注意的是 关联表的数据不能重复 否则会报错 所以我们要在bulkCreate中传入ignoreDuplicates: true 来忽略重复项
    await Student.bulkCreate(students, { transaction, include: { model: InterestClasses, ignoreDuplicates: true } });

技巧2:直接获取到普通对象结果

javascript 复制代码
  // 小技巧:获取表数据后可以直接使用JSON.parse(JSON.stringify(data))来将数据转换为普通对象 不做原数组的操作的话 可以直接返回该数组
  // 为什么不能使用nest+raw呢 在findAll下使用 数组会变成 拆散的数据 返回n*m条 {id:1,name:'语文',money:1250,students:{id:1,name:xxx}}
  const class_list = await InterestClasses.findAll({ include: { model: Student, through: { attributes: [] } }, transaction });
  //   [{
  //     id: 1,
  //     name: '语文',
  //     money: 1250,
  //     students: [ [Object], [Object], [Object], [Object], [Object] ]
  //   },...]
  console.log(JSON.parse(JSON.stringify(class_list)));
   //  小技巧:findOne下也不能使用raw:true+nest:true  因为findOne下只能返回一个对象 include里的数据只会有一条 所以不能使用nest:true
  //  直接对对象上的toJSON方法就可以拿到正常对象
    const student_list = await Student.findOne({ include: { model: InterestClasses, through: { attributes: [] } }, transaction });
  //   {
  //     id: 1,
  //     name: '谢明飞',
  //     interest_classes: [
  //       { id: 1, name: '语文', money: 1250 },
  //       { id: 2, name: '数学', money: 1300 }
  //     ]
  //   }
    console.log(student_list.toJSON());

技巧3:劫持findAll方法 减少重复深拷贝写法

javascript 复制代码
  // 发现没有我们每次都要JSON.parse(JSON.stringify(data))来将数据转换为普通对象
  // 小技巧6:劫持原方法 根据条件返回是否要原始数组
  let original = Model.findAll;
  Model.findAll = async function (...args) {
    let [options] = args;
    let value = await original.apply(this, args);
    return options && options.origin ? JSON.parse(JSON.stringify(value)) : value;
  };

  //   [
  //     { id: 1, name: '谢明飞' },
  //     { id: 2, name: '何飞良' },
  //     { id: 3, name: '黄鹏涛' },
  //     { id: 4, name: '曹强良' },
  //     { id: 5, name: '唐鹏良' },
  //     { id: 6, name: '梁辉军' },
  //     { id: 7, name: '谢飞鹏' },
  //     { id: 8, name: '陈辉刚' },
  //     { id: 9, name: '胡震辉' },
  //     { id: 10, name: '曹健明' }
  //   ]
  //   console.log(await Student.findAll({ origin: true }));

技巧4:fn函数

javascript 复制代码
  // 和我们上一章一样 但是findOne的时候就会报错 提示 interest_classes.money这行不存在
    const student_cost = await Student.findAll({
      origin:true,
      attributes: ["name", [sequelize.fn("SUM", sequelize.col("interest_classes.money")), "cost"]],
      include: { model: InterestClasses, through: { attributes: [] }, attributes: [] },
      group: ["Student.id"],
    });
  //   [
  //     { name: "谢明飞", cost: 2550 },
  //     { name: "何飞良", cost: 2550 },
  //     { name: "黄鹏涛", cost: 1418 },
  //     { name: "曹强良", cost: 1300 },
  //     { name: "唐鹏良", cost: 1418 },
  //     { name: "梁辉军", cost: 1300 },
  //     { name: "谢飞鹏", cost: 2718 },
  //     { name: "陈辉刚", cost: 2550 },
  //     { name: "胡震辉", cost: 2550 },
  //     { name: "曹健明", cost: 1250 },
  //   ];
    console.log(student_cost);

// Unknown column 'interest_classes.money' in 'field list'
  // 解决方案:加上where条件的id
    const student_cost_one = await Student.findOne({
      attributes: ["name", [sequelize.fn("SUM", sequelize.col("interest_classes.money")), "cost"]],
      include: { model: InterestClasses,  through: { attributes: [] }, attributes: [] },
      group:["Student.id"]
    });
    console.log(student_cost_one);

 // 不加where的语句就会出现这个样的查询方式 FROM 里面取了一条数据 然后再去关联表里面取数据 我们对attributes编写的fn函数位置不对了
  //  源码sequelize/model.js 1236行 当findOne 无where 或者where里没有 primaryKey uniqueKeys 时 会为 limit=1 因为是先深拷贝的options 所以我们后写的limit也无用
  //   SELECT
  //   `student`.*
  // FROM
  //   (
  //       SELECT
  //           `student`.`id`,
  //           `student`.`name`,
  //           SUM(`interest_classes`.`money`) AS `cost`
  //       FROM
  //           `student` AS `student`
  //       LIMIT 1
  //   ) AS `student`
  // LEFT OUTER JOIN (
  //   `student_interest_class` AS `interest_classes->student_interest_class`
  //   INNER JOIN `interest_classes` AS `interest_classes` ON `interest_classes`.`id` = `interest_classes->student_interest_class`.`interest_class_id`
  // ) ON `student`.`id` = `interest_classes->student_interest_class`.`student_id`

  const right_student_cost_one = await Student.findOne({
    where: {
      id: 1,
    },
    attributes: ["name", [sequelize.fn("SUM", sequelize.col("interest_classes.money")), "cost"]],
    include: { model: InterestClasses, through: { attributes: [] }, attributes: [] },
  });
  //   { name: '谢明飞', cost: 2550 }
  console.log(right_student_cost_one.toJSON());

技巧5:literal函数做计算操作

javascript 复制代码
  // 通过literal计算出该兴趣班报名的总报名费 literal在属性上的操作 是完全自由的 可以使用任何函数 甚至叠加函数 插入查询等
  let class_cost = await InterestClasses.findAll({
    origin:true,
    attributes: [
      "name",
      "money",
      [sequelize.literal(`count(students.id) * interest_classes.money`), "total_money"],
      [sequelize.fn("count", sequelize.col("students.id")), "count"],
    ],
    include: { model: Student, through: { attributes: [] }, attributes: [] },
    group: ["interest_classes.id"],
  });
  // [
  //   { name: '语文', money: 1250, total_money: 6250, count: 5 },
  //   { name: '数学', money: 1300, total_money: 9100, count: 7 },
  //   { name: '英语', money: 1418, total_money: 4254, count: 3 }
  // ]
  console.log(class_cost);

技巧7:反复使用的原始查询转换为视图 再使用模型映射

javascript 复制代码
  // 我们之前遇到原始语句才能做到的事情 如果反复调用 那要封装起来 或者还有新的查询条件的时候 又要修改原方法
  // 小技巧:视图操作 其实sequelize支持对视图进行查询的操作且允许各种条件查询 但是要注意的是 视图是不能进行修改的
  // 需求:获取每个科目最新报名的人
  // 当这种情况出现的时候 正常思路是 通过StudentInterestClass去groupby interest_class_id 然后order by
  let lastest_student = await StudentInterestClass.findAll({
    group: ["interest_class_id"],
    order: [["id", "desc"]],
    include: [
      { model: Student, attributes: ["name"] },
      { model: InterestClasses, attributes: ["name"] },
    ],
    origin: true,
  });
  // 发现查询结果达不到要求
  //   [
  //     {
  //       id: 5,
  //       student_id: 3,
  //       interest_class_id: 3,
  //       student: { name: '黄鹏涛' },
  //       interest_class: { name: '英语' }
  //     },
  //     {
  //       id: 2,
  //       student_id: 1,
  //       interest_class_id: 2,
  //       student: { name: '谢明飞' },
  //       interest_class: { name: '数学' }
  //     },
  //     {
  //       id: 1,
  //       student_id: 1,
  //       interest_class_id: 1,
  //       student: { name: '谢明飞' },
  //       interest_class: { name: '语文' }
  //     }
  //   ]
  console.log(lastest_student);

// 进行视图创建
 await sequelize.query(
    "CREATE OR REPLACE VIEW join_view AS SELECT b.id, student_id, interest_class_id, student.`name` AS `student_name`, interest_class.`name` AS `subject_name`, interest_class.`money` AS `money` FROM (SELECT *, ROW_NUMBER() OVER (PARTITION BY interest_class_id ORDER BY id DESC) AS rn FROM `student_interest_class` AS r) AS b LEFT OUTER JOIN `student` AS `student` ON `b`.`student_id` = `student`.`id` LEFT OUTER JOIN `interest_classes` AS `interest_class` ON `b`.`interest_class_id` = `interest_class`.`id`"
  );
  // 防止重复调用query的情况下 我们可以创建视图 再通过模型去绑定视图 这样就可以直接使用模型的方法来查询了
  //   CREATE OR REPLACE VIEW join_view AS
  // SELECT
  //   b.id,
  //   student_id,
  //   interest_class_id,
  //   student.`name` AS `student_name`,
  //   interest_class.`name` AS `subject_name`,
  //   interest_class.`money` AS `money`
  // FROM (
  //   SELECT
  //     *, ROW_NUMBER() OVER (
  //       PARTITION BY interest_class_id
  //       ORDER BY id DESC
  //     ) AS rn
  //   FROM `student_interest_class` AS r
  // ) AS b
  // LEFT OUTER JOIN `student` AS `student` ON `b`.`student_id` = `student`.`id`
  // LEFT OUTER JOIN `interest_classes` AS `interest_class` ON `b`.`interest_class_id` = `interest_class`.`id`;
  
  // 创建视图模型
  const JoinView = sequelize.define(
    "join_view",
    {
      id: { type: INTEGER, primaryKey: true, autoIncrement: true },
      student_id: { type: INTEGER, allowNull: false },
      interest_class_id: { type: INTEGER, allowNull: false },
      student_name: { type: STRING, allowNull: false },
      subject_name: { type: STRING, allowNull: false },
      money: { type: FLOAT, allowNull: false },
    },
    { timestamps: false }
  );
  //   [
  //     {
  //       id: 15,
  //       student_id: 10,
  //       interest_class_id: 1,
  //       student_name: '曹健明',
  //       subject_name: '语文',
  //       money: 1250
  //     },
  //     {
  //       id: 14,
  //       student_id: 9,
  //       interest_class_id: 2,
  //       student_name: '胡震辉',
  //       subject_name: '数学',
  //       money: 1300
  //     },
  //     {
  //       id: 9,
  //       student_id: 7,
  //       interest_class_id: 3,
  //       student_name: '谢飞鹏',
  //       subject_name: '英语',
  //       money: 1418
  //     }
  //   ]

  // 视图方便了我们做的查询操作 但是不能完全依赖视图 视图毕竟是通过语句生成的 不如原表操作速度块 为了效率 有时候我们可以牺牲空间来转换 下次我们讲讲钩子
  console.log(await JoinView.findAll({ origin: true, group: ["subject_name"] }));

这次的分享到这里就结束了,祝愿大家越变越强,工资up up up

相关推荐
Java中文社群2 分钟前
聊聊SpringAI流式输出的底层实现?
java·人工智能·后端
雷渊4 分钟前
如何设计一个订单号生成服务?
后端
雷渊9 分钟前
设计秒杀系统需要考虑哪些因素?
后端
super凹凸曼18 分钟前
分享一个把你的API快速升级为MCP规范的方案,可在线体验
java·后端·开源
离线请留言20 分钟前
本地密码管理器-Vaultwarden
后端
IT杨秀才21 分钟前
LangChain框架入门系列(3):数据连接
人工智能·后端·langchain
IT杨秀才22 分钟前
LangChain框架入门系列(2):Hello LangChain
人工智能·后端·langchain
七月丶25 分钟前
🧼 为什么我开始在项目里禁用 CSS 文件?
前端·javascript·后端
一只叫煤球的猫26 分钟前
事务原来很简单——SpringBoot中如何正确使用事务
spring boot·后端·面试
蚂蚁搬家咯33 分钟前
redis-shake实现无感知不停服迁移redis服务以及数据
后端