Sequelize 和 MySQL 對(duì)照

如果你覺得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)說明:

  1. 建表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)慎背传。

  2. 所有數(shù)據(jù)類型,請(qǐng)參考文檔數(shù)據(jù)類型台夺。

  3. 模型還可以定義虛擬屬性径玖、類方法、實(shí)例方法颤介,請(qǐng)參考文檔:模型定義

  4. 其他一些特殊定義如下所示:

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始绍、updatedestroy等)话侄,需要獲取“干凈”的JSON對(duì)象可以調(diào)用get({'plain': true})亏推。

通過模型的類方法可以獲取模型對(duì)象(比如:findByIdfindAll等)年堆。

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_atupdated_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子句

Sequelizewhere配置項(xiàng)基本上完全支持了SQLwhere子句的功能包帚,非常強(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í)也支持ORNOT吼鱼、甚至多種條件的聯(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_iduser的屬性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)用usergetAccount方法,根據(jù)外鍵來獲取對(duì)應(yīng)的account腮郊。

但是其實(shí)我們用面向?qū)ο蟮乃季S來思考應(yīng)該是獲取user的時(shí)候就能通過user.account的方式來訪問account對(duì)象摹蘑。這可以通過Sequelizeeager 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ù)userid值來查詢滿足條件的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勋眯,所以將notesleft joinusers婴梧。

情況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塞蹭,所以將usersleft joinnotes

一點(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_idnote_id來和tagsnotes進(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_idnote_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ù)雜试躏。首先是notestaggings進(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è)查詢和上面的查詢類似。首先是tagstaggins進(jìn)行了一個(gè)inner join寡键,選出tags掀泳;然后notes和剛join出的集合再做一次left join,得到結(jié)果昌腰。

其他沒有涉及東西

這篇文章已經(jīng)夠長(zhǎng)了,但是其實(shí)我們還有很多沒有涉及的東西膀跌,比如:聚合函數(shù)及查詢(having遭商、group by)、模型的驗(yàn)證(validate)捅伤、定義鉤子(hooks)劫流、索引等等。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市祠汇,隨后出現(xiàn)的幾起案子仍秤,更是在濱河造成了極大的恐慌,老刑警劉巖可很,帶你破解...
    沈念sama閱讀 219,490評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件诗力,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡我抠,警方通過查閱死者的電腦和手機(jī)苇本,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,581評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來菜拓,“玉大人瓣窄,你說我怎么就攤上這事∧啥Γ” “怎么了俺夕?”我有些...
    開封第一講書人閱讀 165,830評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)贱鄙。 經(jīng)常有香客問我劝贸,道長(zhǎng),這世上最難降的妖魔是什么贰逾? 我笑而不...
    開封第一講書人閱讀 58,957評(píng)論 1 295
  • 正文 為了忘掉前任悬荣,我火速辦了婚禮,結(jié)果婚禮上疙剑,老公的妹妹穿的比我還像新娘氯迂。我一直安慰自己,他們只是感情好言缤,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,974評(píng)論 6 393
  • 文/花漫 我一把揭開白布嚼蚀。 她就那樣靜靜地躺著,像睡著了一般管挟。 火紅的嫁衣襯著肌膚如雪轿曙。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,754評(píng)論 1 307
  • 那天僻孝,我揣著相機(jī)與錄音导帝,去河邊找鬼。 笑死穿铆,一個(gè)胖子當(dāng)著我的面吹牛您单,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播荞雏,決...
    沈念sama閱讀 40,464評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼虐秦,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼平酿!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起悦陋,我...
    開封第一講書人閱讀 39,357評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤蜈彼,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后俺驶,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體幸逆,經(jīng)...
    沈念sama閱讀 45,847評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,995評(píng)論 3 338
  • 正文 我和宋清朗相戀三年痒钝,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了秉颗。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,137評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡送矩,死狀恐怖蚕甥,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情栋荸,我是刑警寧澤菇怀,帶...
    沈念sama閱讀 35,819評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站晌块,受9級(jí)特大地震影響爱沟,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜匆背,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,482評(píng)論 3 331
  • 文/蒙蒙 一呼伸、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧钝尸,春花似錦括享、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,023評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至猪叙,卻和暖如春娇斩,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背穴翩。 一陣腳步聲響...
    開封第一講書人閱讀 33,149評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工犬第, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人芒帕。 一個(gè)月前我還...
    沈念sama閱讀 48,409評(píng)論 3 373
  • 正文 我出身青樓歉嗓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親副签。 傳聞我的和親對(duì)象是個(gè)殘疾皇子遥椿,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,086評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容