获取器和修改器
模型层
// app/model/user.js module.exports = app => { const { STRING, INTEGER, DATE } = app.Sequelize; const User = app.model.define('user', { id: { type: INTEGER, primaryKey: true, autoIncrement: true }, name: { type: STRING(30), // 单独字段的getter,查询时都会调用 // this.getDataValue('name') 获取原始值 get() { const age = this.getDataValue('age'); return this.getDataValue('name') + '年龄:' + age; } }, age: { type: INTEGER, // 单独字段的setter,新增和更新时调用 // this.setDataValue('name') 设置原始值 set(val) { this.setDataValue('age', val * 10); } }, created_at: DATE, updated_at: DATE, }); // 关联用户资料 User.associate = function(models) { app.model.User.hasOne(app.model.Userinfo); } return User; };
控制器层
async show() { // 根据主键查询 let user = await this.ctx.model.User.findOne({ where: { id: 3 } }); // 获取原始值 user.getDataValue('name') this.ctx.body = user.getDataValue('name') }
模型钩子
模型层
module.exports = app => { ... // 钩子 // 查询前 User.beforeFind((user, option) => { console.log('查询前'); }); // 查询后 User.afterFind((user, option) => { console.log('查询后'); }); // 新增前 User.beforeCreate((user, option) => { console.log('新增前'); }); // 新增后 User.afterCreate((user, option) => { console.log('新增后'); }); // 修改前 User.beforeUpdate((user, option) => { console.log('修改前'); }); // 修改后 User.afterUpdate((user, option) => { console.log('修改后'); // 真正修改才会触发,数据相同不会触发 }); // 删除前 User.beforeDestroy((user, option) => { console.log('删除前'); }); // 删除后 User.afterDestroy((user, option) => { console.log('删除后'); }); return User; };
查询
主键查询
Model.findByPk(1)
查找不存在则创建
方法 findOrCreate 可用于检查数据库中是否已存在某个元素. 如果是这种情况,则该方法将生成相应的实例. 如果元素不存在,将会被创建.
如果是这种情况,则该方法将导致相应的实例. 如果元素不存在,将会被创建.
假设我们有一个空的数据库,一个 User 模型有一个 username 和 job.
User .findOrCreate({ where: { username: 'sdepold' }, defaults: { job: 'Technical Lead JavaScript' } }) . then(([user, created]) => { console.log(user.get({ plain: true })) console.log(created) /* findOrCreate 返回一个包含已找到或创建的对象的数组,找到或创建的对象和一个布尔值,如果创建一个新对象将为true,否则为false,像这样: [ { username: 'sdepold', job: 'Technical Lead JavaScript', id: 1, createdAt: Fri Mar 22 2013 21: 28: 34 GMT + 0100(CET), updatedAt: Fri Mar 22 2013 21: 28: 34 GMT + 0100(CET) }, true ] 在上面的例子中,第三行的数组将分成2部分,并将它们作为参数传递给回调函数,在这种情况下将它们视为 "user" 和 "created" .(所以“user”将是返回数组的索引0的对象,并且 "created" 将等于 "true".) */ })
代码创建了一个新的实例. 所以当我们已经有一个实例了 …
User.create({ username: 'fnord', job: 'omnomnom' }) .then(() => User.findOrCreate({ where: { username: 'fnord' }, defaults: { job: 'something else' } })) .then(([user, created]) => { console.log(user.get({ plain: true })) console.log(created) /* 在这个例子中,findOrCreate 返回一个如下的数组: [ { username: 'fnord', job: 'omnomnom', id: 2, createdAt: Fri Mar 22 2013 21: 28: 34 GMT + 0100(CET), updatedAt: Fri Mar 22 2013 21: 28: 34 GMT + 0100(CET) }, false ] 由findOrCreate返回的数组通过第三行的数组扩展为两部分,并且这些部分将作为2个参数传递给回调函数,在这种情况下将其视为 "user" 和 "created" .(所以“user”将是返回数组的索引0的对象,并且 "created" 将等于 "false".) */ })
…现有条目将不会更改. 看到第二个用户的 “job”,并且实际上创建操作是假的.
查找并计数
findAndCountAll - 在数据库中搜索多个元素,返回数据和总计数
这是一个方便的方法,它结合了 findAll 和 count(见下文),当处理与分页相关的查询时,这是有用的,你想用 limit 和 offset 检索数据,但也需要知道总数与查询匹配的记录数:
处理程序成功将始终接收具有两个属性的对象:
- count - 一个整数,总数记录匹配where语句和关联的其它过滤器
- rows - 一个数组对象,记录在limit和offset范围内匹配where语句和关联的其它过滤器,
Project .findAndCountAll({ where: { title: { [Op.like]: 'foo%' } }, offset: 10, limit: 2 }) .then(result => { console.log(result.count); console.log(result.rows); });
它支持 include. 只有标记为 required 的 include 将被添加到计数部分:
假设你想查找附有个人资料的所有用户:
User.findAndCountAll({ include: [ { model: Profile, required: true} ], limit: 3 });
因为 Profile 的 include 有 required 设置,这将导致内部连接,并且只有具有 profile 的用户将被计数. 如果我们从 include 中删除required,那么有和没有 profile 的用户都将被计数. 在include中添加一个 where 语句会自动使它成为 required:
User.findAndCountAll({ include: [ { model: Profile, where: { active: true }} ], limit: 3 });
上面的查询只会对具有 active profile 的用户进行计数,因为在将 where 语句添加到 include 时,required 被隐式设置为 true.
传递给 findAndCountAll 的 options 对象与 findAll 相同(如下所述).
查询多个(常用)
// 找到多个条目 Project.findAll().then(projects => { // projects 将是所有 Project 实例的数组 }) // 搜索特定属性 - 使用哈希 Project.findAll({ where: { name: 'A Project' } }).then(projects => { // projects将是一个具有指定 name 的 Project 实例数组 }) // 在特定范围内进行搜索 Project.findAll({ where: { id: [1,2,3] } }).then(projects => { // projects将是一系列具有 id 1,2 或 3 的项目 // 这实际上是在做一个 IN 查询 }) Project.findAll({ where: { id: { [Op.and]: {a: 5}, // 且 (a = 5) [Op.or]: [{a: 5}, {a: 6}], // (a = 5 或 a = 6) [Op.gt]: 6, // id > 6 [Op.gte]: 6, // id >= 6 [Op.lt]: 10, // id < 10 [Op.lte]: 10, // id <= 10 [Op.ne]: 20, // id != 20 [Op.between]: [6, 10], // 在 6 和 10 之间 [Op.notBetween]: [11, 15], // 不在 11 和 15 之间 [Op.in]: [1, 2], // 在 [1, 2] 之中 [Op.notIn]: [1, 2], // 不在 [1, 2] 之中 [Op.like]: '%hat', // 包含 '%hat' [Op.notLike]: '%hat', // 不包含 '%hat' [Op.iLike]: '%hat', // 包含 '%hat' (不区分大小写) (仅限 PG) [Op.notILike]: '%hat', // 不包含 '%hat' (仅限 PG) [Op.overlap]: [1, 2], // && [1, 2] (PG数组重叠运算符) [Op.contains]: [1, 2], // @> [1, 2] (PG数组包含运算符) [Op.contained]: [1, 2], // <@ [1, 2] (PG数组包含于运算符) [Op.any]: [2,3], // 任何数组[2, 3]::INTEGER (仅限 PG) }, status: { [Op.not]: false, // status 不为 FALSE } } })
复合过滤 / OR / NOT 查询
你可以使用多层嵌套的 AND,OR 和 NOT 条件进行一个复合的 where 查询. 为了做到这一点,你可以使用 or , and 或 not 运算符:
Project.findOne({ where: { name: 'a project', [Op.or]: [ { id: [1,2,3] }, { id: { [Op.gt]: 10 } } ] } }) Project.findOne({ where: { name: 'a project', id: { [Op.or]: [ [1,2,3], { [Op.gt]: 10 } ] } } })
这两段代码将生成以下内容:
SELECT * FROM `Projects` WHERE ( `Projects`.`name` = 'a project' AND (`Projects`.`id` IN (1,2,3) OR `Projects`.`id` > 10) ) LIMIT 1;
not 示例:
Project.findOne({ where: { name: 'a project', [Op.not]: [ { id: [1,2,3] }, { array: { [Op.contains]: [3,4,5] } } ] } });
将生成:
SELECT * FROM `Projects` WHERE ( `Projects`.`name` = 'a project' AND NOT (`Projects`.`id` IN (1,2,3) OR `Projects`.`array` @> ARRAY[3,4,5]::INTEGER[]) ) LIMIT 1;
用限制,偏移,顺序和分组操作数据集
要获取更多相关数据,可以使用限制,偏移,顺序和分组:
// 限制查询的结果 Project.findAll({ limit: 10 }) // 跳过前10个元素 Project.findAll({ offset: 10 }) // 跳过前10个元素,并获取2个 Project.findAll({ offset: 10, limit: 2 })
分组和排序的语法是相同的,所以下面只用一个单独的例子来解释分组,而其余的则是排序. 你下面看到的所有内容也可以对分组进行
Project.findAll({order: [['title', 'DESC']]}) // 生成 ORDER BY title DESC Project.findAll({group: 'name'}) // 生成 GROUP BY name
请注意,在上述两个示例中,提供的字符串逐字插入到查询中,所以不会转义列名称. 当你向 order / group 提供字符串时,将始终如此. 如果要转义列名,你应该提供一个参数数组,即使你只想通过单个列进行 order / group
something.findOne({ order: [ // 将返回 `name` ['name'], // 将返回 `username` DESC ['username', 'DESC'], // 将返回 max(`age`) sequelize.fn('max', sequelize.col('age')), // 将返回 max(`age`) DESC [sequelize.fn('max', sequelize.col('age')), 'DESC'], // 将返回 otherfunction(`col1`, 12, 'lalala') DESC [sequelize.fn('otherfunction', sequelize.col('col1'), 12, 'lalala'), 'DESC'], // 将返回 otherfunction(awesomefunction(`col`)) DESC,这个嵌套是可以无限的! [sequelize.fn('otherfunction', sequelize.fn('awesomefunction', sequelize.col('col'))), 'DESC'] ] })
回顾一下,order / group数组的元素可以是以下内容:
- String - 将被引用
- Array - 第一个元素将被引用,第二个将被逐字地追加
- Object -
raw 将被添加逐字引用
如果未设置 raw,一切都被忽略,查询将失败
- Sequelize.fn 和 Sequelize.col 返回函数和引用的列名
字段过滤
想要只选择某些属性,可以使用 attributes 选项. 通常是传递一个数组:
Model.findAll({ attributes: ['foo', 'bar'] });
SELECT foo, bar …
属性可以使用嵌套数组来重命名:
Model.findAll({ attributes: ['foo', ['bar', 'baz']] });
SELECT foo, bar AS baz …
也可以使用 sequelize.fn 来进行聚合:
Model.findAll({ attributes: [[sequelize.fn('COUNT', sequelize.col('hats')), 'no_hats']] });
SELECT COUNT(hats) AS no_hats …
使用聚合功能时,必须给它一个别名,以便能够从模型中访问它. 在上面的例子中,你可以使用 instance.get('no_hats') 获得帽子数量.
有时,如果你只想添加聚合,则列出模型的所有属性可能令人厌烦:
// This is a tiresome way of getting the number of hats... Model.findAll({ attributes: ['id', 'foo', 'bar', 'baz', 'quz', [sequelize.fn('COUNT', sequelize.col('hats')), 'no_hats']] }); // This is shorter, and less error prone because it still works if you add / remove attributes Model.findAll({ attributes: { include: [[sequelize.fn('COUNT', sequelize.col('hats')), 'no_hats']] } }); SELECT id, foo, bar, baz, quz, COUNT(hats) AS no_hats ...
同样,它也可以排除一些指定的表字段:
Model.findAll({ attributes: { exclude: ['baz'] } }); SELECT id, foo, bar, quz ...
Where
无论你是通过 findAll/find 或批量 updates/destroys 进行查询,都可以传递一个 where 对象来过滤查询.
where 通常用 attribute:value 键值对获取一个对象,其中 value 可以是匹配等式的数据或其他运算符的键值对象.
也可以通过嵌套 or 和 and 运算符 的集合来生成复杂的 AND/OR 条件.
基础
const Op = Sequelize.Op; Post.findAll({ where: { authorId: 2 } }); // SELECT * FROM post WHERE authorId = 2 Post.findAll({ where: { authorId: 12, status: 'active' } }); // SELECT * FROM post WHERE authorId = 12 AND status = 'active'; Post.findAll({ where: { [Op.or]: [{authorId: 12}, {authorId: 13}] } }); // SELECT * FROM post WHERE authorId = 12 OR authorId = 13; Post.findAll({ where: { authorId: { [Op.or]: [12, 13] } } }); // SELECT * FROM post WHERE authorId = 12 OR authorId = 13; Post.destroy({ where: { status: 'inactive' } }); // DELETE FROM post WHERE status = 'inactive'; Post.update({ updatedAt: null, }, { where: { deletedAt: { [Op.ne]: null } } }); // UPDATE post SET updatedAt = null WHERE deletedAt NOT NULL; Post.findAll({ where: sequelize.where(sequelize.fn('char_length', sequelize.col('status')), 6) }); // SELECT * FROM post WHERE char_length(status) = 6;
操作符
Sequelize 可用于创建更复杂比较的符号运算符 -
const Op = Sequelize.Op [Op.and]: {a: 5} // 且 (a = 5) [Op.or]: [{a: 5}, {a: 6}] // (a = 5 或 a = 6) [Op.gt]: 6, // id > 6 [Op.gte]: 6, // id >= 6 [Op.lt]: 10, // id < 10 [Op.lte]: 10, // id <= 10 [Op.ne]: 20, // id != 20 [Op.eq]: 3, // = 3 [Op.not]: true, // 不是 TRUE [Op.between]: [6, 10], // 在 6 和 10 之间 [Op.notBetween]: [11, 15], // 不在 11 和 15 之间 [Op.in]: [1, 2], // 在 [1, 2] 之中 [Op.notIn]: [1, 2], // 不在 [1, 2] 之中 [Op.like]: '%hat', // 包含 '%hat' [Op.notLike]: '%hat' // 不包含 '%hat' [Op.iLike]: '%hat' // 包含 '%hat' (不区分大小写) (仅限 PG) [Op.notILike]: '%hat' // 不包含 '%hat' (仅限 PG) [Op.startsWith]: 'hat' // 类似 'hat%' [Op.endsWith]: 'hat' // 类似 '%hat' [Op.substring]: 'hat' // 类似 '%hat%' [Op.regexp]: '^[h|a|t]' // 匹配正则表达式/~ '^[h|a|t]' (仅限 MySQL/PG) [Op.notRegexp]: '^[h|a|t]' // 不匹配正则表达式/!~ '^[h|a|t]' (仅限 MySQL/PG) [Op.iRegexp]: '^[h|a|t]' // ~* '^[h|a|t]' (仅限 PG) [Op.notIRegexp]: '^[h|a|t]' // !~* '^[h|a|t]' (仅限 PG) [Op.like]: { [Op.any]: ['cat', 'hat']} // 包含任何数组['cat', 'hat'] - 同样适用于 iLike 和 notLike [Op.overlap]: [1, 2] // && [1, 2] (PG数组重叠运算符) [Op.contains]: [1, 2] // @> [1, 2] (PG数组包含运算符) [Op.contained]: [1, 2] // <@ [1, 2] (PG数组包含于运算符) [Op.any]: [2,3] // 任何数组[2, 3]::INTEGER (仅限PG) [Op.col]: 'user.organization_id' // = 'user'.'organization_id', 使用数据库语言特定的列标识符, 本例使用 PG
范围选项
所有操作符都支持支持的范围类型查询.
请记住,提供的范围值也可以定义绑定的 inclusion/exclusion.
// 所有上述相等和不相等的操作符加上以下内容: [Op.contains]: 2 // @> '2'::integer (PG range contains element operator) [Op.contains]: [1, 2] // @> [1, 2) (PG range contains range operator) [Op.contained]: [1, 2] // <@ [1, 2) (PG range is contained by operator) [Op.overlap]: [1, 2] // && [1, 2) (PG range overlap (have points in common) operator) [Op.adjacent]: [1, 2] // -|- [1, 2) (PG range is adjacent to operator) [Op.strictLeft]: [1, 2] // << [1, 2) (PG range strictly left of operator) [Op.strictRight]: [1, 2] // >> [1, 2) (PG range strictly right of operator) [Op.noExtendRight]: [1, 2] // &< [1, 2) (PG range does not extend to the right of operator) [Op.noExtendLeft]: [1, 2] // &> [1, 2) (PG range does not extend to the left of operator)
组合
{ rank: { [Op.or]: { [Op.lt]: 1000, [Op.eq]: null } } } // rank < 1000 OR rank IS NULL { createdAt: { [Op.lt]: new Date(), [Op.gt]: new Date(new Date() - 24 * 60 * 60 * 1000) } } // createdAt < [timestamp] AND createdAt > [timestamp] { [Op.or]: [ { title: { [Op.like]: 'Boat%' } }, { description: { [Op.like]: '%boat%' } } ] } // title LIKE 'Boat%' OR description LIKE '%boat%'
关系 / 关联
// 找到所有具有至少一个 task 的 project,其中 task.state === project.state Project.findAll({ include: [{ model: Task, where: { state: Sequelize.col('project.state') } }] })
分页 / 限制
// 获取10个实例/行 Project.findAll({ limit: 10 }) // 跳过8个实例/行 Project.findAll({ offset: 8 }) // 跳过5个实例,然后取5个 Project.findAll({ offset: 5, limit: 5 })
排序
order 需要一个条目的数组来排序查询或者一个 sequelize 方法.一般来说,你将要使用任一属性的 tuple/array,并确定排序的正反方向.
Subtask.findAll({ order: [ // 将转义标题,并根据有效的方向参数列表验证DESC ['title', 'DESC'], // 将按最大值排序(age) sequelize.fn('max', sequelize.col('age')), // 将按最大顺序(age) DESC [sequelize.fn('max', sequelize.col('age')), 'DESC'], // 将按 otherfunction 排序(`col1`, 12, 'lalala') DESC [sequelize.fn('otherfunction', sequelize.col('col1'), 12, 'lalala'), 'DESC'], // 将使用模型名称作为关联的名称排序关联模型的 created_at. [Task, 'createdAt', 'DESC'], // Will order through an associated model's created_at using the model names as the associations' names. [Task, Project, 'createdAt', 'DESC'], // 将使用关联的名称由关联模型的created_at排序. ['Task', 'createdAt', 'DESC'], // Will order by a nested associated model's created_at using the names of the associations. ['Task', 'Project', 'createdAt', 'DESC'], // Will order by an associated model's created_at using an association object. (优选方法) [Subtask.associations.Task, 'createdAt', 'DESC'], // Will order by a nested associated model's created_at using association objects. (优选方法) [Subtask.associations.Task, Task.associations.Project, 'createdAt', 'DESC'], // Will order by an associated model's created_at using a simple association object. [{model: Task, as: 'Task'}, 'createdAt', 'DESC'], // 嵌套关联模型的 created_at 简单关联对象排序 [{model: Task, as: 'Task'}, {model: Project, as: 'Project'}, 'createdAt', 'DESC'] ] // 将按年龄最大值降序排列 order: sequelize.literal('max(age) DESC') // 按最年龄大值升序排列,当省略排序条件时默认是升序排列 order: sequelize.fn('max', sequelize.col('age')) // 按升序排列是省略排序条件的默认顺序 order: sequelize.col('age') // 将根据方言随机排序 (而不是 fn('RAND') 或 fn('RANDOM')) order: sequelize.random() })
count - 计算数据库中元素的出现次数
还有一种数据库对象计数的方法:
Project.count().then(c => { console.log("There are " + c + " projects!") }) Project.count({ where: {'id': {[Op.gt]: 25}} }).then(c => { console.log("There are " + c + " projects with an id greater than 25.") })
max - 获取特定表中特定属性的最大值
这里是获取属性的最大值的方法:
/* 我们假设3个具有属性年龄的对象. 第一个是10岁, 第二个是5岁, 第三个是40岁. */ Project.max('age').then(max => { // 将返回 40 }) Project.max('age', { where: { age: { [Op.lt]: 20 } } }).then(max => { // 将会是 10 })
min - 获取特定表中特定属性的最小值
这里是获取属性的最小值的方法:
/* 我们假设3个具有属性年龄的对象. 第一个是10岁, 第二个是5岁, 第三个是40岁. */ Project.min('age').then(min => { // 将返回 5 }) Project.min('age', { where: { age: { [Op.gt]: 5 } } }).then(min => { // 将会是 10 })
sum - 特定属性的值求和
为了计算表的特定列的总和,可以使用“sum”方法.
/* 我们假设3个具有属性年龄的对象. 第一个是10岁, 第二个是5岁, 第三个是40岁. */ Project.sum('age').then(sum => { // 将返回 55 }) Project.sum('age', { where: { age: { [Op.gt]: 5 } } }).then(sum => { // 将会是 50 })