当前位置:Gxlcms > 数据库问题 > Sequelize 和 MySQL 对照

Sequelize 和 MySQL 对照

时间:2021-07-01 10:21:17 帮助过:13人阅读

基本上,Sequelize的操作都会返回一个Promise,在co的框架里面可以直接进行yield,非常方便。

建立数据库连接

var sequelize = new Sequelize(
    ‘sample‘, // 数据库名
    ‘root‘,   // 用户名
    ‘zuki‘,   // 用户密码
    {
        ‘dialect‘: ‘mysql‘,  // 数据库使用mysql
        ‘host‘: ‘localhost‘, // 数据库服务器ip
        ‘port‘: 3306,        // 数据库服务器端口
        ‘define‘: {
            // 字段以下划线(_)来分割(默认是驼峰命名风格)
            ‘underscored‘: true
        }
    }
);

定义单张表

Sequelize

var User = sequelize.define(
    // 默认表名(一般这里写单数),生成时会自动转换成复数形式
    // 这个值还会作为访问模型相关的模型时的属性名,所以建议用小写形式
    ‘user‘,
    // 字段定义(主键、created_at、updated_at默认包含,不用特殊定义)
    {
        ‘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;

几点说明:

  1. 建表SQL会自动执行的意思是你主动调用sync的时候。类似这样:User.sync({force: true});(加force:true,会先删掉表后再建表)。我们也可以先定义好表结构,再来定义Sequelize模型,这时可以不用sync。两者在定义阶段没有什么关系,直到我们真正开始操作模型时,才会触及到表的操作,但是我们当然还是要尽量保证模型和表的同步(可以借助一些migration工具)。自动建表功能有风险,使用需谨慎。

  2. 所有数据类型,请参考文档数据类型。

  3. 模型还可以定义虚拟属性、类方法、实例方法,请参考文档:模型定义

  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字段改个名
        ‘updatedAt‘: ‘utime‘

        // 将deletedAt字段改名
        // 同时需要设置paranoid为true(此种模式下,删除数据时不会进行物理删除,而是设置deletedAt为当前时间
        ‘deletedAt‘: ‘dtime‘,
        ‘paranoid‘: true
    }
);

单表增删改查

通过Sequelize获取的模型对象都是一个DAO(Data Access Object)对象,这些对象会拥有许多操作数据库表的实例对象方法(比如:saveupdatedestroy等),需要获取“干净”的JSON对象可以调用get({‘plain‘: true})

通过模型的类方法可以获取模型对象(比如:findByIdfindAll等)。

Sequelize

// 方法1:build后对象只存在于内存中,调用save后才操作db
var user = User.build({
    ‘emp_id‘: ‘1‘,
    ‘nick‘: ‘小红‘,
    ‘department‘: ‘技术部‘
});
user = yield user.save();
console.log(user.get({‘plain‘: true}));

// 方法2:直接操作db
var user = yield User.create({
    ‘emp_id‘: ‘2‘,
    ‘nick‘: ‘小明‘,
    ‘department‘: ‘技术部‘
});
console.log(user.get({‘plain‘: true}));

SQL

INSERT INTO `users` 
(`id`, `emp_id`, `nick`, `department`, `updated_at`, `created_at`) 
VALUES 
(DEFAULT, ‘1‘, ‘小红‘, ‘技术部‘, ‘2015-11-02 14:49:54‘, ‘2015-11-02 14:49:54‘);

Sequelize会为主键id设置DEFAULT值来让数据库产生自增值,还将当前时间设置成了created_atupdated_at字段,非常方便。

Sequelize

// 方法1:操作对象属性(不会操作db),调用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;

更新操作时,Sequelize将将当前时间设置成了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‘]}
});

这样就只会更新nick字段,而emp_id会被忽略。这种方法在对表单提交过来的一大推数据中只更新某些属性的时候比较有用。

Sequelize

yield user.destroy();

SQL

DELETE FROM `users` WHERE `id` = 1;

这里有个特殊的地方是,如果我们开启了paranoid(偏执)模式,destroy的时候不会执行DELETE语句,而是执行一个UPDATE语句将deleted_at字段设置为当前时间(一开始此字段值为NULL)。我们可以使用user.destroy({force: true})来强制删除,从而执行DELETE语句进行物理删除。

查全部

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配置项基本上完全支持了SQLwhere子句的功能,非常强大。我们一步步来进行介绍。

