跳至主要内容
版本: v6 - 稳定版

模型查询 - 基础

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.andOp.orOp.not 可用于创建任意复杂的嵌套逻辑比较。

使用 Op.andOp.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.fnsequelize.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 提供了 ordergroup 选项,用于使用 ORDER BYGROUP BY

排序

order 选项接收一个用于对查询进行排序的项目数组或 Sequelize 方法。这些项目本身是 [column, direction] 形式的数组。列将被正确转义,并且方向将在有效方向的白名单中进行检查(例如 ASCDESCNULLS 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(将引用列名)

分组

分组和排序的语法相同,除了分组不接受数组的最后一个参数作为方向(没有 ASCDESCNULLS FIRST 等)。

你也可以直接将字符串传递给 group,它将被直接(逐字)包含在生成的 SQL 中。谨慎使用,不要与用户生成的内容一起使用。

Project.findAll({ group: 'name' });
// yields 'GROUP BY name'

限制和分页

limitoffset 选项允许你使用限制/分页

// 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`);

maxminsum

Sequelize 还提供了 maxminsum 方便方法。

假设我们有三个用户,他们的年龄分别为 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

incrementdecrement

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