跳至主要內容
版本:v6 - stable

模型查詢 - 基礎

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

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 子句可以使用很多運算子,這些運算子可以從 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 函式呼叫和表格欄位。 這些方法應該用於代替傳遞純字串(例如 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
)
)

僅限 Postgres 的範圍運算子

範圍類型可以使用所有支援的運算子進行查詢。

請記住,提供的範圍值也可以定義界限包含/排除

[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 中,可以指定字串來參照運算子,而不是使用符號。 這現在已被淘汰且強烈不建議使用,並可能會在下一個主要版本中移除。 如果您真的需要,可以在 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