如果你覺得Sequelize的文檔有點(diǎn)多、雜,不方便看,可以看看這篇。
在使用NodeJS來(lái)關(guān)系型操作數(shù)據(jù)庫(kù)時(shí),為了方便,通常都會(huì)選擇一個(gè)合適的ORM(Object Relationship Model)框架。畢竟直接操作SQL比較繁瑣,通過(guò)ORM框架,我們可以使用面向?qū)ο蟮姆绞絹?lái)操作表。NodeJS社區(qū)有很多的ORM框架,我比較喜歡Sequelize,它功能豐富,可以非常方便的進(jìn)行連表查詢。
這篇文章我們就來(lái)看看,Sequelize是如何在SQL之上進(jìn)行抽象、封裝,從而提高開發(fā)效率的。
安裝
這篇文章主要使用MySQL、Sequelize、co來(lái)進(jìn)行介紹。安裝非常簡(jiǎn)單:
$ npm install --save co
$ npm install --save sequelize
$ npm install --save mysql
代碼模板如下:
var Sequelize = require('sequelize');
var co = require('co');
co(function* () {
// code here
}).catch(function(e) {
console.log(e);
});
基本上,Sequelize的操作都會(huì)返回一個(gè)Promise,在co的框架里面可以直接進(jìn)行yield,非常方便。
建立數(shù)據(jù)庫(kù)連接
var sequelize = new Sequelize(
'sample', // 數(shù)據(jù)庫(kù)名
'root', // 用戶名
'zuki', // 用戶密碼
{
'dialect': 'mysql', // 數(shù)據(jù)庫(kù)使用mysql
'host': 'localhost', // 數(shù)據(jù)庫(kù)服務(wù)器ip
'port': 3306, // 數(shù)據(jù)庫(kù)服務(wù)器端口
'define': {
// 字段以下劃線(_)來(lái)分割(默認(rèn)是駝峰命名風(fēng)格)
'underscored': true
}
}
);
定義單張表
Sequelize:
var User = sequelize.define(
// 默認(rèn)表名(一般這里寫單數(shù)),生成時(shí)會(huì)自動(dòng)轉(zhuǎn)換成復(fù)數(shù)形式
// 這個(gè)值還會(huì)作為訪問(wèn)模型相關(guān)的模型時(shí)的屬性名,所以建議用小寫形式
'user',
// 字段定義(主鍵、created_at、updated_at默認(rèn)包含,不用特殊定義)
{
'emp_id': {
'type': Sequelize.CHAR(10), // 字段類型
'allowNull': false, // 是否允許為NULL
'unique': true // 字段是否UNIQUE
},
'nick': {
'type': Sequelize.CHAR(10),
'allowNull': false
},
'department': {
'type': Sequelize.STRING(64),
'allowNull': true
}
}
);
SQL:
CREATE TABLE IF NOT EXISTS `users` (
`id` INTEGER NOT NULL auto_increment ,
`emp_id` CHAR(10) NOT NULL UNIQUE,
`nick` CHAR(10) NOT NULL,
`department` VARCHAR(64),
`created_at` DATETIME NOT NULL,
`updated_at` DATETIME NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB;
幾點(diǎn)說(shuō)明:
建表
SQL會(huì)自動(dòng)執(zhí)行的意思是你主動(dòng)調(diào)用sync的時(shí)候。類似這樣:User.sync({force: true});(加force:true,會(huì)先刪掉表后再建表)。我們也可以先定義好表結(jié)構(gòu),再來(lái)定義Sequelize模型,這時(shí)可以不用sync。兩者在定義階段沒有什么關(guān)系,直到我們真正開始操作模型時(shí),才會(huì)觸及到表的操作,但是我們當(dāng)然還是要盡量保證模型和表的同步(可以借助一些migration工具)。自動(dòng)建表功能有風(fēng)險(xiǎn),使用需謹(jǐn)慎。所有數(shù)據(jù)類型,請(qǐng)參考文檔數(shù)據(jù)類型。
模型還可以定義虛擬屬性、類方法、實(shí)例方法,請(qǐng)參考文檔:模型定義
其他一些特殊定義如下所示:
var User = sequelize.define(
'user',
{
'emp_id': {
'type': Sequelize.CHAR(10), // 字段類型
'allowNull': false, // 是否允許為NULL
'unique': true // 字段是否UNIQUE
},
'nick': {
'type': Sequelize.CHAR(10),
'allowNull': false
},
'department': {
'type': Sequelize.STRING(64),
'allowNull': true
}
},
{
// 自定義表名
'freezeTableName': true,
'tableName': 'xyz_users',
// 是否需要增加createdAt、updatedAt、deletedAt字段
'timestamps': true,
// 不需要createdAt字段
'createdAt': false,
// 將updatedAt字段改個(gè)名
'updatedAt': 'utime'
// 將deletedAt字段改名
// 同時(shí)需要設(shè)置paranoid為true(此種模式下,刪除數(shù)據(jù)時(shí)不會(huì)進(jìn)行物理刪除,而是設(shè)置deletedAt為當(dāng)前時(shí)間
'deletedAt': 'dtime',
'paranoid': true
}
);
單表增刪改查
通過(guò)Sequelize獲取的模型對(duì)象都是一個(gè)DAO(Data Access Object)對(duì)象,這些對(duì)象會(huì)擁有許多操作數(shù)據(jù)庫(kù)表的實(shí)例對(duì)象方法(比如:save、update、destroy等),需要獲取“干凈”的JSON對(duì)象可以調(diào)用get({'plain': true})。
通過(guò)模型的類方法可以獲取模型對(duì)象(比如:findById、findAll等)。
增
Sequelize:
// 方法1:build后對(duì)象只存在于內(nèi)存中,調(diào)用save后才操作db
var user = User.build({
'emp_id': '1',
'nick': '小紅',
'department': '技術(shù)部'
});
user = yield user.save();
console.log(user.get({'plain': true}));
// 方法2:直接操作db
var user = yield User.create({
'emp_id': '2',
'nick': '小明',
'department': '技術(shù)部'
});
console.log(user.get({'plain': true}));
SQL:
INSERT INTO `users`
(`id`, `emp_id`, `nick`, `department`, `updated_at`, `created_at`)
VALUES
(DEFAULT, '1', '小紅', '技術(shù)部', '2015-11-02 14:49:54', '2015-11-02 14:49:54');
Sequelize會(huì)為主鍵id設(shè)置DEFAULT值來(lái)讓數(shù)據(jù)庫(kù)產(chǎn)生自增值,還將當(dāng)前時(shí)間設(shè)置成了created_at和updated_at字段,非常方便。
改
Sequelize:
// 方法1:操作對(duì)象屬性(不會(huì)操作db),調(diào)用save后操作db
user.nick = '小白';
user = yield user.save();
console.log(user.get({'plain': true}));
// 方法2:直接update操作db
user = yield user.update({
'nick': '小白白'
});
console.log(user.get({'plain': true}));
SQL:
UPDATE `users`
SET `nick` = '小白白', `updated_at` = '2015-11-02 15:00:04'
WHERE `id` = 1;
更新操作時(shí),Sequelize將將當(dāng)前時(shí)間設(shè)置成了updated_at,非常方便。
如果想限制更新屬性的白名單,可以這樣寫:
// 方法1
user.emp_id = '33';
user.nick = '小白';
user = yield user.save({'fields': ['nick']});
// 方法2
user = yield user.update(
{'emp_id': '33', 'nick': '小白'},
{'fields': ['nick']}
});
這樣就只會(huì)更新nick字段,而emp_id會(huì)被忽略。這種方法在對(duì)表單提交過(guò)來(lái)的一大推數(shù)據(jù)中只更新某些屬性的時(shí)候比較有用。
刪
Sequelize:
yield user.destroy();
SQL:
DELETE FROM `users` WHERE `id` = 1;
這里有個(gè)特殊的地方是,如果我們開啟了paranoid(偏執(zhí))模式,destroy的時(shí)候不會(huì)執(zhí)行DELETE語(yǔ)句,而是執(zhí)行一個(gè)UPDATE語(yǔ)句將deleted_at字段設(shè)置為當(dāng)前時(shí)間(一開始此字段值為NULL)。我們可以使用user.destroy({force: true})來(lái)強(qiáng)制刪除,從而執(zhí)行DELETE語(yǔ)句進(jìn)行物理刪除。
查
查全部
Sequelize:
var users = yield User.findAll();
console.log(users);
SQL:
SELECT `id`, `emp_id`, `nick`, `department`, `created_at`, `updated_at` FROM `users`;
限制字段
Sequelize:
var users = yield User.findAll({
'attributes': ['emp_id', 'nick']
});
console.log(users);
SQL:
SELECT `emp_id`, `nick` FROM `users`;
字段重命名
Sequelize:
var users = yield User.findAll({
'attributes': [
'emp_id', ['nick', 'user_nick']
]
});
console.log(users);
SQL:
SELECT `emp_id`, `nick` AS `user_nick` FROM `users`;
where子句
Sequelize的where配置項(xiàng)基本上完全支持了SQL的where子句的功能,非常強(qiáng)大。我們一步步來(lái)進(jìn)行介紹。
基本條件
Sequelize:
var users = yield User.findAll({
'where': {
'id': [1, 2, 3],
'nick': 'a',
'department': null
}
});
console.log(users);
SQL:
SELECT `id`, `emp_id`, `nick`, `department`, `created_at`, `updated_at`
FROM `users` AS `user`
WHERE
`user`.`id` IN (1, 2, 3) AND
`user`.`nick`='a' AND
`user`.`department` IS NULL;
可以看到,k: v被轉(zhuǎn)換成了k = v,同時(shí)一個(gè)對(duì)象的多個(gè)k: v對(duì)被轉(zhuǎn)換成了AND條件,即:k1: v1, k2: v2轉(zhuǎn)換為k1 = v1 AND k2 = v2。
這里有2個(gè)要點(diǎn):
如果v是
null,會(huì)轉(zhuǎn)換為IS NULL(因?yàn)?code>SQL沒有= NULL
這種語(yǔ)法)如果v是數(shù)組,會(huì)轉(zhuǎn)換為
IN條件(因?yàn)?code>SQL沒有=[1,2,3]這種語(yǔ)法,況且也沒數(shù)組這種類型)
操作符
操作符是對(duì)某個(gè)字段的進(jìn)一步約束,可以有多個(gè)(對(duì)同一個(gè)字段的多個(gè)操作符會(huì)被轉(zhuǎn)化為AND)。
Sequelize:
var users = yield User.findAll({
'where': {
'id': {
'$eq': 1, // id = 1
'$ne': 2, // id != 2
'$gt': 6, // id > 6
'$gte': 6, // id >= 6
'$lt': 10, // id < 10
'$lte': 10, // id <= 10
'$between': [6, 10], // id BETWEEN 6 AND 10
'$notBetween': [11, 15], // id NOT BETWEEN 11 AND 15
'$in': [1, 2], // id IN (1, 2)
'$notIn': [3, 4] // id NOT IN (3, 4)
},
'nick': {
'$like': '%a%', // nick LIKE '%a%'
'$notLike': '%a' // nick NOT LIKE '%a'
},
'updated_at': {
'$eq': null, // updated_at IS NULL
'$ne': null // created_at IS NOT NULL
}
}
});
SQL:
SELECT `id`, `emp_id`, `nick`, `department`, `created_at`, `updated_at`
FROM `users` AS `user`
WHERE
(
`user`.`id` = 1 AND
`user`.`id` != 2 AND
`user`.`id` > 6 AND
`user`.`id` >= 6 AND
`user`.`id` < 10 AND
`user`.`id` <= 10 AND
`user`.`id` BETWEEN 6 AND 10 AND
`user`.`id` NOT BETWEEN 11 AND 15 AND
`user`.`id` IN (1, 2) AND
`user`.`id` NOT IN (3, 4)
)
AND
(
`user`.`nick` LIKE '%a%' AND
`user`.`nick` NOT LIKE '%a'
)
AND
(
`user`.`updated_at` IS NULL AND
`user`.`updated_at` IS NOT NULL
);
這里我們發(fā)現(xiàn),其實(shí)相等條件k: v這種寫法是操作符寫法k: {$eq: v}的簡(jiǎn)寫。而要實(shí)現(xiàn)不等條件就必須使用操作符寫法k: {$ne: v}。
條件
上面我們說(shuō)的條件查詢,都是AND查詢,Sequelize同時(shí)也支持OR、NOT、甚至多種條件的聯(lián)合查詢。
AND條件
Sequelize:
var users = yield User.findAll({
'where': {
'$and': [
{'id': [1, 2]},
{'nick': null}
]
}
});
SQL:
SELECT `id`, `emp_id`, `nick`, `department`, `created_at`, `updated_at`
FROM `users` AS `user`
WHERE
(
`user`.`id` IN (1, 2) AND
`user`.`nick` IS NULL
);
OR條件
Sequelize:
var users = yield User.findAll({
'where': {
'$or': [
{'id': [1, 2]},
{'nick': null}
]
}
});
SQL:
SELECT `id`, `emp_id`, `nick`, `department`, `created_at`, `updated_at`
FROM `users` AS `user`
WHERE
(
`user`.`id` IN (1, 2) OR
`user`.`nick` IS NULL
);
NOT條件
Sequelize:
var users = yield User.findAll({
'where': {
'$not': [
{'id': [1, 2]},
{'nick': null}
]
}
});
SQL:
SELECT `id`, `emp_id`, `nick`, `department`, `created_at`, `updated_at`
FROM `users` AS `user`
WHERE
NOT (
`user`.`id` IN (1, 2) AND
`user`.`nick` IS NULL
);
轉(zhuǎn)換規(guī)則
我們這里做個(gè)總結(jié)。Sequelize對(duì)where配置的轉(zhuǎn)換規(guī)則的偽代碼大概如下:
function translate(where) {
for (k, v of where) {
if (k == 表字段) {
// 先統(tǒng)一轉(zhuǎn)為操作符形式
if (v == 基本值) { // k: 'xxx'
v = {'$eq': v};
}
if (v == 數(shù)組) { // k: [1, 2, 3]
v = {'$in': v};
}
// 操作符轉(zhuǎn)換
for (opk, opv of v) {
// op將opk轉(zhuǎn)換對(duì)應(yīng)的SQL表示
=> k + op(opk, opv) + AND;
}
}
// 邏輯操作符處理
if (k == '$and') {
for (item in v) {
=> translate(item) + AND;
}
}
if (k == '$or') {
for (item in v) {
=> translate(item) + OR;
}
}
if (k == '$not') {
NOT +
for (item in v) {
=> translate(item) + AND;
}
}
}
function op(opk, opv) {
switch (opk) {
case $eq => ('=' + opv) || 'IS NULL';
case $ne => ('!=' + opv) || 'IS NOT NULL';
case $gt => '>' + opv;
case $lt => '<' + opv;
case $gte => '>=' + opv;
case $lte => '<=' + opv;
case $between => 'BETWEEN ' + opv[0] + ' AND ' + opv[1];
case $notBetween => 'NOT BETWEEN ' + opv[0] + ' AND ' + opv[1];
case $in => 'IN (' + opv.join(',') + ')';
case $notIn => 'NOT IN (' + opv.join(',') + ')';
case $like => 'LIKE ' + opv;
case $notLike => 'NOT LIKE ' + opv;
}
}
}
我們看一個(gè)復(fù)雜例子,基本上就是按上述流程來(lái)進(jìn)行轉(zhuǎn)換。
Sequelize:
var users = yield User.findAll({
'where': {
'id': [3, 4],
'$not': [
{
'id': {
'$in': [1, 2]
}
},
{
'$or': [
{'id': [1, 2]},
{'nick': null}
]
}
],
'$and': [
{'id': [1, 2]},
{'nick': null}
],
'$or': [
{'id': [1, 2]},
{'nick': null}
]
}
});
SQL:
SELECT `id`, `emp_id`, `nick`, `department`, `created_at`, `updated_at`
FROM `users` AS `user`
WHERE
`user`.`id` IN (3, 4)
AND
NOT
(
`user`.`id` IN (1, 2)
AND
(`user`.`id` IN (1, 2) OR `user`.`nick` IS NULL)
)
AND
(
`user`.`id` IN (1, 2) AND `user`.`nick` IS NULL
)
AND
(
`user`.`id` IN (1, 2) OR `user`.`nick` IS NULL
);
排序
Sequelize:
var users = yield User.findAll({
'order': [
['id', 'DESC'],
['nick']
]
});
SQL:
SELECT `id`, `emp_id`, `nick`, `department`, `created_at`, `updated_at`
FROM `users` AS `user`
ORDER BY `user`.`id` DESC, `user`.`nick`;
分頁(yè)
Sequelize:
var countPerPage = 20, currentPage = 5;
var users = yield User.findAll({
'limit': countPerPage, // 每頁(yè)多少條
'offset': countPerPage * (currentPage - 1) // 跳過(guò)多少條
});
SQL:
SELECT `id`, `emp_id`, `nick`, `department`, `created_at`, `updated_at`
FROM `users` AS `user`
LIMIT 80, 20;
其他查詢方法
查詢一條數(shù)據(jù)
Sequelize:
user = yield User.findById(1);
user = yield User.findOne({
'where': {'nick': 'a'}
});
SQL:
SELECT `id`, `emp_id`, `nick`, `department`, `created_at`, `updated_at`
FROM `users` AS `user`
WHERE `user`.`id` = 1 LIMIT 1;
SELECT `id`, `emp_id`, `nick`, `department`, `created_at`, `updated_at`
FROM `users` AS `user`
WHERE `user`.`nick` = 'a' LIMIT 1;
查詢并獲取數(shù)量
Sequelize:
var result = yield User.findAndCountAll({
'limit': 20,
'offset': 0
});
console.log(result);
SQL:
SELECT count(*) AS `count` FROM `users` AS `user`;
SELECT `id`, `emp_id`, `nick`, `department`, `created_at`, `updated_at`
FROM `users` AS `user`
LIMIT 20;
這個(gè)方法會(huì)執(zhí)行2個(gè)SQL,返回的result對(duì)象將包含2個(gè)字段:result.count是數(shù)據(jù)總數(shù),result.rows是符合查詢條件的所有數(shù)據(jù)。
批量操作
插入
Sequelize:
var users = yield User.bulkCreate(
[
{'emp_id': 'a', 'nick': 'a'},
{'emp_id': 'b', 'nick': 'b'},
{'emp_id': 'c', 'nick': 'c'}
]
);
SQL:
INSERT INTO `users`
(`id`,`emp_id`,`nick`,`created_at`,`updated_at`)
VALUES
(NULL,'a','a','2015-11-03 02:43:30','2015-11-03 02:43:30'),
(NULL,'b','b','2015-11-03 02:43:30','2015-11-03 02:43:30'),
(NULL,'c','c','2015-11-03 02:43:30','2015-11-03 02:43:30');
這里需要注意,返回的users數(shù)組里面每個(gè)對(duì)象的id值會(huì)是null。如果需要id值,可以重新取下數(shù)據(jù)。
更新
Sequelize:
var affectedRows = yield User.update(
{'nick': 'hhhh'},
{
'where': {
'id': [2, 3, 4]
}
}
);
SQL:
UPDATE `users`
SET `nick`='hhhh',`updated_at`='2015-11-03 02:51:05'
WHERE `id` IN (2, 3, 4);
這里返回的affectedRows其實(shí)是一個(gè)數(shù)組,里面只有一個(gè)元素,表示更新的數(shù)據(jù)條數(shù)(看起來(lái)像是Sequelize的一個(gè)bug)。
刪除
Sequelize:
var affectedRows = yield User.destroy({
'where': {'id': [2, 3, 4]}
});
SQL:
DELETE FROM `users` WHERE `id` IN (2, 3, 4);
這里返回的affectedRows是一個(gè)數(shù)字,表示刪除的數(shù)據(jù)條數(shù)。
關(guān)系
關(guān)系一般有三種:一對(duì)一、一對(duì)多、多對(duì)多。Sequelize提供了清晰易用的接口來(lái)定義關(guān)系、進(jìn)行表間的操作。
當(dāng)說(shuō)到關(guān)系查詢時(shí),一般會(huì)需要獲取多張表的數(shù)據(jù)。有建議用連表查詢join的,有不建議的。我的看法是,join查詢這種黑科技在數(shù)據(jù)量小的情況下可以使用,基本沒有什么影響,數(shù)據(jù)量大的時(shí)候,join的性能可能會(huì)是硬傷,應(yīng)該盡量避免,可以分別根據(jù)索引取單表數(shù)據(jù)然后在應(yīng)用層對(duì)數(shù)據(jù)進(jìn)行join、merge。當(dāng)然,查詢時(shí)一定要分頁(yè),不要findAll。
一對(duì)一
模型定義
Sequelize:
var User = sequelize.define('user',
{
'emp_id': {
'type': Sequelize.CHAR(10),
'allowNull': false,
'unique': true
}
}
);
var Account = sequelize.define('account',
{
'email': {
'type': Sequelize.CHAR(20),
'allowNull': false
}
}
);
/*
* User的實(shí)例對(duì)象將擁有g(shù)etAccount、setAccount、addAccount方法
*/
User.hasOne(Account);
/*
* Account的實(shí)例對(duì)象將擁有g(shù)etUser、setUser、addUser方法
*/
Account.belongsTo(User);
SQL:
CREATE TABLE IF NOT EXISTS `users` (
`id` INTEGER NOT NULL auto_increment ,
`emp_id` CHAR(10) NOT NULL UNIQUE,
`created_at` DATETIME NOT NULL,
`updated_at` DATETIME NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB;
CREATE TABLE IF NOT EXISTS `accounts` (
`id` INTEGER NOT NULL auto_increment ,
`email` CHAR(20) NOT NULL,
`created_at` DATETIME NOT NULL,
`updated_at` DATETIME NOT NULL,
`user_id` INTEGER,
PRIMARY KEY (`id`),
FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE SET NULL ON UPDATE CASCADE
) ENGINE=InnoDB;
可以看到,這種關(guān)系中外鍵user_id加在了Account上。另外,Sequelize還給我們生成了外鍵約束。
一般來(lái)說(shuō),外鍵約束在有些自己定制的數(shù)據(jù)庫(kù)系統(tǒng)里面是禁止的,因?yàn)闀?huì)帶來(lái)一些性能問(wèn)題。所以,建表的SQL一般就去掉約束,同時(shí)給外鍵加一個(gè)索引(加速查詢),數(shù)據(jù)的一致性就靠應(yīng)用層來(lái)保證了。
關(guān)系操作
增
Sequelize:
var user = yield User.create({'emp_id': '1'});
var account = user.createAccount({'email': 'a'});
console.log(account.get({'plain': true}));
SQL:
INSERT INTO `users`
(`id`,`emp_id`,`updated_at`,`created_at`)
VALUES
(DEFAULT,'1','2015-11-03 06:24:53','2015-11-03 06:24:53');
INSERT INTO `accounts`
(`id`,`email`,`user_id`,`updated_at`,`created_at`)
VALUES
(DEFAULT,'a',1,'2015-11-03 06:24:53','2015-11-03 06:24:53');
SQL執(zhí)行邏輯是:
- 使用對(duì)應(yīng)的的
user_id作為外鍵在accounts表里插入一條數(shù)據(jù)。
改
Sequelize:
var anotherAccount = yield Account.create({'email': 'b'});
console.log(anotherAccount);
anotherAccount = yield user.setAccount(anotherAccount);
console.log(anotherAccount);
SQL:
INSERT INTO `accounts`
(`id`,`email`,`updated_at`,`created_at`)
VALUES
(DEFAULT,'b','2015-11-03 06:37:14','2015-11-03 06:37:14');
SELECT `id`, `email`, `created_at`, `updated_at`, `user_id`
FROM `accounts` AS `account` WHERE (`account`.`user_id` = 1);
UPDATE `accounts` SET `user_id`=NULL,`updated_at`='2015-11-03 06:37:14' WHERE `id` = 1;
UPDATE `accounts` SET `user_id`=1,`updated_at`='2015-11-03 06:37:14' WHERE `id` = 2;
SQL執(zhí)行邏輯是:
插入一條
account數(shù)據(jù),此時(shí)外鍵user_id是空的,還沒有關(guān)聯(lián)user找出當(dāng)前
user所關(guān)聯(lián)的account并將其user_id置為`NUL(為了保證一對(duì)一關(guān)系)設(shè)置新的
acount的外鍵user_id為user的屬性id,生成關(guān)系
刪
Sequelize:
yield user.setAccount(null);
SQL:
SELECT `id`, `email`, `created_at`, `updated_at`, `user_id`
FROM `accounts` AS `account`
WHERE (`account`.`user_id` = 1);
UPDATE `accounts`
SET `user_id`=NULL,`updated_at`='2015-11-04 00:11:35'
WHERE `id` = 1;
這里的刪除實(shí)際上只是“切斷”關(guān)系,并不會(huì)真正的物理刪除記錄。
SQL執(zhí)行邏輯是:
找出
user所關(guān)聯(lián)的account數(shù)據(jù)將其外鍵
user_id設(shè)置為NULL,完成關(guān)系的“切斷”
查
Sequelize:
var account = yield user.getAccount();
console.log(account);
SQL:
SELECT `id`, `email`, `created_at`, `updated_at`, `user_id`
FROM `accounts` AS `account`
WHERE (`account`.`user_id` = 1);
這里就是調(diào)用user的getAccount方法,根據(jù)外鍵來(lái)獲取對(duì)應(yīng)的account。
但是其實(shí)我們用面向?qū)ο蟮乃季S來(lái)思考應(yīng)該是獲取user的時(shí)候就能通過(guò)user.account的方式來(lái)訪問(wèn)account對(duì)象。這可以通過(guò)Sequelize的eager loading(急加載,和懶加載相反)來(lái)實(shí)現(xiàn)。
eager loading的含義是說(shuō),取一個(gè)模型的時(shí)候,同時(shí)也把相關(guān)的模型數(shù)據(jù)也給我取過(guò)來(lái)(我很著急,不能按默認(rèn)那種取一個(gè)模型就取一個(gè)模型的方式,我還要更多)。方法如下:
Sequelize:
var user = yield User.findById(1, {
'include': [Account]
});
console.log(user.get({'plain': true}));
/*
* 輸出類似:
{ id: 1,
emp_id: '1',
created_at: Tue Nov 03 2015 15:25:27 GMT+0800 (CST),
updated_at: Tue Nov 03 2015 15:25:27 GMT+0800 (CST),
account:
{ id: 2,
email: 'b',
created_at: Tue Nov 03 2015 15:25:27 GMT+0800 (CST),
updated_at: Tue Nov 03 2015 15:25:27 GMT+0800 (CST),
user_id: 1 } }
*/
SQL:
SELECT `user`.`id`, `user`.`emp_id`, `user`.`created_at`, `user`.`updated_at`, `account`.`id` AS `account.id`, `account`.`email` AS `account.email`, `account`.`created_at` AS `account.created_at`, `account`.`updated_at` AS `account.updated_at`, `account`.`user_id` AS `account.user_id`
FROM `users` AS `user` LEFT OUTER JOIN `accounts` AS `account`
ON `user`.`id` = `account`.`user_id`
WHERE `user`.`id` = 1 LIMIT 1;
可以看到,我們對(duì)2個(gè)表進(jìn)行了一個(gè)外聯(lián)接,從而在取user的同時(shí)也獲取到了account。
其他補(bǔ)充說(shuō)明
如果我們重復(fù)調(diào)用user.createAccount方法,實(shí)際上會(huì)在數(shù)據(jù)庫(kù)里面生成多條user_id一樣的數(shù)據(jù),并不是真正的一對(duì)一。
所以,在應(yīng)用層保證一致性時(shí),就需要我們遵循良好的編碼約定。新增就用user.createAccount,更改就用user.setAccount。
也可以給user_id加一個(gè)UNIQUE約束,在數(shù)據(jù)庫(kù)層面保證一致性,這時(shí)就需要做好try/catch,發(fā)生插入異常的時(shí)候能夠知道是因?yàn)椴迦肓硕鄠€(gè)account。
另外,我們上面都是使用user來(lái)對(duì)account進(jìn)行操作。實(shí)際上反向操作也是可以的,這是因?yàn)槲覀兌x了Account.belongsTo(User)。在Sequelize里面定義關(guān)系時(shí),關(guān)系的調(diào)用方會(huì)獲得相關(guān)的“關(guān)系”方法,一般為了兩邊都能操作,會(huì)同時(shí)定義雙向關(guān)系(這里雙向關(guān)系指的是模型層面,并不會(huì)在數(shù)據(jù)庫(kù)表中出現(xiàn)兩個(gè)表都加上外鍵的情況,請(qǐng)放心)。
一對(duì)多
模型定義
Sequelize:
var User = sequelize.define('user',
{
'emp_id': {
'type': Sequelize.CHAR(10),
'allowNull': false,
'unique': true
}
}
);
var Note = sequelize.define('note',
{
'title': {
'type': Sequelize.CHAR(64),
'allowNull': false
}
}
);
/*
* User的實(shí)例對(duì)象將擁有g(shù)etNotes、setNotes、addNote、createNote、removeNote、hasNote方法
*/
User.hasMany(Note);
/*
* Note的實(shí)例對(duì)象將擁有g(shù)etUser、setUser、createUser方法
*/
Note.belongsTo(User);
SQL:
CREATE TABLE IF NOT EXISTS `users` (
`id` INTEGER NOT NULL auto_increment ,
`emp_id` CHAR(10) NOT NULL UNIQUE,
`created_at` DATETIME NOT NULL,
`updated_at` DATETIME NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB;
CREATE TABLE IF NOT EXISTS `notes` (
`id` INTEGER NOT NULL auto_increment ,
`title` CHAR(64) NOT NULL,
`created_at` DATETIME NOT NULL,
`updated_at` DATETIME NOT NULL,
`user_id` INTEGER,
PRIMARY KEY (`id`),
FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE SET NULL ON UPDATE CASCADE
) ENGINE=InnoDB;
可以看到這種關(guān)系中,外鍵user_id加在了多的一端(notes表)。同時(shí)相關(guān)的模型也自動(dòng)獲得了一些方法。
關(guān)系操作
增
方法1
Sequelize:
var user = yield User.create({'emp_id': '1'});
var note = yield user.createNote({'title': 'a'});
console.log(note);
SQL:
NSERT INTO `users`
(`id`,`emp_id`,`updated_at`,`created_at`)
VALUES
(DEFAULT,'1','2015-11-03 23:52:05','2015-11-03 23:52:05');
INSERT INTO `notes`
(`id`,`title`,`user_id`,`updated_at`,`created_at`)
VALUES
(DEFAULT,'a',1,'2015-11-03 23:52:05','2015-11-03 23:52:05');
SQL執(zhí)行邏輯:
- 使用
user的主鍵id值作為外鍵直接在notes表里插入一條數(shù)據(jù)。
方法2
Sequelize:
var user = yield User.create({'emp_id': '1'});
var note = yield Note.create({'title': 'b'});
yield user.addNote(note);
SQL:
INSERT INTO `users`
(`id`,`emp_id`,`updated_at`,`created_at`)
VALUES
(DEFAULT,'1','2015-11-04 00:02:56','2015-11-04 00:02:56');
INSERT INTO `notes`
(`id`,`title`,`updated_at`,`created_at`)
VALUES
(DEFAULT,'b','2015-11-04 00:02:56','2015-11-04 00:02:56');
UPDATE `notes`
SET `user_id`=1,`updated_at`='2015-11-04 00:02:56'
WHERE `id` IN (1);
SQL執(zhí)行邏輯:
插入一條
note數(shù)據(jù),此時(shí)該條數(shù)據(jù)的外鍵user_id為空使用
user的屬性id值再更新該條note數(shù)據(jù),設(shè)置好外鍵,完成關(guān)系建立
改
Sequelize:
// 為user增加note1、note2
var user = yield User.create({'emp_id': '1'});
var note1 = yield user.createNote({'title': 'a'});
var note2 = yield user.createNote({'title': 'b'});
// 先創(chuàng)建note3、note4
var note3 = yield Note.create({'title': 'c'});
var note4 = yield Note.create({'title': 'd'});
// user擁有的note更改為note3、note4
yield user.setNotes([note3, note4]);
SQL:
/* 省去了創(chuàng)建語(yǔ)句 */
SELECT `id`, `title`, `created_at`, `updated_at`, `user_id`
FROM `notes` AS `note` WHERE `note`.`user_id` = 1;
UPDATE `notes`
SET `user_id`=NULL,`updated_at`='2015-11-04 12:45:12'
WHERE `id` IN (1, 2);
UPDATE `notes`
SET `user_id`=1,`updated_at`='2015-11-04 12:45:12'
WHERE `id` IN (3, 4);
SQL執(zhí)行邏輯:
根據(jù)
user的屬性id查詢所有相關(guān)的note數(shù)據(jù)將
note1、note2的外鍵user_id置為NULL,切斷關(guān)系將
note3、note4的外鍵user_id置為user的屬性id,完成關(guān)系建立
這里為啥還要查出所有的note數(shù)據(jù)呢?因?yàn)槲覀冃枰鶕?jù)傳人setNotes的數(shù)組來(lái)計(jì)算出哪些note要切斷關(guān)系、哪些要新增關(guān)系,所以就需要查出來(lái)進(jìn)行一個(gè)計(jì)算集合的“交集”運(yùn)算。
刪
Sequelize:
var user = yield User.create({'emp_id': '1'});
var note1 = yield user.createNote({'title': 'a'});
var note2 = yield user.createNote({'title': 'b'});
yield user.setNotes([]);
SQL:
SELECT `id`, `title`, `created_at`, `updated_at`, `user_id`
FROM `notes` AS `note` WHERE `note`.`user_id` = 1;
UPDATE `notes`
SET `user_id`=NULL,`updated_at`='2015-11-04 12:50:08'
WHERE `id` IN (1, 2);
實(shí)際上,上面說(shuō)到的“改”已經(jīng)有“刪”的操作了(去掉note1、note2的關(guān)系)。這里的操作是刪掉用戶的所有note數(shù)據(jù),直接執(zhí)行user.setNotes([])即可。
SQL執(zhí)行邏輯:
根據(jù)
user的屬性id查出所有相關(guān)的note數(shù)據(jù)將其外鍵
user_id置為NULL,切斷關(guān)系
還有一個(gè)真正的刪除方法,就是removeNote。如下所示:
Sequelize:
yield user.removeNote(note);
SQL:
UPDATE `notes`
SET `user_id`=NULL,`updated_at`='2015-11-06 01:40:12'
WHERE `user_id` = 1 AND `id` IN (1);
查
情況1
查詢user的所有滿足條件的note數(shù)據(jù)。
Sequelize:
var notes = yield user.getNotes({
'where': {
'title': {
'$like': '%css%'
}
}
});
notes.forEach(function(note) {
console.log(note);
});
SQL:
SELECT `id`, `title`, `created_at`, `updated_at`, `user_id`
FROM `notes` AS `note`
WHERE (`note`.`user_id` = 1 AND `note`.`title` LIKE '%a%');
這種方法的SQL很簡(jiǎn)單,直接根據(jù)user的id值來(lái)查詢滿足條件的note即可。
情況2
查詢所有滿足條件的note,同時(shí)獲取note屬于哪個(gè)user。
Sequelize:
var notes = yield Note.findAll({
'include': [User],
'where': {
'title': {
'$like': '%css%'
}
}
});
notes.forEach(function(note) {
// note屬于哪個(gè)user可以通過(guò)note.user訪問(wèn)
console.log(note);
});
SQL:
SELECT `note`.`id`, `note`.`title`, `note`.`created_at`, `note`.`updated_at`, `note`.`user_id`,
`user`.`id` AS `user.id`, `user`.`emp_id` AS `user.emp_id`, `user`.`created_at` AS `user.created_at`, `user`.`updated_at` AS `user.updated_at`
FROM `notes` AS `note` LEFT OUTER JOIN `users` AS `user`
ON `note`.`user_id` = `user`.`id`
WHERE `note`.`title` LIKE '%css%';
這種方法,因?yàn)楂@取的主體是note,所以將notes去left join了users。
情況3
查詢所有滿足條件的user,同時(shí)獲取該user所有滿足條件的note。
Sequelize:
var users = yield User.findAll({
'include': [Note],
'where': {
'created_at': {
'$lt': new Date()
}
}
});
users.forEach(function(user) {
// user的notes可以通過(guò)user.notes訪問(wèn)
console.log(user);
});
SQL:
SELECT `user`.`id`, `user`.`emp_id`, `user`.`created_at`, `user`.`updated_at`,
`notes`.`id` AS `notes.id`, `notes`.`title` AS `notes.title`, `notes`.`created_at` AS `notes.created_at`, `notes`.`updated_at` AS `notes.updated_at`, `notes`.`user_id` AS `notes.user_id`
FROM `users` AS `user` LEFT OUTER JOIN `notes` AS `notes`
ON `user`.`id` = `notes`.`user_id`
WHERE `user`.`created_at` < '2015-11-05 01:51:35';
這種方法獲取的主體是user,所以將users去left join了notes。
一點(diǎn)補(bǔ)充
關(guān)于各種join的區(qū)別,可以參考:http://blog.codinghorror.com/a-visual-explanation-of-sql-joins/。
關(guān)于eager loading我想再啰嗦幾句。include里面?zhèn)鬟f的是去取相關(guān)模型,默認(rèn)是取全部,我們也可以再對(duì)這個(gè)模型進(jìn)行一層過(guò)濾。像下面這樣:
Sequelize:
// 查詢創(chuàng)建時(shí)間在今天之前的所有user,同時(shí)獲取他們note的標(biāo)題中含有關(guān)鍵字css的所有note
var users = yield User.findAll({
'include': [
{
'model': Note,
'where': {
'title': {
'$like': '%css%'
}
}
}
],
'where': {
'created_at': {
'$lt': new Date()
}
}
});
SQL:
SELECT `user`.`id`, `user`.`emp_id`, `user`.`created_at`, `user`.`updated_at`,
`notes`.`id` AS `notes.id`, `notes`.`title` AS `notes.title`, `notes`.`created_at` AS `notes.created_at`, `notes`.`updated_at` AS `notes.updated_at`, `notes`.`user_id` AS `notes.user_id`
FROM `users` AS `user` INNER JOIN `notes` AS `notes`
ON `user`.`id` = `notes`.`user_id` AND `notes`.`title` LIKE '%css%'
WHERE `user`.`created_at` < '2015-11-05 01:58:31';
注意:當(dāng)我們對(duì)include的模型加了where過(guò)濾時(shí),會(huì)使用inner join來(lái)進(jìn)行查詢,這樣保證只有那些擁有標(biāo)題含有css關(guān)鍵詞note的用戶才會(huì)返回。
多對(duì)多關(guān)系
在多對(duì)多關(guān)系中,必須要額外一張關(guān)系表來(lái)將2個(gè)表進(jìn)行關(guān)聯(lián),這張表可以是單純的一個(gè)關(guān)系表,也可以是一個(gè)實(shí)際的模型(含有自己的額外屬性來(lái)描述關(guān)系)。我比較喜歡用一個(gè)模型的方式,這樣方便以后做擴(kuò)展。
模型定義
Sequelize:
var Note = sequelize.define('note',
{
'title': {
'type': Sequelize.CHAR(64),
'allowNull': false
}
}
);
var Tag = sequelize.define('tag',
{
'name': {
'type': Sequelize.CHAR(64),
'allowNull': false,
'unique': true
}
}
);
var Tagging = sequelize.define('tagging',
{
'type': {
'type': Sequelize.INTEGER(),
'allowNull': false
}
}
);
// Note的實(shí)例擁有g(shù)etTags、setTags、addTag、addTags、createTag、removeTag、hasTag方法
Note.belongsToMany(Tag, {'through': Tagging});
// Tag的實(shí)例擁有g(shù)etNotes、setNotes、addNote、addNotes、createNote、removeNote、hasNote方法
Tag.belongsToMany(Note, {'through': Tagging});
SQL:
CREATE TABLE IF NOT EXISTS `notes` (
`id` INTEGER NOT NULL auto_increment ,
`title` CHAR(64) NOT NULL,
`created_at` DATETIME NOT NULL,
`updated_at` DATETIME NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB;
CREATE TABLE IF NOT EXISTS `tags` (
`id` INTEGER NOT NULL auto_increment ,
`name` CHAR(64) NOT NULL UNIQUE,
`created_at` DATETIME NOT NULL,
`updated_at` DATETIME NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB;
CREATE TABLE IF NOT EXISTS `taggings` (
`type` INTEGER NOT NULL,
`created_at` DATETIME NOT NULL,
`updated_at` DATETIME NOT NULL,
`tag_id` INTEGER ,
`note_id` INTEGER ,
PRIMARY KEY (`tag_id`, `note_id`),
FOREIGN KEY (`tag_id`) REFERENCES `tags` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
FOREIGN KEY (`note_id`) REFERENCES `notes` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB;
可以看到,多對(duì)多關(guān)系中單獨(dú)生成了一張關(guān)系表,并設(shè)置了2個(gè)外鍵tag_id和note_id來(lái)和tags和notes進(jìn)行關(guān)聯(lián)。關(guān)于關(guān)系表的命名,我比較喜歡使用動(dòng)詞,因?yàn)檫@張表是用來(lái)表示兩張表的一種聯(lián)系,而且這種聯(lián)系多數(shù)時(shí)候伴隨著一種動(dòng)作。比如:用戶收藏商品(collecting)、用戶購(gòu)買商品(buying)、用戶加入項(xiàng)目(joining)等等。
增
方法1
Sequelize:
var note = yield Note.create({'title': 'note'});
yield note.createTag({'name': 'tag'}, {'type': 0});
SQL:
INSERT INTO `notes`
(`id`,`title`,`updated_at`,`created_at`)
VALUES
(DEFAULT,'note','2015-11-06 02:14:38','2015-11-06 02:14:38');
INSERT INTO `tags`
(`id`,`name`,`updated_at`,`created_at`)
VALUES
(DEFAULT,'tag','2015-11-06 02:14:38','2015-11-06 02:14:38');
INSERT INTO `taggings`
(`tag_id`,`note_id`,`type`,`created_at`,`updated_at`)
VALUES
(1,1,0,'2015-11-06 02:14:38','2015-11-06 02:14:38');
SQL執(zhí)行邏輯:
在
notes表插入記錄在
tags表中插入記錄使用對(duì)應(yīng)的值設(shè)置外鍵
tag_id和note_id以及關(guān)系模型本身需要的屬性(type: 0)在關(guān)系表tagging中插入記錄
關(guān)系表本身需要的屬性,通過(guò)傳遞一個(gè)額外的對(duì)象給設(shè)置方法來(lái)實(shí)現(xiàn)。
方法2
Sequelize:
var note = yield Note.create({'title': 'note'});
var tag = yield Tag.create({'name': 'tag'});
yield note.addTag(tag, {'type': 1});
SQL:
INSERT INTO `notes`
(`id`,`title`,`updated_at`,`created_at`)
VALUES
(DEFAULT,'note','2015-11-06 02:20:52','2015-11-06 02:20:52');
INSERT INTO `tags`
(`id`,`name`,`updated_at`,`created_at`)
VALUES
(DEFAULT,'tag','2015-11-06 02:20:52','2015-11-06 02:20:52');
INSERT INTO `taggings`
(`tag_id`,`note_id`,`type`,`created_at`,`updated_at`)
VALUES
(1,1,1,'2015-11-06 02:20:52','2015-11-06 02:20:52');
這種方法和上面的方法實(shí)際上是一樣的。只是我們先手動(dòng)create了一個(gè)Tag模型。
方法3
Sequelize:
var note = yield Note.create({'title': 'note'});
var tag1 = yield Tag.create({'name': 'tag1'});
var tag2 = yield Tag.create({'name': 'tag2'});
yield note.addTags([tag1, tag2], {'type': 2});
SQL:
INSERT INTO `notes`
(`id`,`title`,`updated_at`,`created_at`)
VALUES
(DEFAULT,'note','2015-11-06 02:25:18','2015-11-06 02:25:18');
INSERT INTO `tags`
(`id`,`name`,`updated_at`,`created_at`)
VALUES
(DEFAULT,'tag1','2015-11-06 02:25:18','2015-11-06 02:25:18');
INSERT INTO `tags`
(`id`,`name`,`updated_at`,`created_at`)
VALUES
(DEFAULT,'tag2','2015-11-06 02:25:18','2015-11-06 02:25:18');
INSERT INTO `taggings` (`tag_id`,`note_id`,`type`,`created_at`,`updated_at`)
VALUES
(1,1,2,'2015-11-06 02:25:18','2015-11-06 02:25:18'),
(2,1,2,'2015-11-06 02:25:18','2015-11-06 02:25:18');
這種方法可以進(jìn)行批量添加。當(dāng)執(zhí)行addTags時(shí),實(shí)際上就是設(shè)置好對(duì)應(yīng)的外鍵及關(guān)系模型本身的屬性,然后在關(guān)系表中批量的插入數(shù)據(jù)。
改
Sequelize:
// 先添加幾個(gè)tag
var note = yield Note.create({'title': 'note'});
var tag1 = yield Tag.create({'name': 'tag1'});
var tag2 = yield Tag.create({'name': 'tag2'});
yield note.addTags([tag1, tag2], {'type': 2});
// 將tag改掉
var tag3 = yield Tag.create({'name': 'tag3'});
var tag4 = yield Tag.create({'name': 'tag4'});
yield note.setTags([tag3, tag4], {'type': 3});
SQL:
/* 前面添加部分的sql,和上面一樣*/
INSERT INTO `notes`
(`id`,`title`,`updated_at`,`created_at`)
VALUES
(DEFAULT,'note','2015-11-06 02:25:18','2015-11-06 02:25:18');
INSERT INTO `tags`
(`id`,`name`,`updated_at`,`created_at`)
VALUES
(DEFAULT,'tag1','2015-11-06 02:25:18','2015-11-06 02:25:18');
INSERT INTO `tags`
(`id`,`name`,`updated_at`,`created_at`)
VALUES
(DEFAULT,'tag2','2015-11-06 02:25:18','2015-11-06 02:25:18');
INSERT INTO `taggings`
(`tag_id`,`note_id`,`type`,`created_at`,`updated_at`)
VALUES
(1,1,2,'2015-11-06 02:25:18','2015-11-06 02:25:18'),
(2,1,2,'2015-11-06 02:25:18','2015-11-06 02:25:18');
/* 更改部分的sql */
INSERT INTO `tags`
(`id`,`name`,`updated_at`,`created_at`)
VALUES
(DEFAULT,'tag3','2015-11-06 02:29:55','2015-11-06 02:29:55');
INSERT INTO `tags`
(`id`,`name`,`updated_at`,`created_at`)
VALUES
(DEFAULT,'tag4','2015-11-06 02:29:55','2015-11-06 02:29:55');
/* 先刪除關(guān)系 */
DELETE FROM `taggings`
WHERE `note_id` = 1 AND `tag_id` IN (1, 2);
/* 插入新關(guān)系 */
INSERT INTO `taggings`
(`tag_id`,`note_id`,`type`,`created_at`,`updated_at`)
VALUES
(3,1,3,'2015-11-06 02:29:55','2015-11-06 02:29:55'),
(4,1,3,'2015-11-06 02:29:55','2015-11-06 02:29:55');
執(zhí)行邏輯是,先將tag1、tag2在關(guān)系表中的關(guān)系刪除,然后再將tag3、tag4對(duì)應(yīng)的關(guān)系插入關(guān)系表。
刪
Sequelize:
// 先添加幾個(gè)tag
var note = yield Note.create({'title': 'note'});
var tag1 = yield Tag.create({'name': 'tag1'});
var tag2 = yield Tag.create({'name': 'tag2'});
var tag3 = yield Tag.create({'name': 'tag2'});
yield note.addTags([tag1, tag2, tag3], {'type': 2});
// 刪除一個(gè)
yield note.removeTag(tag1);
// 全部刪除
yield note.setTags([]);
SQL:
/* 刪除一個(gè) */
DELETE FROM `taggings` WHERE `note_id` = 1 AND `tag_id` IN (1);
/* 刪除全部 */
SELECT `type`, `created_at`, `updated_at`, `tag_id`, `note_id`
FROM `taggings` AS `tagging`
WHERE `tagging`.`note_id` = 1;
DELETE FROM `taggings` WHERE `note_id` = 1 AND `tag_id` IN (2, 3);
刪除一個(gè)很簡(jiǎn)單,直接將關(guān)系表中的數(shù)據(jù)刪除。
全部刪除時(shí),首先需要查出關(guān)系表中note_id對(duì)應(yīng)的所有數(shù)據(jù),然后一次刪掉。
查
情況1
查詢note所有滿足條件的tag。
Sequelize:
var tags = yield note.getTags({
//這里可以對(duì)tags進(jìn)行where
});
tags.forEach(function(tag) {
// 關(guān)系模型可以通過(guò)tag.tagging來(lái)訪問(wèn)
console.log(tag);
});
SQL:
SELECT `tag`.`id`, `tag`.`name`, `tag`.`created_at`, `tag`.`updated_at`,
`tagging`.`type` AS `tagging.type`, `tagging`.`created_at` AS `tagging.created_at`, `tagging`.`updated_at` AS `tagging.updated_at`, `tagging`.`tag_id` AS `tagging.tag_id`, `tagging`.`note_id` AS `tagging.note_id`
FROM `tags` AS `tag`
INNER JOIN `taggings` AS `tagging`
ON
`tag`.`id` = `tagging`.`tag_id` AND `tagging`.`note_id` = 1;
可以看到這種查詢,就是執(zhí)行一個(gè)inner join。
情況2
查詢所有滿足條件的tag,同時(shí)獲取每個(gè)tag所在的note。
Sequelize:
var tags = yield Tag.findAll({
'include': [
{
'model': Note
// 這里可以對(duì)notes進(jìn)行where
}
]
// 這里可以對(duì)tags進(jìn)行where
});
tags.forEach(function(tag) {
// tag的notes可以通過(guò)tag.notes訪問(wèn),關(guān)系模型可以通過(guò)tag.notes[0].tagging訪問(wèn)
console.log(tag);
});
SQL:
SELECT `tag`.`id`, `tag`.`name`, `tag`.`created_at`, `tag`.`updated_at`,
`notes`.`id` AS `notes.id`, `notes`.`title` AS `notes.title`, `notes`.`created_at` AS `notes.created_at`, `notes`.`updated_at` AS `notes.updated_at`,
`notes.tagging`.`type` AS `notes.tagging.type`, `notes.tagging`.`created_at` AS `notes.tagging.created_at`, `notes.tagging`.`updated_at` AS `notes.tagging.updated_at`, `notes.tagging`.`tag_id` AS `notes.tagging.tag_id`, `notes.tagging`.`note_id` AS `notes.tagging.note_id`
FROM `tags` AS `tag`
LEFT OUTER JOIN
(
`taggings` AS `notes.tagging` INNER JOIN `notes` AS `notes`
ON
`notes`.`id` = `notes.tagging`.`note_id`
)
ON `tag`.`id` = `notes.tagging`.`tag_id`;
這個(gè)查詢就稍微有點(diǎn)復(fù)雜。首先是notes和taggings進(jìn)行了一個(gè)inner join,選出notes;然后tags和剛join出的集合再做一次left join,得到結(jié)果。
情況3
查詢所有滿足條件的note,同時(shí)獲取每個(gè)note所有滿足條件的tag。
Sequelize:
var notes = yield Note.findAll({
'include': [
{
'model': Tag
// 這里可以對(duì)tags進(jìn)行where
}
]
// 這里可以對(duì)notes進(jìn)行where
});
notes.forEach(function(note) {
// note的tags可以通過(guò)note.tags訪問(wèn),關(guān)系模型通過(guò)note.tags[0].tagging訪問(wèn)
console.log(note);
});
SQL:
SELECT
`note`.`id`, `note`.`title`, `note`.`created_at`, `note`.`updated_at`,
`tags`.`id` AS `tags.id`, `tags`.`name` AS `tags.name`, `tags`.`created_at` AS `tags.created_at`, `tags`.`updated_at` AS `tags.updated_at`,
`tags.tagging`.`type` AS `tags.tagging.type`, `tags.tagging`.`created_at` AS `tags.tagging.created_at`, `tags.tagging`.`updated_at` AS `tags.tagging.updated_at`, `tags.tagging`.`tag_id` AS `tags.tagging.tag_id`, `tags.tagging`.`note_id` AS `tags.tagging.note_id`
FROM `notes` AS `note`
LEFT OUTER JOIN
(
`taggings` AS `tags.tagging` INNER JOIN `tags` AS `tags`
ON
`tags`.`id` = `tags.tagging`.`tag_id`
)
ON
`note`.`id` = `tags.tagging`.`note_id`;
這個(gè)查詢和上面的查詢類似。首先是tags和taggins進(jìn)行了一個(gè)inner join,選出tags;然后notes和剛join出的集合再做一次left join,得到結(jié)果。
其他沒有涉及東西
這篇文章已經(jīng)夠長(zhǎng)了,但是其實(shí)我們還有很多沒有涉及的東西,比如:聚合函數(shù)及查詢(having、group by)、模型的驗(yàn)證(validate)、定義鉤子(hooks)、索引等等。