这次我们又整理了几个关于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