1. 程式人生 > 資料庫 >MySQL 中使用子查詢總結

MySQL 中使用子查詢總結

文章目錄


1. 子查詢的分類

1.1. 非關聯子查詢

子查詢語句可以單獨執行,不需要引用包含語句中的任何內容。稱為非關聯子查詢。

1.1.1. 單行單列子查詢

子查詢語句返回一個單行單列的表,這種型別的子查詢稱為標量子查詢,並且可以位於常用運算子(=、<>、<、>、<=、>=)的任意一邊。

SELECT account_id, product_cd, cust_id, avail_balance FROM account WHERE account_id = (
	SELECT MAX(account_id) FROM account
);

SELECT account_id, product_cd, cust_id, avail_balance FROM account WHERE open_emp_id <> (
	SELECT e.emp_id FROM employee e 
	INNER JOIN branch b ON e.assigned_branch_id = b.branch_id
	WHERE e.title = 'Head Teller' AND b.city = 'Woburn'
);

1.1.2. 多行單列子查詢

子查詢語句返回一個多行單列的表。可以使用的預算符:INNOT INALLANY

IN運算子用於檢視是否能在一個表示式集合中找到某一個表示式。

# IN 運算子
SELECT emp_id, fname, lname, title FROM employee WHERE emp_id IN (
	SELECT superior_emp_id FROM employee
);

NOT IN運算子用於檢視是否某一個表示式不在一個表示式集合中。

# NOT IN 運算子
SELECT emp_id, fname, lname, title FROM employee WHERE emp_id NOT IN (
	SELECT superior_emp_id FROM employee WHERE superior_emp_id IS NOT NULL
);

ALL運算子用於將某單值與集合中的每個值進行比較。構建這種條件需要將一個比較運算子(=、<>、<、>、<=、>=)與ALL運算子配合使用。只有與集合中所有成員比較都成立時條件才為真。

# 查詢僱員ID與任何主管ID不同的所有僱員。這裡和上面使用 NOT IN 的結果是一樣的。
SELECT emp_id, fname, lname, title FROM employee WHERE emp_id <> ALL (
	SELECT superior_emp_id FROM employee WHERE superior_emp_id IS NOT NULL
);

NOT IN<> ALL運算子等效。一般直接使用NOT IN即可。
當使用NOT IN或者<> ALL運算子比較一個值與一個集合時,需要確保值集合中不包含NULL值,這是因為任何一個將值與NULL進行比較的行為都將產生未知的結果。

# 查詢可用餘額小於(Frank Tucker 所有賬戶 中最小余額的賬戶 的餘額)的賬戶。
SELECT account_id, product_cd, cust_id, avail_balance FROM account WHERE avail_balance < ALL(
	SELECT a.avail_balance FROM account a
	INNER JOIN individual i ON a.cust_id = i.cust_id
	WHERE i.fname = 'Frank' AND i.lname = 'Tucker'
);

ANY運算子允許將一個值與一個集合中的每個值進行比較,只要有一個成立,則條件為真。

# 查詢可用餘額大於(Frank Tucker 所有賬戶 中任意賬戶 的餘額)的賬戶。
SELECT account_id, product_cd, cust_id, avail_balance FROM account WHERE avail_balance > ANY(
	SELECT a.avail_balance FROM account a
	INNER JOIN individual i ON a.cust_id = i.cust_id
	WHERE i.fname = 'Frank' AND i.lname = 'Tucker'
);

IN= ANY運算子等效。一般直接使用IN即可。

1.1.3. 多列子查詢

子查詢語句返回一個多列的表。

SELECT account_id, product_cd, cust_id FROM account WHERE(open_branch_id, open_emp_id) IN (
	SELECT b.branch_id, e.emp_id FROM branch b 
	INNER JOIN employee e ON b.branch_id = e.assigned_branch_id
	WHERE b.name = 'Woburn Branch' AND (e.title = 'Teller' OR e.title = 'Head Teller')
);

上面語句等價的單列子查詢:

SELECT account_id, product_cd, cust_id FROM account WHERE open_branch_id = (
	SELECT branch_id FROM branch WHERE name = 'Woburn Branch' AND open_emp_id IN (
		SELECT emp_id FROM employee WHERE title = 'Teller' OR title = 'Head Teller'
	)
);

1.2. 關聯子查詢

關聯子查詢依賴包含語句並引用其一列或者多列。與非關聯子查詢不同,關聯子查詢不是在包含語句執行之前一次執行完畢,而是為每一個候選行執行一次。

