本文不去講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則代表,將財務部和財務部員工一起刪除