换个姿势聊一聊CRUD

本文不去讲CRUD的基础知识,我们来换个姿势聊一聊CRUD操作。

我想很多人可能会面临下面这些问题:

  • 批量插入数据,一条失败全部回滚,但想忽略失败的数据继续插入怎么办?
  • 数据存在时更新数据,不存在时插入数据,一定要在每次插入数据之前读一次数据库进行判断吗,批量插入时又怎么办?
  • 子查询有没有可能带来性能问题?什么是相关子查询?如何解决相关子查询性能问题?
  • 多表连接查询,一个相同的查询条件写在WHERE子句和写在连接条件子句中有何区别?
  • UPDATE操作可以使用表连接吗?可以同时更新多张表吗?
  • DELETE操作可以使用表连接吗?可以同时删除多张表吗?

接下来,我们直接进入正题。

1. 批量插入,忽略插入失败的数据

有一个部门表,表结构如下:

CREATE TABLE `t_dept` (
  `deptno` int NOT NULL,
  `dname` varchar(20) DEFAULT NULL,
  `loc` varchar(20) DEFAULT NULL,
  PRIMARY KEY (`deptno`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

里面有这几条原始数据

INSERT INTO `t_dept` VALUES (10, 'ACCOUNTING', 'NEW YORK');
INSERT INTO `t_dept` VALUES (20, 'RESEARCH', 'DALLAS');
INSERT INTO `t_dept` VALUES (30, 'SALES', 'CHICAGO');
INSERT INTO `t_dept` VALUES (40, 'OPERATIONS', 'BOSTON');

往部门表批量插入数据的常规写法:

INSERT INTO t_dept(deptno,dname,loc) VALUES
	(40,'企划部','北京'),
	(50,'培训部','上海'),
	(60,'后勤部','北京'),
	(70,'技术部','北京'),
	(80,'市场部','北京')

deptno是t_dept表中的主键id,假设t_dept表中原来就存在deptno=40的数据。则在执行插入 (40,‘企划部’,‘北京’) 这条记录时会遇到错误,从而回滚事务,其所带来的结果便是一条数据都没有被插入。

如果你想要插入数据时忽略错误,可以用IGNORE关键字:

INSERT IGNORE INTO t_dept(deptno,dname,loc) VALUES
	(40,'企划部','北京'),
	(50,'培训部','上海'),
	(60,'后勤部','北京'),
	(70,'技术部','北京'),
	(80,'市场部','北京')

在使用了IGNORE关键字后,遇到错误不会回滚事务,并且还会继续插入数据。

2. 存在更新,不存在插入

有一个员工IP分配表,表结构如下:

CREATE TABLE `t_emp_ip` (
  `id` int NOT NULL,
  `empno` int NOT NULL,
  `ip` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  PRIMARY KEY (`id`) USING BTREE,
  UNIQUE KEY `empno` (`empno`) USING BTREE,
  UNIQUE KEY `ip` (`ip`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC;

其中id为主键id,empno和ip都不允许重复,换句话说一个员工只能有一个IP。

先往这个表里插入一些原始数据

INSERT INTO t_emp_ip(id,empno,ip) VALUES
	(1,8000,'192.168.99.40'),
	(2,8001,'192.168.99.41'),
	(3,8002,'192.168.99.42'),
	(4,8003,'192.168.99.43')

假设我想要实现empno存在时更新IP,不存在时才插入IP该怎么实现呢?

  • 很容易想到的便是先根据empno去查询记录,如果能查询到则执行UPDATE语句,不能查询到则执行INSERT语句。

这种方法可以,但略显麻烦,其实一个ON DUPLICATE KEY UPDATE语句便可轻松搞定。

INSERT INTO t_emp_ip(id,empno,ip) VALUES
	(5,8004,'192.168.99.44'),
	(6,8005,'192.168.99.45'),
	(7,8006,'192.168.99.46'),
	(8,8001,'192.168.99.47')
ON DUPLICATE KEY UPDATE ip=VALUES(ip)

当KEY值冲突时,执行后面的UPDATE语句,将表中的ip字段更新为传入的ip值(如果更新多列数据,用逗号分割)。不冲突时,正常执行插入。

不过需要注意的是,ON DUPLICATE KEY是MySQL的特有语法,相同语句在其他数据库不能运行,这一点需要特别注意。

3. 子查询,相关子查询

子查询,即是在查询条件中使用SELECT语句,将SELECT语句的输出结果作为查询的条件。

有一个员工表,其结构如下:

CREATE TABLE `t_emp` (
  `empno` int NOT NULL,
  `ename` varchar(20) DEFAULT NULL,
  `job` varchar(20) DEFAULT NULL,
  `mgr` int DEFAULT NULL,
  `hiredate` date DEFAULT NULL,
  `sal` decimal(10,2) DEFAULT NULL,
  `comm` decimal(10,2) DEFAULT NULL,
  `deptno` int DEFAULT NULL,
  PRIMARY KEY (`empno`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

里面有这几条原始数据

INSERT INTO `t_emp` VALUES (7369, 'SMITH', 'CLERK', 7902, '1980-12-17 00:00:00', 800.00, NULL, 20);
INSERT INTO `t_emp` VALUES (7499, 'ALLEN', 'SALESMAN', 7698, '1981-02-20 00:00:00', 1600.00, 300.00, 30);
INSERT INTO `t_emp` VALUES (7521, 'WARD', 'SALESMAN', 7698, '1981-02-22 00:00:00', 1250.00, 500.00, 30);
INSERT INTO `t_emp` VALUES (7566, 'JONES', 'MANAGER', 7839, '1981-04-02 00:00:00', 2975.00, NULL, 20);
INSERT INTO `t_emp` VALUES (7654, 'MARTIN', 'SALESMAN', 7698, '1981-09-28 00:00:00', 1250.00, 1400.00, 30);
INSERT INTO `t_emp` VALUES (7698, 'BLAKE', 'MANAGER', 7839, '1981-05-01 00:00:00', 2850.00, NULL, 30);
INSERT INTO `t_emp` VALUES (7782, 'CLARK', 'MANAGER', 7839, '1981-06-09 00:00:00', 2450.00, NULL, 10);
INSERT INTO `t_emp` VALUES (7788, 'SCOTT', 'ANALYST', 7566, '1982-12-09 00:00:00', 3000.00, NULL, 20);
INSERT INTO `t_emp` VALUES (7839, 'KING', 'PRESIDENT', NULL, '1981-11-17 00:00:00', 5000.00, NULL, 10);
INSERT INTO `t_emp` VALUES (7844, 'TURNER', 'SALESMAN', 7698, '1981-09-08 00:00:00', 1500.00, 0.00, 30);
INSERT INTO `t_emp` VALUES (7876, 'ADAMS', 'CLERK', 7788, '1983-01-12 00:00:00', 1100.00, NULL, 20);
INSERT INTO `t_emp` VALUES (7900, 'JAMES', 'CLERK', 7698, '1981-12-03 00:00:00', 950.00, NULL, 30);
INSERT INTO `t_emp` VALUES (7902, 'FORD', 'ANALYST', 7566, '1981-12-03 00:00:00', 3000.00, NULL, 20);
INSERT INTO `t_emp` VALUES (7934, 'MILLER', 'CLERK', 7782, '1982-01-23 00:00:00', 1300.00, NULL, 10);

如果要查询工资高于JONES的员工,则很容易想到以下写法:

SELECT
	ename,
	sal 
FROM
	t_emp 
WHERE
	sal > ( SELECT sal FROM t_emp WHERE ename = 'JONES' )

这条SQL语句的WHERE子句中的SELECT就是子查询。

那什么是相关子查询?

  • 相关子查询就是要循环执行多次的子查询。因为MySQL默认关闭了缓存,所以WHER子句每执行一次都要执行一次子查询。换句话说,如果表中有1w条记录,则(SELECT sal FROM t_emp WHERE ename = ‘JONES’)将会被执行1w次。

既然子查询效率如此低下,那可以用子查询吗?

  • 答案是肯定的,因为如果你使用的是MyBatis、Hibernate这样的框架去操作数据库,是可以使用子查询的。因为其默认打开了一级缓存,子查询只会被执行一次,而不是次次执行。如果你是在命令行窗口使用子查询,那效率肯定是低下的。

那有没有完美的办法解决这个问题呢?

  • 解决方案便是使用FROM子句来代替WHERE子查询。

4. 使用FROM子句替代子查询

SELECT
	e.ename,
	e.sal 
FROM
	t_emp e
	JOIN ( SELECT sal FROM t_emp WHERE ename = 'JONES' ) t ON e.sal > t.sal

这条SQL语句的输出结果和第三节SQL语句的输出结果一致,但其运行效率却比之前那条SQL语句高。因为FROM子查询只会执行一次,所以不是相关子查询。

5. 表连接查询条件位置的影响

5.1 内连接查询

查询工资大于2500的员工,并显示出员工的所在部门。当使用内连接时,以下两种写法的运行完全一致

SELECT
	e.ename,
	e.sal,
	d.dname 
FROM
	t_emp e
	JOIN t_dept d ON e.deptno = d.deptno 
	AND e.sal > 2500
SELECT
	e.ename,
	e.sal,
	d.dname 
FROM
	t_emp e
	JOIN t_dept d ON e.deptno = d.deptno 
	WHERE e.sal > 2500

5.2 外连接查询

把上面的内连接查询改成左外连接查询

SELECT
	e.ename,
	e.sal,
	d.dname 
FROM
	t_emp e
	LEFT JOIN t_dept d ON e.deptno = d.deptno 
	AND e.sal > 2500
SELECT
	e.ename,
	e.sal,
	d.dname 
FROM
	t_emp e
	LEFT JOIN t_dept d ON e.deptno = d.deptno 
WHERE
	e.sal > 2500

这两条SQL语句的执行结果就不相同了:

  • 第一个SQL语句因为把条件放到了表连接的子句中,所以可以完整的展现左表,对于不符合sal>2500的数据列,其部门信息将为NULL。
  • 第二个SQL语句因为把条件放到了WHERE子句中,故使用该条件做了最终过滤,只显示出了sal>2500的数据列。

6. UPDATE使用表连接

假设现在有一个给ACCOUNTING部门全体员工加薪500的需求,最容易想到的方式也许是这样的:

UPDATE t_emp 
SET sal = sal + 500 
WHERE
	deptno = ( SELECT deptno FROM t_dept WHERE dname = 'ACCOUNTING' )

相信,在这里你一眼就看出来了,WHERE子句后面使用的是相关子查询。

那可不可以用带表连接的UPDATE语句呢?答案是肯定的。

UPDATE t_emp e
JOIN t_dept d ON e.deptno = d.deptno AND d.dname = 'ACCOUNTING'
SET e.sal = e.sal + 500

你以为到这就结束了吗?

还有更高级的:

UPDATE t_emp e
JOIN t_dept d ON e.deptno = d.deptno AND d.dname = 'ACCOUNTING'
SET e.sal = e.sal + 500,d.dname = '财务部'

这条语句直接修改了两张表里的数据,是不是很方便啊。

7. DELETE使用表连接

DELETE语句也可以使用表连接,使用表连接的DELETE语句同样可以做到避免相关子查询,同样可以做到删除多张表。

假设需要删除财务部这个部门及其底下的所有员工信息,可以这样做:

DELETE d,e 
FROM
	t_emp e
	JOIN t_dept d ON d.deptno = e.deptno 
	AND d.dname = '财务部'

如果写DELETE e则代表只删除财务部门的所有员工,而不删除财务部;如果写DELETE e,d则代表,将财务部和财务部员工一起删除

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章