模型查询 - 基础
Sequelize 提供了多种方法来帮助您查询数据库中的数据。
重要提示:要使用 Sequelize 执行可用于生产环境的查询,请确保您已阅读 事务指南。事务对于确保数据完整性和提供其他好处至关重要。
本指南将介绍如何执行标准 CRUD 查询。
简单 INSERT 查询
首先,一个简单的示例
// Create a new user
const jane = await User.create({ firstName: 'Jane', lastName: 'Doe' });
console.log("Jane's auto-generated ID:", jane.id);
The Model.create()
方法是使用 Model.build()
构建未保存的实例并使用 instance.save()
保存该实例的简写方法。
还可以定义哪些属性可以在 create
方法中设置。这在您基于用户可以填写的表单创建数据库条目时尤其有用。使用它例如允许您限制 User
模型仅设置用户名,而不设置管理员标志(即,isAdmin
)
const user = await User.create(
{
username: 'alice123',
isAdmin: true,
},
{ fields: ['username'] },
);
// let's assume the default of isAdmin is false
console.log(user.username); // 'alice123'
console.log(user.isAdmin); // false
简单 SELECT 查询
您可以使用 findAll
方法从数据库中读取整个表
// Find all users
const users = await User.findAll();
console.log(users.every(user => user instanceof User)); // true
console.log('All users:', JSON.stringify(users, null, 2));
SELECT * FROM ...
为 SELECT 查询指定属性
要仅选择某些属性,可以使用 attributes
选项
Model.findAll({
attributes: ['foo', 'bar'],
});
SELECT foo, bar FROM ...
可以使用嵌套数组对属性进行重命名
Model.findAll({
attributes: ['foo', ['bar', 'baz'], 'qux'],
});
SELECT foo, bar AS baz, qux FROM ...
您可以使用 sequelize.fn
执行聚合
Model.findAll({
attributes: ['foo', [sequelize.fn('COUNT', sequelize.col('hats')), 'n_hats'], 'bar'],
});
SELECT foo, COUNT(hats) AS n_hats, bar FROM ...
使用聚合函数时,您必须为其指定别名,才能从模型中访问它。在上面的示例中,您可以使用 instance.n_hats
获取帽子数量。
如果您只想添加聚合,列出模型的所有属性可能很麻烦
// This is a tiresome way of getting the number of hats (along with every column)
Model.findAll({
attributes: [
'id',
'foo',
'bar',
'baz',
'qux',
'hats', // We had to list all attributes...
[sequelize.fn('COUNT', sequelize.col('hats')), 'n_hats'], // To add the aggregation...
],
});
// This is shorter, and less error prone because it still works if you add / remove attributes from your model later
Model.findAll({
attributes: {
include: [[sequelize.fn('COUNT', sequelize.col('hats')), 'n_hats']],
},
});
SELECT id, foo, bar, baz, qux, hats, COUNT(hats) AS n_hats FROM ...
类似地,还可以删除一些选定的属性
Model.findAll({
attributes: { exclude: ['baz'] },
});
-- Assuming all columns are 'id', 'foo', 'bar', 'baz' and 'qux'
SELECT id, foo, bar, qux FROM ...
应用 WHERE 子句
where
选项用于过滤查询。有许多运算符可用于 where
子句,这些运算符以 Symbols 的形式从 Op
提供。
基础知识
Post.findAll({
where: {
authorId: 2,
},
});
// SELECT * FROM post WHERE authorId = 2;
请注意,没有显式传递运算符(来自 Op
),因此 Sequelize 默认假设为等式比较。上面的代码等效于
const { Op } = require('sequelize');
Post.findAll({
where: {
authorId: {
[Op.eq]: 2,
},
},
});
// SELECT * FROM post WHERE authorId = 2;
可以传递多个检查
Post.findAll({
where: {
authorId: 12,
status: 'active',
},
});
// SELECT * FROM post WHERE authorId = 12 AND status = 'active';
就像 Sequelize 在第一个示例中推断出 Op.eq
运算符一样,Sequelize 在这里推断出调用方希望对这两个检查使用 AND
。上面的代码等效于
const { Op } = require('sequelize');
Post.findAll({
where: {
[Op.and]: [{ authorId: 12 }, { status: 'active' }],
},
});
// SELECT * FROM post WHERE authorId = 12 AND status = 'active';
可以轻松执行 OR
const { Op } = require('sequelize');
Post.findAll({
where: {
[Op.or]: [{ authorId: 12 }, { authorId: 13 }],
},
});
// SELECT * FROM post WHERE authorId = 12 OR authorId = 13;
由于上面是一个涉及相同字段的 OR
,因此 Sequelize 允许您使用略有不同的结构,该结构更易读并生成相同的行为
const { Op } = require('sequelize');
Post.destroy({
where: {
authorId: {
[Op.or]: [12, 13],
},
},
});
// DELETE FROM post WHERE authorId = 12 OR authorId = 13;
运算符
Sequelize 提供了多个运算符。
const { Op } = require("sequelize");
Post.findAll({
where: {
[Op.and]: [{ a: 5 }, { b: 6 }], // (a = 5) AND (b = 6)
[Op.or]: [{ a: 5 }, { b: 6 }], // (a = 5) OR (b = 6)
someAttribute: {
// Basics
[Op.eq]: 3, // = 3
[Op.ne]: 20, // != 20
[Op.is]: null, // IS NULL
[Op.not]: true, // IS NOT TRUE
[Op.or]: [5, 6], // (someAttribute = 5) OR (someAttribute = 6)
// Using dialect specific column identifiers (PG in the following example):
[Op.col]: 'user.organization_id', // = "user"."organization_id"
// Number comparisons
[Op.gt]: 6, // > 6
[Op.gte]: 6, // >= 6
[Op.lt]: 10, // < 10
[Op.lte]: 10, // <= 10
[Op.between]: [6, 10], // BETWEEN 6 AND 10
[Op.notBetween]: [11, 15], // NOT BETWEEN 11 AND 15
// Other operators
[Op.all]: sequelize.literal('SELECT 1'), // > ALL (SELECT 1)
[Op.in]: [1, 2], // IN [1, 2]
[Op.notIn]: [1, 2], // NOT IN [1, 2]
[Op.like]: '%hat', // LIKE '%hat'
[Op.notLike]: '%hat', // NOT LIKE '%hat'
[Op.startsWith]: 'hat', // LIKE 'hat%'
[Op.endsWith]: 'hat', // LIKE '%hat'
[Op.substring]: 'hat', // LIKE '%hat%'
[Op.iLike]: '%hat', // ILIKE '%hat' (case insensitive) (PG only)
[Op.notILike]: '%hat', // NOT ILIKE '%hat' (PG only)
[Op.regexp]: '^[h|a|t]', // REGEXP/~ '^[h|a|t]' (MySQL/PG only)
[Op.notRegexp]: '^[h|a|t]', // NOT REGEXP/!~ '^[h|a|t]' (MySQL/PG only)
[Op.iRegexp]: '^[h|a|t]', // ~* '^[h|a|t]' (PG only)
[Op.notIRegexp]: '^[h|a|t]', // !~* '^[h|a|t]' (PG only)
[Op.any]: [2, 3], // ANY (ARRAY[2, 3]::INTEGER[]) (PG only)
[Op.match]: Sequelize.fn('to_tsquery', 'fat & rat') // match text search for strings 'fat' and 'rat' (PG only)
// In Postgres, Op.like/Op.iLike/Op.notLike can be combined to Op.any:
[Op.like]: { [Op.any]: ['cat', 'hat'] } // LIKE ANY (ARRAY['cat', 'hat'])
// There are more postgres-only range operators, see below
}
}
});
Op.in
的简写语法
直接将数组传递给 where
选项将隐式使用 IN
运算符
Post.findAll({
where: {
id: [1, 2, 3], // Same as using `id: { [Op.in]: [1,2,3] }`
},
});
// SELECT ... FROM "posts" AS "post" WHERE "post"."id" IN (1, 2, 3);
使用运算符进行逻辑组合
运算符 Op.and
、Op.or
和 Op.not
可用于创建任意复杂的嵌套逻辑比较。
使用 Op.and
和 Op.or
的示例
const { Op } = require("sequelize");
Foo.findAll({
where: {
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%'
}
});
使用 Op.not
的示例
Project.findAll({
where: {
name: 'Some Project',
[Op.not]: [
{ id: [1, 2, 3] },
{
description: {
[Op.like]: 'Hello%',
},
},
],
},
});
上面将生成
SELECT *
FROM `Projects`
WHERE (
`Projects`.`name` = 'Some Project'
AND NOT (
`Projects`.`id` IN (1,2,3)
AND
`Projects`.`description` LIKE 'Hello%'
)
)
使用函数(不仅仅是列)进行高级查询
如果您想要获得类似 WHERE char_length("content") = 7
的结果怎么办?
Post.findAll({
where: sequelize.where(sequelize.fn('char_length', sequelize.col('content')), 7),
});
// SELECT ... FROM "posts" AS "post" WHERE char_length("content") = 7
请注意 sequelize.fn
和 sequelize.col
方法的使用,它们应该分别用于指定 SQL 函数调用和表列。这些方法应用于指定 SQL 函数调用和表列,而不应该传递普通字符串(如 char_length(content)
),因为 Sequelize 需要以不同的方式处理这种情况(例如,使用其他符号转义方法)。
如果您需要更复杂的内容怎么办?
Post.findAll({
where: {
[Op.or]: [
sequelize.where(sequelize.fn('char_length', sequelize.col('content')), 7),
{
content: {
[Op.like]: 'Hello%',
},
},
{
[Op.and]: [
{ status: 'draft' },
sequelize.where(sequelize.fn('char_length', sequelize.col('content')), {
[Op.gt]: 10,
}),
],
},
],
},
});
上面将生成以下 SQL
SELECT
...
FROM "posts" AS "post"
WHERE (
char_length("content") = 7
OR
"post"."content" LIKE 'Hello%'
OR (
"post"."status" = 'draft'
AND
char_length("content") > 10
)
)
仅限 PostgreSQL 的范围运算符
可以使用所有受支持的运算符来查询范围类型。
请记住,提供的范围值还可以 定义边界包含/排除。
[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)
已弃用:运算符别名
在 Sequelize v4 中,可以使用字符串来指定运算符,而不必使用 Symbols。现在已弃用此方法,并且强烈建议不要使用,并且可能在下一个主要版本中删除。如果您确实需要它,可以在 Sequelize 构造函数中传递 operatorAliases
选项。
例如
const { Sequelize, Op } = require('sequelize');
const sequelize = new Sequelize('sqlite::memory:', {
operatorsAliases: {
$gt: Op.gt,
},
});
// Now we can use `$gt` instead of `[Op.gt]` in where clauses:
Foo.findAll({
where: {
$gt: 6, // Works like using [Op.gt]
},
});
简单 UPDATE 查询
更新查询也接受 where
选项,就像上面显示的读取查询一样。
// Change everyone without a last name to "Doe"
await User.update(
{ lastName: 'Doe' },
{
where: {
lastName: null,
},
},
);
简单 DELETE 查询
删除查询也接受 where
选项,就像上面显示的读取查询一样。
// Delete everyone named "Jane"
await User.destroy({
where: {
firstName: 'Jane',
},
});
要销毁所有内容,可以使用 TRUNCATE
SQL
// Truncate the table
await User.destroy({
truncate: true,
});
批量创建
Sequelize 提供了 Model.bulkCreate
方法,允许您一次创建多个记录,只需执行一个查询。
Model.bulkCreate
的用法与 Model.create
非常类似,接收的是对象数组,而不是单个对象。
const captains = await Captain.bulkCreate([{ name: 'Jack Sparrow' }, { name: 'Davy Jones' }]);
console.log(captains.length); // 2
console.log(captains[0] instanceof Captain); // true
console.log(captains[0].name); // 'Jack Sparrow'
console.log(captains[0].id); // 1 // (or another auto-generated value)
但是,默认情况下,bulkCreate
不会对要创建的每个对象运行验证(而 create
会运行)。要使 bulkCreate
也运行这些验证,您必须传递 validate: true
选项。这会降低性能。使用示例
const Foo = sequelize.define('foo', {
name: {
type: DataTypes.TEXT,
validate: {
len: [4, 6],
},
},
});
// This will not throw an error, both instances will be created
await Foo.bulkCreate([{ name: 'abc123' }, { name: 'name too long' }]);
// This will throw an error, nothing will be created
await Foo.bulkCreate([{ name: 'abc123' }, { name: 'name too long' }], {
validate: true,
});
如果您要直接从用户那里接收值,限制要实际插入的列可能会有所帮助。为了支持这一点,bulkCreate()
接受一个 fields
选项,这是一个定义必须考虑哪些字段的数组(其他字段将被忽略)。
await User.bulkCreate([{ username: 'foo' }, { username: 'bar', admin: true }], {
fields: ['username'],
});
// Neither foo nor bar are admins.
排序和分组
Sequelize 提供了 order
和 group
选项,用于使用 ORDER BY
和 GROUP BY
。
排序
order
选项接收一个用于对查询进行排序的项目数组或 Sequelize 方法。这些项目本身是 [column, direction]
形式的数组。列将被正确转义,并且方向将在有效方向的白名单中进行检查(例如 ASC
、DESC
、NULLS FIRST
等)。
Subtask.findAll({
order: [
// Will escape title and validate DESC against a list of valid direction parameters
['title', 'DESC'],
// Will order by max(age)
sequelize.fn('max', sequelize.col('age')),
// Will order by max(age) DESC
[sequelize.fn('max', sequelize.col('age')), 'DESC'],
// Will order by otherfunction(`col1`, 12, 'lalala') DESC
[sequelize.fn('otherfunction', sequelize.col('col1'), 12, 'lalala'), 'DESC'],
// Will order an associated model's createdAt using the model name as the association's name.
[Task, 'createdAt', 'DESC'],
// Will order through an associated model's createdAt using the model names as the associations' names.
[Task, Project, 'createdAt', 'DESC'],
// Will order by an associated model's createdAt using the name of the association.
['Task', 'createdAt', 'DESC'],
// Will order by a nested associated model's createdAt using the names of the associations.
['Task', 'Project', 'createdAt', 'DESC'],
// Will order by an associated model's createdAt using an association object. (preferred method)
[Subtask.associations.Task, 'createdAt', 'DESC'],
// Will order by a nested associated model's createdAt using association objects. (preferred method)
[Subtask.associations.Task, Task.associations.Project, 'createdAt', 'DESC'],
// Will order by an associated model's createdAt using a simple association object.
[{ model: Task, as: 'Task' }, 'createdAt', 'DESC'],
// Will order by a nested associated model's createdAt simple association objects.
[{ model: Task, as: 'Task' }, { model: Project, as: 'Project' }, 'createdAt', 'DESC'],
],
// Will order by max age descending
order: sequelize.literal('max(age) DESC'),
// Will order by max age ascending assuming ascending is the default order when direction is omitted
order: sequelize.fn('max', sequelize.col('age')),
// Will order by age ascending assuming ascending is the default order when direction is omitted
order: sequelize.col('age'),
// Will order randomly based on the dialect (instead of fn('RAND') or fn('RANDOM'))
order: sequelize.random(),
});
Foo.findOne({
order: [
// will return `name`
['name'],
// will return `username` DESC
['username', 'DESC'],
// will return max(`age`)
sequelize.fn('max', sequelize.col('age')),
// will return max(`age`) DESC
[sequelize.fn('max', sequelize.col('age')), 'DESC'],
// will return otherfunction(`col1`, 12, 'lalala') DESC
[sequelize.fn('otherfunction', sequelize.col('col1'), 12, 'lalala'), 'DESC'],
// will return otherfunction(awesomefunction(`col`)) DESC, This nesting is potentially infinite!
[sequelize.fn('otherfunction', sequelize.fn('awesomefunction', sequelize.col('col'))), 'DESC'],
],
});
回顾一下,order 数组的元素可以是以下内容
- 一个字符串(将被自动引用)
- 一个数组,其第一个元素将被引用,第二个元素将被逐字追加
- 一个包含
raw
字段的对象raw
的内容将被逐字添加,不会被引用- 其他所有内容都会被忽略,如果
raw
未设置,查询将失败
- 调用
Sequelize.fn
(将在 SQL 中生成函数调用) - 调用
Sequelize.col
(将引用列名)
分组
分组和排序的语法相同,除了分组不接受数组的最后一个参数作为方向(没有 ASC
、DESC
、NULLS FIRST
等)。
你也可以直接将字符串传递给 group
,它将被直接(逐字)包含在生成的 SQL 中。谨慎使用,不要与用户生成的内容一起使用。
Project.findAll({ group: 'name' });
// yields 'GROUP BY name'
限制和分页
limit
和 offset
选项允许你使用限制/分页
// Fetch 10 instances/rows
Project.findAll({ limit: 10 });
// Skip 8 instances/rows
Project.findAll({ offset: 8 });
// Skip 5 instances and fetch the 5 after that
Project.findAll({ offset: 5, limit: 5 });
通常这些与 order
选项一起使用。
实用方法
Sequelize 还提供了一些实用方法。
count
count
方法简单地统计数据库中元素的出现次数。
console.log(`There are ${await Project.count()} projects`);
const amount = await Project.count({
where: {
id: {
[Op.gt]: 25,
},
},
});
console.log(`There are ${amount} projects with an id greater than 25`);
max
、min
和 sum
Sequelize 还提供了 max
、min
和 sum
方便方法。
假设我们有三个用户,他们的年龄分别为 10、5 和 40。
await User.max('age'); // 40
await User.max('age', { where: { age: { [Op.lt]: 20 } } }); // 10
await User.min('age'); // 5
await User.min('age', { where: { age: { [Op.gt]: 5 } } }); // 10
await User.sum('age'); // 55
await User.sum('age', { where: { age: { [Op.gt]: 5 } } }); // 50
increment
、decrement
Sequelize 还提供了 increment
方便方法。
假设我们有一个用户,他的年龄是 10。
await User.increment({ age: 5 }, { where: { id: 1 } }); // Will increase age to 15
await User.increment({ age: -5 }, { where: { id: 1 } }); // Will decrease age to 5