模型查詢 - 基礎
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.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 函式呼叫和表格欄位。 這些方法應該用於代替傳遞純字串(例如 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 提供了 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