#計算每個客戶的賬戶數,檢索出那些擁有兩個賬戶的客戶
#先從 customer 表中檢索出客戶記錄,接著為每個客戶執行一次子查詢,每次執行都要向子查詢傳遞 c.cust_id,若子查詢返回2,則滿足條件,該行將被加入到結果集。
SELECT c.cust_id, c.cust_type_cd, c.city FROM customer c WHERE 2 = (
	SELECT COUNT(*) FROM account a WHERE a.cust_id = c.cust_id
);
#檢索所有賬戶總額在5000到10000之間的所有客戶。
SELECT c.cust_id, c.cust_type_cd, c.city FROM customer c WHERE (
	SELECT SUM(a.avail_balance) FROM account a WHERE a.cust_id = c.cust_id
) BETWEEN 5000 AND 10000;

EXISTSNOT EXISTS運算子,會檢查子查詢能否返回至少一行,所以包含查詢的條件只需要知道子查詢返回的結果是多少行,而與結果的具體內容無關,所以這裡可以直接使用SELECT 1

#EXISTS ,查詢2000-1-15日有交易記錄的賬戶
SELECT a.account_id, a.product_cd, a.cust_id, a.avail_balance FROM account a WHERE EXISTS (
	SELECT 1 FROM `transaction` t WHERE t.account_id = a.account_id AND t.txn_date = '2000-1-15'
);

#NOT EXISTS ,查詢所有非商業客戶
SELECT a.account_id, a.product_cd, a.cust_id FROM account a WHERE NOT EXISTS (
	SELECT 1 FROM business b WHERE b.cust_id = a.cust_id
);

更新資料,將最新的交易時間更新到 account 表中。

UPDATE account a SET a.last_activity_date = (
	SELECT MAX(t.txn_date) FROM `transaction` t WHERE t.account_id = a.account_id
)WHERE EXISTS (
	SELECT 1 FROM `transaction` t WHERE t.account_id = a.account_id
);

刪除資料,將沒有職員的部門刪掉。

DELETE FROM department WHERE NOT EXISTS (
	SELECT 1 FROM employee WHERE employee.dept_id = department.dept_id
);

注意,在MySQLDELETE語句使用關聯子查詢時,不能使用表別名,會報語法錯誤。

mysql> DELETE FROM department d WHERE NOT EXISTS (
	SELECT 1 FROM employee e WHERE e.dept_id = d.dept_id
);
#提示如下錯誤
1064 - You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'd WHERE NOT EXISTS (
	SELECT 1 FROM employee e WHERE e.dept_id = d.dept_id
)' at line 1
mysql> 

2. 子查詢的使用

2.1. 作為資料來源

子查詢在FROM子句中使用必須是非關聯的,它們首先執行,然後一直保留在記憶體中直至包含查詢執行完畢。

#查詢每個部門的僱員人數
SELECT d.dept_id, d.`NAME`, e_cnt.how_many FROM department d 
INNER JOIN (
	SELECT dept_id, COUNT(*) how_many FROM employee GROUP BY dept_id
) e_cnt ON d.dept_id = e_cnt.dept_id;

除了使用查詢總結現有資料,使用子查詢還可以生成資料庫中不存在的資料。

#按照賬戶餘額對客戶進行分組,但是這些組的定義根本沒有儲存在資料庫中。需要用子查詢進行生成。
SELECT `groups`.name, COUNT(*) num_customers FROM (
	#使用子查詢生成臨時表
	SELECT SUM(a.avail_balance) cust_balance FROM account a 
	INNER JOIN product p ON a.product_cd = p.product_cd 
	WHERE p.product_type_cd = 'ACCOUNT' GROUP BY a.cust_id
) cust_rollup 
INNER JOIN (
	#使用子查詢生成臨時表,這裡注意groups一定要加撇號,否則會報語法錯誤(因為groups是8.0版本新增的保留字)
	SELECT 'Small Fry' name, 0 low_limit, 4999.99 high_limit
	UNION ALL
	SELECT 'Average Joes' name, 5000 low_limit, 9999.99 high_limit
	UNION ALL
	SELECT 'Heavy Hitters' name, 10000 low_limit, 9999999.99 high_limit
) `groups` ON cust_rollup.cust_balance BETWEEN `groups`.low_limit AND `groups`.high_limit
GROUP BY `groups`.name;

當然,我們可以直接新建一張表來記錄這些原本不存在的資料,但是這樣時間長了就會發現資料庫因為這些特殊目的而建的表變得很多,並且雜亂,難以維護。所以還是對於這種類似需求,還是直接使用子查詢的好。堅持這樣一個原則:僅當有明確的商業需求儲存這些新資料時才能新增相應的新表到資料庫

#根據賬戶型別,開戶僱員,及開戶行分組。對所有儲蓄賬戶餘額求和
SELECT p.`name` product, b.`name` branch, CONCAT(e.fname,' ',e.lname) name, account_groups.tot_deposits FROM (
	SELECT product_cd, open_branch_id, open_emp_id, SUM(avail_balance) tot_deposits 
	FROM account GROUP BY product_cd, open_branch_id, open_emp_id
) account_groups
INNER JOIN employee e ON e.emp_id = account_groups.open_emp_id
INNER JOIN branch b ON b.branch_id = account_groups.open_branch_id
INNER JOIN product p ON p.product_cd = account_groups.product_cd
WHERE p.product_type_cd = 'ACCOUNT' ORDER BY 1,2;

