換個姿勢聊一聊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則代表,將財務部和財務部員工一起刪除

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