使用sequelize实现关联查询

作者: adm 分类: node 发布时间: 2023-03-16

关联查询包含一对多和多对一的映射关系,一对多的API为belongsTo,多对一的API为hasMany。使用sequelize进行关联查询时,要根据具体情况选择用哪一个映射关系,因为这其中涉及左右连接、内外连接的性能问题。下面来看一个一对多的例子,当然你可以将这个例子反过来多对一,但是性能会差很多。所以我们要根据数据库表的设计来选择谁关联谁

假设有一个Student表包含id、name、age、address、gender、c_id这几个属性,还有一个Class表包含id、class_id、name、rank这几个属性。student表的c_id对应的是class表的class_id,由于历史原因,student表的c_id并未关联class表的主键id;所以在sequelize中建立映射关系时要特别注意指定class表的关联字段,否则默认关联class表的主键id。

首先定义student和class的model:

export const StudentModel = sequelize.define('student', {
    id: {
        type: Sequelize.BIGINT,
        field: 'id',
        primaryKey: true,
        autoIncrement: true
    },
    name: {
        type: Sequelize.STRING,
        field: 'name'
    },
    cId: { 
        type: Sequelize.BIGINT,
        field: 'c_id' 
    },
    // 其他属性省略。。。。
}, {
    timestamps: false,
    tableName: 'xxx_student',
});
export const ClassModel = sequelize.define('class', {
    id: {
        type: Sequelize.BIGINT,
        field: 'id',
        primaryKey: true,
        autoIncrement: true
    },
    classId: {
        type: Sequelize.BIGINT,
        field: 'class_id'
    },
    // 其他属性省略。。。。
}, {
    timestamps: false,
    tableName: 'xxx_class',
});

建立映射关系,由于student表的c_id字段值都可以在class表的class_id字段值中找到,所以student对于class是一对多的关系:

// student表与class表根据cId关联查询
StudentModel.belongsTo(ClassModel, {as: 'cla', foreignKey: 
'cId', targetKey: 'classId'});

这里需要注意as属性是为class表起别名,如果你的表名太长,这个属性就会非常方便。foreignKey是student表的外键,如果不指定targetKey属性,默认关联的是class表的主键id,但这并不符合我们的需求,所以需要指定targetKey为classId;另外当我们定义了model以后,每个字段在数据库中的样子是以下划线分割命名的,例如c_id,在model中我们使用了驼峰命名法重新对字段进行了命名,那么接下来sequelize所有的API使用都是使用model中的新字段名,他会自动和数据库表字段映射上,所以我们不管是foreignKey还是targetKey的值都是新字段名。

接下来开始真正的关联查询:

// findAndCountAll这个API既查找还统计满足条件的记录数
await StudentModel.findAndCountAll({
    where: criteria, // 这里传入的是一个查询对象,因为我的查询条件是动态的,所以前面构建好后才传入,而不是写死
    offset: start, // 前端分页组件传来的起始偏移量
    limit: Number(pageSize), // 前端分页组件传来的一页显示多少条
    include: [{ // include关键字表示关联查询
        model: ClassModel, // 指定关联的model
        as:'cla', // 由于前面建立映射关系时为class表起了别名,那么这里也要与前面保持一致,否则会报错
        attributes: [['name','className'], 'rank'], // 这里的attributes属性表示查询class表的name和rank字段,其中对name字段起了别名className
    }],
    raw:true // 这个属性表示开启原生查询,原生查询支持的功能更多,自定义更强,注意,开启此项,日期格式化会失效
}).then(
    result => {
        total = result.count; // 如果成功,则可以获取到记录条数
        list = result.rows; // 如果成功,则可以获取到记录集合
    }).catch(err => {
    getLogger().error("xxx-findAndCountAll occur error:", err);
});

关于对class表起的别名cla,你可以在nodejs项目控制台中看到sequelize 转换后的sql语句来验证。上面查询返回的是一个数组,我们来看一下其中一个元素的结构:

{
    "code": 200,
    "data": {
        "total": 136,
        "list": [{
            "id": 11,
            "name": "zhangsan",
            "age": 14,
            "address": "xxxxxx",
            "gender": 1,
            "cId": 3,
            "cla.className": "86重点班",  // 还记得我们之前为class起了别名cla嘛,这里属性前面都带cla.
            "cla.rank": 1  // 同上
        }, {........其他元素省略

如果觉得我的文章对您有用,请随意赞赏。您的支持将鼓励我继续创作!