基本条件

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被转换成了k = v,同时一个对象的多个k: v对被转换成了AND条件,即:k1: v1, k2: v2转换为k1 = v1 AND k2 = v2

这里有2个要点:

  • 如果v是null,会转换为IS NULL(因为SQL没有= NULL
    这种语法)

  • 如果v是数组,会转换为IN条件(因为SQL没有=[1,2,3]这种语法,况且也没数组这种类型)

操作符

操作符是对某个字段的进一步约束,可以有多个(对同一个字段的多个操作符会被转化为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
);

这里我们发现,其实相等条件k: v这种写法是操作符写法k: {$eq: v}的简写。而要实现不等条件就必须使用操作符写法k: {$ne: v}

条件

上面我们说的条件查询,都是AND查询,Sequelize同时也支持ORNOT、甚至多种条件的联合查询。

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
);

转换规则

我们这里做个总结。Sequelizewhere配置的转换规则的伪代码大概如下:

function translate(where) {

    for (k, v of where) {

        if (k == 表字段) {
            // 先统一转为操作符形式
            if (v == 基本值) { // k: ‘xxx‘
                v = {‘$eq‘: v};
            }
            if (v == 数组) { // k: [1, 2, 3]
                v = {‘$in‘: v};
            }

            // 操作符转换
            for (opk, opv of v) {
                // op将opk转换对应的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;
        }
    }

}

我们看一个复杂例子,基本上就是按上述流程来进行转换。

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`;

分页

Sequelize

var countPerPage = 20, currentPage = 5;
var users = yield User.findAll({
    ‘limit‘: countPerPage,                      // 每页多少条
    ‘offset‘: countPerPage * (currentPage - 1)  // 跳过多少条
});

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;

这个方法会执行2个SQL,返回的result对象将包含2个字段:result.count是数据总数,result.rows是符合查询条件的所有数据。

批量操作

插入

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数组里面每个对象的id值会是null。如果需要id值,可以重新取下数据。

更新

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其实是一个数组,里面只有一个元素,表示更新的数据条数(看起来像是Sequelize的一个bug)。

删除

Sequelize

var affectedRows = yield User.destroy({
    ‘where‘: {‘id‘: [2, 3, 4]}
});

SQL

DELETE FROM `users` WHERE `id` IN (2, 3, 4);

这里返回的affectedRows是一个数字,表示删除的数据条数。

关系

关系一般有三种:一对一、一对多、多对多。Sequelize提供了清晰易用的接口来定义关系、进行表间的操作。

当说到关系查询时,一般会需要获取多张表的数据。有建议用连表查询join的,有不建议的。我的看法是,join查询这种黑科技在数据量小的情况下可以使用,基本没有什么影响,数据量大的时候,join的性能可能会是硬伤,应该尽量避免,可以分别根据索引取单表数据然后在应用层对数据进行joinmerge。当然,查询时一定要分页,不要findAll

一对一

模型定义

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的实例对象将拥有getAccount、setAccount、addAccount方法
 */
User.hasOne(Account);
/*
 * Account的实例对象将拥有getUser、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;

可以看到,这种关系中外键user_id加在了Account上。另外,Sequelize还给我们生成了外键约束。

一般来说,外键约束在有些自己定制的数据库系统里面是禁止的,因为会带来一些性能问题。所以,建表的SQL一般就去掉约束,同时给外键加一个索引(加速查询),数据的一致性就靠应用层来保证了。

关系操作

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执行逻辑是:

  • 使用对应的的user_id作为外键在accounts表里插入一条数据。

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执行逻辑是:

  • 插入一条account数据,此时外键user_id是空的,还没有关联user

  • 找出当前user所关联的account并将其user_id置为`NUL(为了保证一对一关系)

  • 设置新的acount的外键user_iduser的属性id,生成关系

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;

这里的删除实际上只是“切断”关系,并不会真正的物理删除记录。

SQL执行逻辑是:

  • 找出user所关联的account数据

  • 将其外键user_id设置为NULL,完成关系的“切断”

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);

这里就是调用usergetAccount方法,根据外键来获取对应的account

但是其实我们用面向对象的思维来思考应该是获取user的时候就能通过user.account的方式来访问account对象。这可以通过Sequelizeeager loading(急加载,和懒加载相反)来实现。

eager loading的含义是说,取一个模型的时候,同时也把相关的模型数据也给我取过来(我很着急,不能按默认那种取一个模型就取一个模型的方式,我还要更多)。方法如下:

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`, 

                  

	 	
                    
                    
                    
                    
                    
                

人气教程排行