摘要:安裝非常簡(jiǎn)單代碼模板如下基本上,的操作都會(huì)返回一個(gè),在的框架里面可以直接進(jìn)行,非常方便。批量操作插入這里需要注意,返回的數(shù)組里面每個(gè)對(duì)象的值會(huì)是。
如果你覺(jué)得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。兩者在定義階段沒(méi)有什么關(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)?b>SQL沒(méi)有= NULL
這種語(yǔ)法)
如果v是數(shù)組,會(huì)轉(zhuǎn)換為IN條件(因?yàn)?b>SQL沒(méi)有=[1,2,3]這種語(yǔ)法,況且也沒(méi)數(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)合查詢。
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 );
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 );
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;其他查詢方法
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;
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ù)量小的情況下可以使用,基本沒(méi)有什么影響,數(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是空的,還沒(méi)有關(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)系操作 增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ù)。
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);查
查詢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即可。
查詢所有滿足條件的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。
查詢所有滿足條件的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。
關(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)系中多帶帶生成了一張關(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)等等。
增 方法1Sequelize:
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)。
方法2Sequelize:
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模型。
方法3Sequelize:
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é)果。
其他沒(méi)有涉及東西這篇文章已經(jīng)夠長(zhǎng)了,但是其實(shí)我們還有很多沒(méi)有涉及的東西,比如:聚合函數(shù)及查詢(having、group by)、模型的驗(yàn)證(validate)、定義鉤子(hooks)、索引等等。
這些主題下次再來(lái)寫寫。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/86200.html
功能梳理完了以后,咱們就可以開始數(shù)據(jù)庫(kù)表設(shè)計(jì)了: 數(shù)據(jù)庫(kù)表圖: showImg(https://segmentfault.com/img/bVbr9GC?w=1922&h=1140); 首先打開Navicat Premium 創(chuàng)建數(shù)據(jù)庫(kù) blog 配置如下: showImg(https://segmentfault.com/img/bVbr81Y?w=720&h=352); 課前學(xué)習(xí):1、Sequ...
摘要:根據(jù)數(shù)據(jù)庫(kù)接收連接數(shù)相應(yīng)配置??蛻魜?lái)了,需要配備一個(gè)點(diǎn)餐員,客戶來(lái)了隨時(shí)響應(yīng)。但是到了關(guān)門的時(shí)間了,我就會(huì)讓撤銷所有的點(diǎn)餐員。如果客戶端請(qǐng)求服務(wù)器內(nèi)沒(méi)有響應(yīng)就會(huì)被拒絕掉,而不會(huì)一直請(qǐng)求著。 網(wǎng)上找了很多資料,大多都是雷同的。我這里也是大同小異 ,只是想記錄一下查閱資料的過(guò)程小白上路,高手勿怪...... 首先當(dāng)然是要安裝 mysql12 和 Sequelize執(zhí)行npm i mysql...
閱讀 3524·2021-09-08 09:36
閱讀 2692·2019-08-30 15:54
閱讀 2413·2019-08-30 15:54
閱讀 1821·2019-08-30 15:44
閱讀 2446·2019-08-26 14:04
閱讀 2490·2019-08-26 14:01
閱讀 2936·2019-08-26 13:58
閱讀 1414·2019-08-26 13:47