如果你覺得Sequelize的文檔有點(diǎn)多、雜山橄,不方便看墙歪,可以看看這篇听系。
在使用NodeJS
來關(guān)系型操作數(shù)據(jù)庫(kù)時(shí),為了方便虹菲,通常都會(huì)選擇一個(gè)合適的ORM
(Object Relationship Model)框架靠胜。畢竟直接操作SQL
比較繁瑣,通過ORM
框架毕源,我們可以使用面向?qū)ο蟮姆绞絹聿僮鞅怼?code>NodeJS社區(qū)有很多的ORM
框架浪漠,我比較喜歡Sequelize
,它功能豐富霎褐,可以非常方便的進(jìn)行連表查詢址愿。
這篇文章我們就來看看,Sequelize
是如何在SQL
之上進(jìn)行抽象冻璃、封裝响谓,從而提高開發(fā)效率的。
安裝
這篇文章主要使用MySQL
省艳、Sequelize
娘纷、co
來進(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': {
// 字段以下劃線(_)來分割(默認(rèn)是駝峰命名風(fēng)格)
'underscored': true
}
}
);
定義單張表
Sequelize
:
var User = sequelize.define(
// 默認(rèn)表名(一般這里寫單數(shù))嬉探,生成時(shí)會(huì)自動(dòng)轉(zhuǎn)換成復(fù)數(shù)形式
// 這個(gè)值還會(huì)作為訪問模型相關(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)說明:
建表
SQL
會(huì)自動(dòng)執(zhí)行的意思是你主動(dòng)調(diào)用sync
的時(shí)候分瘾。類似這樣:User.sync({force: true});
(加force:true
胎围,會(huì)先刪掉表后再建表)。我們也可以先定義好表結(jié)構(gòu)德召,再來定義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
}
);
單表增刪改查
通過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})
亏推。
通過模型的類方法可以獲取模型對(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
值來讓數(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ì)表單提交過來的一大推數(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})
來強(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)大。我們一步步來進(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}
。
條件
上面我們說的條件查詢峭沦,都是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ù)雜例子菇肃,基本上就是按上述流程來進(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) // 跳過多少條
});
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ù)(看起來像是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
提供了清晰易用的接口來定義關(guān)系、進(jìn)行表間的操作界酒。
當(dāng)說到關(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
還給我們生成了外鍵約束。
一般來說闹丐,外鍵約束在有些自己定制的數(shù)據(jù)庫(kù)系統(tǒng)里面是禁止的横殴,因?yàn)闀?huì)帶來一些性能問題。所以卿拴,建表的SQL
一般就去掉約束衫仑,同時(shí)給外鍵加一個(gè)索引(加速查詢),數(shù)據(jù)的一致性就靠應(yīng)用層來保證了堕花。
關(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ù)外鍵來獲取對(duì)應(yīng)的account
腮郊。
但是其實(shí)我們用面向?qū)ο蟮乃季S來思考應(yīng)該是獲取user
的時(shí)候就能通過user.account
的方式來訪問account
對(duì)象摹蘑。這可以通過Sequelize
的eager loading
(急加載,和懶加載相反)來實(shí)現(xiàn)轧飞。
eager loading
的含義是說衅鹿,取一個(gè)模型的時(shí)候撒踪,同時(shí)也把相關(guān)的模型數(shù)據(jù)也給我取過來(我很著急,不能按默認(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ǔ)充說明
如果我們重復(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
來對(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ù)組來計(jì)算出哪些note
要切斷關(guān)系外臂、哪些要新增關(guān)系坐儿,所以就需要查出來進(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í)際上宋光,上面說到的“改”已經(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
值來查詢滿足條件的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可以通過note.user訪問
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可以通過user.notes訪問
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)行一層過濾这刷。像下面這樣:
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
過濾時(shí)娩井,會(huì)使用inner join
來進(jìn)行查詢暇屋,這樣保證只有那些擁有標(biāo)題含有css
關(guān)鍵詞note
的用戶才會(huì)返回。
多對(duì)多關(guān)系
在多對(duì)多關(guān)系中洞辣,必須要額外一張關(guān)系表來將2個(gè)表進(jìn)行關(guān)聯(lián)咐刨,這張表可以是單純的一個(gè)關(guān)系表,也可以是一個(gè)實(shí)際的模型(含有自己的額外屬性來描述關(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
來和tags
和notes
進(jìn)行關(guān)聯(lián)。關(guān)于關(guān)系表的命名佛呻,我比較喜歡使用動(dòng)詞裳朋,因?yàn)檫@張表是用來表示兩張表的一種聯(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)系表本身需要的屬性暖眼,通過傳遞一個(gè)額外的對(duì)象給設(shè)置方法來實(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)系模型可以通過tag.tagging來訪問
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可以通過tag.notes訪問,關(guān)系模型可以通過tag.notes[0].tagging訪問
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可以通過note.tags訪問椅您,關(guān)系模型通過note.tags[0].tagging訪問
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
)劫流、索引等等。