#等價的SQL語句,這裡增加了排序
SELECT p.`name` product, b.`name` branch, CONCAT(e.fname,' ',e.lname) name, SUM(avail_balance) tot_deposits 
FROM account a 
INNER JOIN employee e ON e.emp_id = a.open_emp_id
INNER JOIN branch b ON b.branch_id = a.open_branch_id
INNER JOIN product p ON p.product_cd = a.product_cd
WHERE p.product_type_cd = 'ACCOUNT'
GROUP BY p.`name`, b.`name`, e.fname, e.lname ORDER BY 1,2;

2.2. 作為過濾條件

#查詢開戶最多的僱員
SELECT open_emp_id, COUNT(*) how_many FROM account 
GROUP BY open_emp_id HAVING COUNT(*) = (
	SELECT MAX(emp_cnt.how_many) FROM (
		SELECT COUNT(*) how_many FROM account GROUP BY open_emp_id
	) emp_cnt
);

2.3. 作為表示式生成器

單行單列子查詢,也就是標量子查詢,除了可以用於過濾條件中,還能用在表示式可以出現的任何位置,包括SELECT子句和ORDER BY子句,以及INSERT語句中的VALUES子句。

SELECT子句中使用子查詢。

#根據賬戶型別,開戶僱員,及開戶行分組。對所有儲蓄賬戶餘額求和
SELECT all_prods.product, all_prods.branch, all_prods.name, all_prods.tot_deposits
FROM (
	SELECT (
		SELECT p.name FROM product p WHERE p.product_cd = a.product_cd AND  p.product_type_cd = 'ACCOUNT'
	) product,
	(
		SELECT b.name FROM branch b WHERE b.branch_id = a.open_branch_id
	) branch,
	(
		SELECT CONCAT(e.fname,' ',e.lname) name FROM employee e WHERE e.emp_id = a.open_emp_id
	) name,
	SUM(a.avail_balance) tot_deposits
	FROM account a
	GROUP BY a.product_cd, a.open_branch_id, a.open_emp_id
) all_prods
WHERE all_prods.product IS NOT NULL
ORDER BY 1,2;

ORDER BY子句中使用子查詢。

#查詢僱員資料,根據老闆的姓和僱員的姓排序。
SELECT emp.emp_id, CONCAT(emp.fname, ' ', emp.lname) emp_name ,(
	SELECT CONCAT(boss.fname, ' ', boss.lname) FROM employee boss WHERE boss.emp_id = emp.superior_emp_id
) boss_name
FROM employee emp WHERE emp.superior_emp_id IS NOT NULL
ORDER BY (
	SELECT boss.lname FROM employee boss WHERE boss.emp_id = emp.superior_emp_id
), emp.lname;

除了上面的關聯標量子查詢,還可以使用非關聯標量子查詢為INSERT子句生成值。

#生成一個新的賬戶
INSERT INTO `account` (`account_id`, `product_cd`, `cust_id`, `open_date`, `last_activity_date`, `status`, `open_branch_id`, `open_emp_id`, `avail_balance`, `pending_balance`) 
VALUES (
	NULL, 
	(SELECT product_cd FROM product WHERE name = 'savings account'), 
	(SELECT cust_id FROM customer WHERE fed_id = '555-55-5555'), 
	'2000-01-15',
	'2005-01-04',
	'ACTIVE', 
	(SELECT branch_id FROM branch WHERE name = 'Quincy Branch'), 
	(SELECT emp_id FROM employee WHERE lname = 'Portman' AND fname = 'Frank'), 
	0, 
	0
);

這種方法有一個缺點,當插入的列允許為NULL時,即使子查詢不能反回值,INSERT語句也會成功,此時該欄位會被置為NULL,可能偏離了我們的預期。

3. 子查詢的總結

  • 子查詢返回的結果可以是單行單列,多行單列,多行多列。
  • 子查詢可以獨立於包含語句(非關聯子查詢)。
  • 子查詢可以引用包含語句中的一行或者多行(關聯子查詢)。
  • 子查詢可以用於條件中,這些條件使用比較運算子以及其他特殊目的的運算子(innot inexistsnot exists)。
  • 子查詢可以出現在selectupdatedeleteinsert語句中。
  • 子查詢產生的結果集可以與其他表或者其他子查詢進行連線。
  • 子查詢可以生成值來填充表或者查詢結果集中的一些列。
  • 子查詢可以用於查詢中的selectfromwherehavingorder by子句。