一. 前言
該篇文章基於之前 https://www.cnblogs.com/yaopengfei/p/7182230.html 的基礎上進行補充修改。
1. 簡介
就查詢而言,可以簡單的分為:單表查詢 和 多表查詢。
單表查詢包括:簡單查詢、過濾查詢、結果排序、分頁查詢、聚集函數。
多表查詢包括:笛卡爾積、外鍵約束、內連接查詢、外鏈接查詢、自連接查詢。
2. 數據准備
(1). 用到的表:
產品表(product)。包括:主鍵id、產品名稱(productName)、分類編號(dir_id)、零售價(salePrice)、供應商(supplier)、品牌(brand)、折扣(cutoff)、成本價(costPrice)。
產品分類編號表( productdir)。 包括:主鍵id、編號名稱( dirName)、父id( parent_id) 。
產品庫存表( productstock)。包括:主鍵id、產品id( product_id )、庫存數量( storeNum)、上次進庫時間(lastIncomeDate)、上次出庫時間( lastOutcomeDate)、預警數量(warningNum)。
(2). 對應的字段類型,如下圖:
(3). 用到的SQL語句:
product表

CREATE TABLE `product` ( `id` bigint(11) NOT NULL AUTO_INCREMENT, `productName` varchar(50) DEFAULT NULL, `dir_id` bigint(11) DEFAULT NULL, `salePrice` double(10,2) DEFAULT NULL, `supplier` varchar(50) DEFAULT NULL, `brand` varchar(50) DEFAULT NULL, `cutoff` double(2,2) DEFAULT NULL, `costPrice` double(10,2) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=MyISAM AUTO_INCREMENT=21 DEFAULT CHARSET=utf8; -- ---------------------------- -- Records of product -- ---------------------------- INSERT INTO `product` VALUES ('1', '羅技M90', '3', '90.00', '羅技', '羅技', '0.50', '35.00'); INSERT INTO `product` VALUES ('2', '羅技M100', '3', '49.00', '羅技', '羅技', '0.90', '33.00'); INSERT INTO `product` VALUES ('3', '羅技M115', '3', '99.00', '羅技', '羅技', '0.60', '38.00'); INSERT INTO `product` VALUES ('4', '羅技M125', '3', '80.00', '羅技', '羅技', '0.90', '39.00'); INSERT INTO `product` VALUES ('5', '羅技木星軌跡球', '3', '182.00', '羅技', '羅技', '0.80', '80.00'); INSERT INTO `product` VALUES ('6', '羅技火星軌跡球', '3', '349.00', '羅技', '羅技', '0.87', '290.00'); INSERT INTO `product` VALUES ('7', '羅技G9X', '3', '680.00', '羅技', '羅技', '0.70', '470.00'); INSERT INTO `product` VALUES ('8', '羅技M215', '2', '89.00', '羅技', '羅技', '0.79', '30.00'); INSERT INTO `product` VALUES ('9', '羅技M305', '2', '119.00', '羅技', '羅技', '0.82', '48.00'); INSERT INTO `product` VALUES ('10', '羅技M310', '2', '135.00', '羅技', '羅技', '0.92', '69.80'); INSERT INTO `product` VALUES ('11', '羅技M505', '2', '148.00', '羅技', '羅技', '0.92', '72.00'); INSERT INTO `product` VALUES ('12', '羅技M555', '2', '275.00', '羅技', '羅技', '0.88', '140.00'); INSERT INTO `product` VALUES ('13', '羅技M905', '2', '458.00', '羅技', '羅技', '0.88', '270.00'); INSERT INTO `product` VALUES ('14', '羅技MX1100', '2', '551.00', '羅技', '羅技', '0.76', '300.00'); INSERT INTO `product` VALUES ('15', '羅技M950', '2', '678.00', '羅技', '羅技', '0.78', '320.00'); INSERT INTO `product` VALUES ('16', '羅技MX Air', '2', '1299.00', '羅技', '羅技', '0.72', '400.00'); INSERT INTO `product` VALUES ('17', '羅技G1', '4', '155.00', '羅技', '羅技', '0.80', '49.00'); INSERT INTO `product` VALUES ('18', '羅技G3', '4', '229.00', '羅技', '羅技', '0.77', '96.00'); INSERT INTO `product` VALUES ('19', '羅技G500', '4', '399.00', '羅技', '羅技', '0.88', '130.00'); INSERT INTO `product` VALUES ('20', '羅技G700', '4', '699.00', '羅技', '羅技', '0.79', '278.00');
productdir表

CREATE TABLE `productdir` ( `id` bigint(11) NOT NULL auto_increment, `dirName` varchar(30) default NULL, `parent_id` bigint(11) default NULL, PRIMARY KEY (`id`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8; -- ---------------------------- -- Records -- ---------------------------- INSERT INTO `productdir` VALUES ('1', '鼠標', null); INSERT INTO `productdir` VALUES ('2', '無線鼠標', '1'); INSERT INTO `productdir` VALUES ('3', '有線鼠標', '1'); INSERT INTO `productdir` VALUES ('4', '游戲鼠標', '1');
productstock表

CREATE TABLE `productstock` ( `id` bigint(11) NOT NULL auto_increment, `product_id` bigint(11) default NULL, `storeNum` int(10) default NULL, `lastIncomeDate` datetime default NULL, `lastOutcomeDate` datetime default NULL, `warningNum` int(10) default NULL, PRIMARY KEY (`id`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8; -- ---------------------------- -- Records -- ---------------------------- INSERT INTO `productstock` VALUES ('1', '1', '182', '2015-03-12 20:33:00', '2015-03-12 20:33:04', '20'); INSERT INTO `productstock` VALUES ('2', '2', '27', '2015-03-02 20:33:28', '2015-03-09 20:33:40', '20'); INSERT INTO `productstock` VALUES ('3', '3', '89', '2015-02-28 20:34:13', '2015-03-12 20:34:19', '20'); INSERT INTO `productstock` VALUES ('4', '5', '19', '2015-03-01 20:34:43', '2015-03-12 20:34:48', '20'); INSERT INTO `productstock` VALUES ('5', '6', '3', '2015-02-01 20:35:12', '2015-03-02 20:35:16', '5'); INSERT INTO `productstock` VALUES ('6', '7', '2', '2015-02-02 20:35:59', '2015-02-27 20:36:05', '3'); INSERT INTO `productstock` VALUES ('7', '8', '120', '2015-03-12 20:36:31', '2015-03-12 20:36:33', '20'); INSERT INTO `productstock` VALUES ('8', '9', '58', '2015-03-02 20:36:50', '2015-03-12 20:36:53', '20'); INSERT INTO `productstock` VALUES ('9', '11', '28', '2015-03-02 20:37:12', '2015-03-12 20:37:15', '20'); INSERT INTO `productstock` VALUES ('10', '12', '8', '2015-03-02 20:37:35', '2015-03-09 20:37:38', '5'); INSERT INTO `productstock` VALUES ('11', '13', '3', '2015-03-02 20:37:58', '2015-03-12 20:38:01', '5'); INSERT INTO `productstock` VALUES ('12', '14', '6', '2015-03-02 20:38:20', '2015-03-07 20:38:23', '5'); INSERT INTO `productstock` VALUES ('13', '15', '2', '2015-02-02 20:38:38', '2015-02-24 20:38:44', '5'); INSERT INTO `productstock` VALUES ('14', '16', '3', '2015-02-02 20:39:05', '2015-02-06 20:39:09', '3'); INSERT INTO `productstock` VALUES ('15', '17', '49', '2015-03-02 20:39:36', '2015-03-12 20:39:40', '20'); INSERT INTO `productstock` VALUES ('16', '18', '14', '2015-03-02 20:39:57', '2015-03-09 20:40:01', '10'); INSERT INTO `productstock` VALUES ('17', '20', '7', '2015-03-02 20:40:22', '2015-03-03 20:40:25', '5');
3. 經典SQL的執行順序
二. 單表查詢
1. 簡單查詢
a. 消除結果中的重復數據,用 DISTINCT 關鍵字。
b. 數學邏輯運算符(+ - * / ),運算符的優先級為:
(1). 乘除高於加減。
(2). 同級運算從左到右。
(3). 括號的優先級最高。
c. 設置列名的別名。
(1). 改變列的標題頭,用 as 關鍵字,可以省略。
(2). 用於表示計算結果的含義。
(3). 如果別名中有特殊字符,或者輕質大小寫敏感,或有空格是,都需要加雙引號。
#1.需求:查詢所有貨品信息 select * from product #2.查詢所有貨品的id,productName,salePrice select id,productName,salePrice from product #3 需求:查詢商品的分類編號。 SELECT DISTINCT dir_id FROM product
#需求:查詢所有貨品的id,名稱和批發價(批發價=賣價*折扣) SELECT id,productName,salePrice*cutoff FROM product #需求:查詢所有貨品的id,名稱,和各進50個的成本價(成本=costPirce) SELECT id,productName,costPrice*50 FROM product #需求:查詢所有貨品的id,名稱,各進50個,並且每個運費1元的成本 SELECT id,productName,(costPrice+1)*50 FROM product
SELECT id,productName,(costPrice+1)*50 as pf FROM product SELECT id,productName,(costPrice+1)*50 pf FROM product SELECT id,productName,(costPrice+1)*50 "p f" FROM product
2. 過濾查詢
a. SQL語句的執行順序: FROM → WHERE → SELECT → ORDER BY。
b. 邏輯運算符:
(1). AND (&&) : 組合條件都為true,返回true。
(2). OR ( || ) : 組合條件之一為true, 就返回true。
(3). NOT ( ! ) : 組合條件都為false,返回true。
需求: 選擇id,貨品名稱,批發價在300-400之間的貨品 SELECT id productName,salePrice FROM product WHERE salePrice >=300 AND salePrice<=400 需求: 選擇id,貨品名稱,分類編號為2,4的所有貨品 SELECT id productName,salePrice FROM product WHERE dir_id=3 OR dir_id<=4 需求: 選擇id,貨品名詞,分類編號不為2的所有商品 SELECT id productName,salePrice FROM product WHERE dir_id!=2 SELECT id productName,salePrice FROM product WHERE NOT dir_id=2 需求: 選擇id,貨品名稱,分類編號的貨品零售價大於等於250或者是成本大於等於200 SELECT id,productName,dir_id,salePrice,costPrice from product WHERE salePrice >=250 or costPrice>=200
c. 比較運算符:
(1). 等於: =
(2). 大於: >
(3). 大於或等於: >=
(4). 小於:<
(5). 小於或等於:<=
(6). 不等於: != 或 <>
注意:運算符的優先級由低到高:所有比較運算符 → NOT → AND → OR → 括號。
# 需求: 查詢貨品零售價為119的所有貨品信息. SELECT * FROM product WHERE salePrice=119 #需求: 查詢貨品名為羅技G9X的所有貨品信息. SELECT * FROM product WHERE productName='羅技G9X' SELECT * FROM product WHERE productName='羅技g9x' SELECT * FROM product WHERE BINARY productName='羅技g9x' #二進制區分大小寫 #需求: 查詢貨品名 不為 羅技G9X的所有貨品信息. SELECT * FROM product WHERE productName!='羅技G9X' SELECT * FROM product WHERE productName<>'羅技G9X' 需求: 查詢分類編號不等於2的貨品信息 SELECT * FROM product WHERE dir_id!=2 需求: 查詢貨品名稱,零售價小於等於200的貨品 SELECT productName FROM product WHERE salePrice<=200 需求: 查詢id,貨品名稱,批發價大於350的貨品 SELECT id,productName,salePrice *cutoff FROM product WHERE salePrice *cutoff >350 思考:使用where后面使用別名不行,總結select和where的執行順序(思考下面兩個都不好用的原因, 第二個是因為別名只能放在列名的后面) SELECT id,productName,salePrice *cutoff pf FROM product WHERE pf >350 SELECT id,productName, pf FROM product WHERE salePrice *cutoff pf >350
SELECT id,productName FROM product WHERE (NOT productName LIKE '%M%' AND salePrice > 100) OR (dir_id = 2)
d. 范圍查詢:BETWEEN AND 表示某一值域范圍的記錄。
格式: SELECT * FROM 表名 WHERE 列名 BETWEEN minvalue AND maxvalue; (兩邊都為閉區間)。
需求: 選擇id,貨品名稱,批發價在300-400之間的貨品 SELECT id,productName,salePrice FROM product WHERE salePrice BETWEEN 300 AND 400 需求: 選擇id,貨品名稱,批發價不在300-400之間的貨品 SELECT id,productName,salePrice FROM product WHERE NOT salePrice BETWEEN 300 AND 400
e. 集合查詢:使用IN運算符,判斷列的值是否在指定的集合中。
格式: WHERE 列名 IN ( 值1, 值2,....) 。
需求:選擇id,貨品名稱,分類編號為2,4的所有貨品 SELECT id productName,salePrice FROM product WHERE dir_id=2 OR dir_id=4 SELECT id productName,salePrice FROM product WHERE dir_id IN(2,4)
需求:選擇id,貨品名稱,分類編號不為2,4的所有貨品
SELECT id productName,salePrice FROM product WHERE NOT dir_id IN(2,4)
f. 空值判斷:IS NULL,判斷列的值是否為空。
格式:WHERE 列名 IS NULL。
--需求:查詢商品名為NULL的所有商品信息。 SELECT * FROM product WHERE productName IS NULL
g. 模糊查詢:使用 LIKE 運算符執行通配查詢。
(1). %: 表示零或多個字符。
(2). _ : 表示一個字符。
需求: 查詢id,貨品名稱,貨品名稱匹配'%羅技M9_' SELECT id, productName FROM product WHERE productName LIKE '%羅技M9_' 需求: 查詢id,貨品名稱,分類編號,零售價大於等於200並且貨品名稱匹配'%羅技M1__' SELECT id,productName,dir_id FROM product WHERE productName LIKE '%羅技M9__' AND salePrice >=200
3. 結果排序
使用ORDER BY 子句進行排序,ASC: 升序 (默認,可省),DESC:降序;ORDER BY 子句出現在SELECT語句最后。
格式: SELECT *
FROM table_name
WHERE 條件
ORDER BY 列名1 [ASC/DESC] , 列名2 [ASC/DESC] .... 。
注意: 不能對使用了引號的別名進行排序。
#按照某一列來排序: #需求:選擇id,貨品名稱,分類編號,零售價並且按零售價降序排序 SELECT id,productName,dir_id,salePrice from product ORDER BY salePrice DESC #按多列排序: #需求: 選擇id,貨品名稱,分類編號,零售價先按分類編號排序,再按零售價排序 SELECT id,productName,dir_id,salePrice from product ORDER BY dir_id ASC,salePrice DESC #列的別名排序: #需求:查詢M系列並按照批發價排序(加上別名) SELECT * ,salePrice * cutoff FROM product WHERE productName LIKE '%M%' ORDER BY salePrice * cutoff SELECT * ,salePrice * cutoff pf FROM product WHERE productName LIKE '%M%' ORDER BY pf #需求:查詢分類為2並按照批發價排序(加上別名) SELECT * ,salePrice * cutoff "pf" FROM product WHERE dir_id=2 ORDER BY "pf"
4. 分頁
(1). 真分頁(物理分頁、數據庫分頁):每次分頁的時候,都從數據庫中截取指定條數的數據。優點:不會內存溢出。缺點:復雜,翻頁比較慢。
假分頁(邏輯分頁、內存分頁):一次性把數據全部查出來,存在內存里,翻頁的時候,從內存中截取指定條數即可。優點:簡單,翻頁快。缺點:若數據過多,可能內存溢出。
(2). MySQL中分頁的寫法:
格式1:LIMIT beginIndex, pageSize ;
其中,beginIndex,表示從哪一個索引位置開始截取數據(索引是從0開始), pageSize,表示每頁顯示最多的條數
分頁語句:SELECT * FROM 表名 LIMIT (currentPage-1)*pageSize,pageSize .
currentPage為當前頁數,通常是前端傳遞過來的。
-- 分頁 (獲取的是 第11,12,13 條數據) SELECT * FROM product LIMIT 10,3
格式2:LIMIT N ; 代表取前N條數據。 (等價於: LIMIT 0,N )
--獲取的是前10條數據 SELECT * FROM product LIMIT 10
格式3:LIMIT M,-1 ; 代表獲取第 M+1條 到最后的數據 ( MySQL5.7中已經不支持這種寫法了)
--獲取第11條 到 最后的數據 SELECT * FROM product LIMIT 10,-1
格式4:LIMIT M OFFSET N ; 代表跨過N條數據,取M條數據
-- 獲取的是第 3,4 ,5條數據 select * from T_WxLoginUser LIMIT 2,3; -- 獲取的也是第 3,4 ,5條數據 (和上面的語句效果等價) select * from T_WxLoginUser LIMIT 3 OFFSET 2;
(3). SQLServer分頁:
方案一:利用ROW_NUMBER() over() 和 between and,進行分頁。ROW_NUMBER() over() 表示把該表按照某個字段進行排序,然后新生成一列,從1到n,如下圖:
over里的排序要晚於外層 where,group by,order by
會按照over里面的排序,新增一列(1----n),比如 newRow, 然后基於這一列,使用between and 進行區間獲取
可以將出來的數據進行排列1-----n,即多了一列
select *, ROW_NUMBER() over(order by userAge desc) as newRow from UserInfor
演變過程:
select *, ROW_NUMBER() over(order by userAge desc) as newRow from UserInfor select * from (select *, ROW_NUMBER() over(order by userAge desc) as newRow from UserInfor) as t select * from (select *, ROW_NUMBER() over(order by userAge desc) as newRow from UserInfor) as t where t.newRow between 1 and 2
方案2: 利用offset-fetch (SQLServer2012后開始支持),分頁實現的思路:
--在分頁實現中,使用Order By子句,按照指定的columns對結果集進行排序;
--使用offset子句跳過前N條:offset (@PageIndex-1)*@RowsPerPage rows;
--使用fetch子句取N條:fetch next @RowsPerPage rows only;
--跨過3行取剩下的 select * from UserInfor order by userAge desc offset 3 rows --跨過3行取剩下2行 select * from UserInfor order by userAge desc offset 3 rows fetch next 2 rows only
封裝成存儲過程:(這里基於方案一、方案二各一種,然后基於方案一寫了一個萬能的分頁)

-- 基於"方案一"的存儲過程分頁 if (exists (select * from sys.objects where name = 'FenYe1')) drop proc FenYe1 go create proc FenYe1( @pageSize int=3, --輸入參數:每頁的條數,默認值為2 @pageIndex int=1, --輸入參數:當前頁數,默認值為1 @totalCount int output, --輸出參數:總條數 @pageCount int output --輸出參數:總頁數 ) as select * from (select *, ROW_NUMBER() over(order by userAge desc) as newRow from UserInfor) as t where t.newRow between ((@pageIndex-1)*@pageSize)+1 and (@pageSize*@pageIndex); select @totalCount=COUNT(*) from UserInfor; set @pageCount=CEILING(@totalCount * 1.0 /@pageSize); --執行該分頁的存儲過程 declare @myTotalCount int, --聲明變量用來接收存儲過程中的輸出參數 @myPageCount int --聲明變量用來接收存儲過程中的輸出參數 exec FenYe1 2,1,@myTotalCount output,@myPageCount output; --每頁2條,求第1頁的數據 select @myTotalCount as '總條數',@myPageCount as '總頁數'; -- 基於"方案二"的存儲過程分頁 if (exists (select * from sys.objects where name = 'FenYe2')) drop proc FenYe2 go create proc FenYe2( @pageSize int=3, --輸入參數:每頁的條數,默認值為2 @pageIndex int=1, --輸入參數:當前頁數,默認值為1 @totalCount int output, --輸出參數:總條數 @pageCount int output --輸出參數:總頁數 ) as select * from UserInfor order by userAge desc offset (@pageIndex-1)*@pageSize rows fetch next @pageSize rows only; select @totalCount=COUNT(*) from UserInfor; set @pageCount=CEILING(@totalCount * 1.0 /@pageSize); --執行該分頁的存儲過程 declare @myTotalCount int, --聲明變量用來接收存儲過程中的輸出參數 @myPageCount int --聲明變量用來接收存儲過程中的輸出參數 exec FenYe2 4,2,@myTotalCount output,@myPageCount output; --每頁4條,求第2頁的數據 select @myTotalCount as '總條數',@myPageCount as '總頁數'; --基於"方案一"創建一個萬能表的分頁 if (exists (select * from sys.objects where name = 'WangNengFenYe')) drop proc WangNengFenYe go create proc WangNengFenYe( @TableName varchar(50), --表名 @ReFieldsStr varchar(200) = '*', --字段名(全部字段為*) @OrderString varchar(200), --排序字段(必須!支持多字段不用加order by) @WhereString varchar(500) =N'', --條件語句(不用加where) @PageSize int, --每頁多少條記錄 @PageIndex int = 1 , --指定當前為第幾頁 @TotalRecord int output --返回總記錄數 ) as begin --處理開始點和結束點 Declare @StartRecord int; Declare @EndRecord int; Declare @TotalCountSql nvarchar(500); Declare @SqlString nvarchar(2000); set @StartRecord = (@PageIndex-1)*@PageSize + 1 set @EndRecord = @StartRecord + @PageSize - 1 SET @TotalCountSql= N'select @TotalRecord = count(*) from ' + @TableName;--總記錄數語句 SET @SqlString = N'(select row_number() over (order by '+ @OrderString +') as rowId,'+@ReFieldsStr+' from '+ @TableName;--查詢語句 -- IF (@WhereString! = '' or @WhereString!=null) BEGIN SET @TotalCountSql=@TotalCountSql + ' where '+ @WhereString; SET @SqlString =@SqlString+ ' where '+ @WhereString; END --第一次執行得到 --IF(@TotalRecord is null) -- BEGIN EXEC sp_executesql @totalCountSql,N'@TotalRecord int out',@TotalRecord output;--返回總記錄數 -- END ----執行主語句 set @SqlString ='select * from ' + @SqlString + ') as t where rowId between ' + ltrim(str(@StartRecord)) + ' and ' + ltrim(str(@EndRecord)); Exec(@SqlString) END --執行 --對UserInfor表進行分頁,根據userAge排序,每頁4條,求第2頁的數據 declare @totalCount int exec WangNengFenYe 'UserInfor','*','userAge desc','',4,2,@totalCount output; select @totalCount as '總條數';--總記錄數。
5. 聚集函數
(1). COUNT : 統計結果的記錄數。
(2). MAX : 統計計算最大值。
(3). MIN : 統計最小值。
(4). SUM: 統計計算求和。
(5). AVG: 統計計算平均值。
需求:查詢所有商品平均零售價 SELECT AVG(salePrice) FROM product 需求:查詢商品總記錄數(注意在Java中必須使用long接收) SELECT COUNT(id) FROM product 需求:查詢分類為2的商品總數 SELECT COUNT(id) FROM product WHERE dir_id=2 需求:查詢商品的最小零售價,最高零售價,以及所有商品零售價總和 SELECT MIN(salePrice) 最小零售價,MAX(salePrice) 最高零售價,SUM(salePrice) 商品零售價總和 FROM product
三. 多表查詢
1. 笛卡爾積
多表查詢會產生笛卡爾積。 假設集合A={a,b},集合B={0,1,2},則兩個集合的笛卡爾積為{(a,0),(a,1),(a,2),(b,0),(b,1),(b,2)},實際運行環境下,應避免使用全笛卡爾集。
解決笛卡爾積最有效的方法:等值連接(是內連接的一種)。
-- 需求:查詢所有的貨品信息+對應的貨品分類信息 SELECT productName,dirName FROM product,productdir WHERE dir_id = productdir.id
2. 外鍵約束
(1). 主鍵約束(PRIMARY KEY): 約束在當前表中,指定列的值非空且唯一.
(2). 外鍵約束(FOREIGN KEY): A表中的外鍵列. A表中的外鍵列的值必須參照於B表中的某一列(B表主鍵).
注意:在MySQL中,InnoDB支持事務和外鍵.修改表的存儲引擎為InnDB。格式:ALTER TABLE 表名 ENGINE='InnoDB'。
3. 說明
(1) 主表:數據可以獨立存在,就是被參考的表。 productdir
(2). 從表:表中的數據,必須參照於主表的數據。product
注意:在刪除表的時候,先刪除從表,再刪除主表。
3. 多表查詢詳解
多表查詢包括三類:內連接查詢(隱式內連接和顯示內連接)、外連接查詢 (左外鏈接、右外鏈接、全鏈接 )、自連接查詢,各自的關系如下圖:
經典代碼分享:

/* 1 */ SELECT <select_list> FROM TableA A LEFT JOIN TableB B ON A.Key = B.Key; /* 2 */ SELECT <select_list> FROM TableA A RIGHT JOIN TableB B ON A.Key = B.Key; /* 3 */ SELECT <select_list> FROM TableA A INNER JOIN TableB B ON A.Key = B.Key; /* 4 */ SELECT <select_list> FROM TableA A LEFT JOIN TableB B ON A.Key = B.Key WHERE B.Key IS NULL; /* 5 */ SELECT <select_list> FROM TableA A RIGHT JOIN TableB B ON A.Key = B.Key WHERE A.Key IS NULL; /* 6 */ SELECT <select_list> FROM TableA A FULL OUTER JOIN TableB B ON A.Key = B.Key; /* MySQL不支持FULL OUTER JOIN這種語法 可以改成 1+2 */ SELECT <select_list> FROM TableA A LEFT JOIN TableB B ON A.Key = B.Key UNION SELECT <select_list> FROM TableA A RIGHT JOIN TableB B ON A.Key = B.Key; /* 7 */ SELECT <select_list> FROM TableA A FULL OUTER JOIN TableB B ON A.Key = B.Key WHERE A.Key IS NULL OR B.Key IS NULL; /* MySQL不支持FULL OUTER JOIN這種語法 可以改成 4+5 */ SELECT <select_list> FROM TableA A LEFT JOIN TableB B ON A.Key = B.Key WHERE B.Key IS NULL; UNION SELECT <select_list> FROM TableA A RIGHT JOIN TableB B ON A.Key = B.Key WHERE A.Key IS NULL;
注:在MySQL中,join、inner join、cross join含義相同,都是用於等值連接的,另外MySQL中沒有 full outer join,可以通過left join + right join +union來實現相應需求。
(1). 內連接
內連接查詢出來的結果是多表交叉共有的,分為:隱式內連接和顯示內連接。 還有一種划分方式,內連接分為:等值連接和非等值連接。
PS:不管是隱式內連接還是顯示內連接, 當寫法是 A.列 = B.列 的時候,就是等值連接;但如果寫成 A.列!=B.列 就是非等值連接,所以說等值連接是內連接的子級。【如有出入,歡迎探討】
A. 隱式內連接 (關鍵字 =)
B. 顯示內連接 (關鍵字 inner join on,inner可以省略,推薦寫法)
PS 在做等值連接的時候,若A表中的和B表中的列相同,可以縮寫為:(MySQL特有)
需求:查詢所有商品的名稱和分類名稱: 隱式內連接: SELECT p.productName,pd.dirName FROM product p,productdir pd WHERE p.dir_id = pd.id 顯示內連接: SELECT p.productName,pd.dirName FROM product p INNER JOIN productdir pd ON p.dir_id = pd.id 顯示內連接: SELECT p.productName,pd.dirName FROM product p JOIN productdir pd ON p.dir_id = pd.id 需求: 查詢零售價大於200的無線鼠標 SELECT * FROM product p,productdir pd WHERE p.dir_id = pd.id AND p.salePrice >200 And pd.dirName ='無線鼠標' SELECT * FROM product p JOIN productdir pd on p.dir_id = pd.id WHERE p.salePrice >200 And pd.dirName ='無線鼠標' 需求: 查詢每個貨品對應的分類以及對應的庫存 SELECT p.productName,pd.dirName,ps.storeNum FROM product p,productdir pd,productstock ps WHERE p.dir_id = pd.id AND p.id = ps.product_id SELECT p.productName,pd.dirName,ps.storeNum FROM product p JOIN productdir pd on p.dir_id = pd.id JOIN productstock ps on p.id = ps.product_id 需求: 如果庫存貨品都銷售完成,按照利潤從高到低查詢貨品名稱,零售價,貨品分類(三張表). select *, (p.salePrice - p.costPrice) * ps.storeNum lirun FROM product p,productdir pd,productstock ps WHERE p.dir_id = pd.id AND p.id = ps.product_id ORDER BY lirun DESC select *, (p.salePrice - p.costPrice) * ps.storeNum lirun FROM product p JOIN productdir pd on p.dir_id = pd.id JOIN productstock ps on p.id = ps.product_id ORDER BY lirun DESC
(2). 外連接查詢
左外連接:查詢出JOIN左邊表的全部數據,JOIN右邊的表不匹配的數據用NULL來填充,關鍵字:left join
右外連接:查詢出JOIN右邊表的全部數據,JOIN左邊的表不匹配的數據用NULL來填充,關鍵字:right join
全連接: (左連接 - 內連接) +內連接 + (右連接 - 內連接) = 左連接+右連接-內連接 關鍵字:full join
PS: A LEFT JOIN B 等價於 B RIGHT JOIN A
注意:無論是左外連接還是右外連接,都要注意 1對多 的情況,左外鏈接的數據數量取決於左表,右外鏈接的數據數量取決於右表。
-- 外鏈接 # 查詢所有商品的名稱和分類名稱 左連接: SELECT * FROM product p LEFT JOIN productdir pd ON p.dir_id = pd.id -- 等價於 SELECT * FROM productdir pd RIGHT JOIN product p ON p.dir_id = pd.id 右連接: SELECT * FROM product p RIGHT JOIN productdir pd ON p.dir_id = pd.id
(3). 自連接查詢:把一張表看成兩張表來做查詢
補充一個例子:
S表(學生表):sno(學號)、sname(學生姓名)、sage、ssex。
求學號比 WANG 同學大,而年齡比他小的學生姓名。
--寫法1 select sname from S where sno > (select sno from S where sname = 'WANG') and sage < (select sage from S where sname ='WANG'); --寫法2 (自連接) select S2.sname from S as S1, S as S2 where S1.sno= S2.sno and S1.sname='WANG' and S2.sno>S1.sno and S2.sage < S1.sage;
四. 其它
1. group by 分組
SQL中的group by分組與linq、lambda完全不同,select中查詢的字段必須是group by語句的后面字段,作為分組的依據;如果要使用其他字段,其他字段必須被包含在聚合函數中。常見的聚合函數有:count、sum、avg、min、max。
group by 后面通常會加having,表示對分組后的數據進行限制。
案例1
--根據用戶性別分類,統計不同性別年齡最大值,年齡總和,用戶數量。 select userSex,MAX(userAge) as MaxAge,SUM(userAge) as TotalAges,count(*) as Num from Sys_UserInfor group by userSex
案例2
查詢只選修了一門課程的學員姓名和年齡
--S表(學生表):sno(學號)、sname(學生姓名)、sage、ssex --C表(課程表):cno(課程號)、cname(課程名)、teacher(老師名) --SC表(成績表):sno、cno、grade(成績) select sname,sage from S where sno in (select S.sno from S inner join Sc on S.sno =Sc.sno group by Sc.sno having count(*)>1);
案例3
要求輸出每個用戶的基本信息、所在的群總數、自己創建的群數、參與別人創建的群數,沒有的話數量顯示0。
用到的表:
select t1.userPhone,t1.userNickName,t1.userType,TotalNum=ISNULL(t5.TotalNum,0) ,num1=ISNULL(t3.num1,0), num2=(ISNULL(t5.TotalNum,0)-ISNULL(t3.num1,0)) from T_ChatUser as t1 left join ( select t2.groupOwnerId as groupOwnerId , count(*) as num1 from T_ChatGroup as t2 group by t2.groupOwnerId ) as t3 on t1.id =t3.groupOwnerId left join ( select t4.userId, count(*) as TotalNum from T_GroupUser as t4 group by t4.userId ) as t5 on t1.id =t5.userId order by t1.addTime desc
五. 實戰練習
1. 練習1
--相關表說明
--S表(學生表):sno(學號)、sname(學生姓名)、sage、ssex
--C表(課程表):cno(課程號)、cname(課程名)、teacher(老師名)
--Sc表(成績表):sno、cno、grade(成績)
練習題分享:

--相關表說明 --S表(學生表):sno(學號)、sname(學生姓名)、sage、ssex --C表(課程表):cno(課程號)、cname(課程名)、teacher(老師名) --Sc表(成績表):sno、cno、grade(成績) --一. 測試一 --1. 求年齡大於所有女同學年齡的男學生姓名和年齡 select sname,sage from S where S.ssex='男' and S.sage >(select Max(sage) from S where ssex='女' ); select sname,sage from S where S.ssex='男' and S.sage > All(select sage from S where ssex='女'); --2.求年齡大於女同學平均年齡的男學生姓名和年齡。 select sname,sage from S where ssex='男' and sage >(select Avg(sage) from S where S.ssex='女'); --3.求成績為空值的學生學號和課程號 select sno,cno from Sc where Sc.grade is null; --4.檢索姓名以 WANG 打頭的所有學生的姓名和年齡。 select sname,sage from S where sname like 'WANG%'; --5.檢索學號比 WANG 同學大,而年齡比他小的學生姓名。 select sname from S where sno > (select sno from S where sname = 'WANG') and sage < (select sage from S where sname ='WANG'); --寫法2 select S2.sname from S as S1, S as S2 where S1.sno= S2.sno and S1.sname='WANG' and S2.sno>S1.sno and S2.sage < S1.sage; --6.統計每門課程的學生選修人數(超過 2 人的課程才統計),要求輸出課程號、選修人數,查詢結果按人數降序排列,若人數相同,按課程號升序排列。 select cno,count(*) as Num from Sc group by cno having count(*) >2 order by Num desc,cno asc; --7.求 王森莉 老師所授課程的每門課程的學生平均成績。 --顯示內連接 (關鍵字 inner join on,inner可以省略,推薦寫法) select Sc.cno,Avg(Sc.grade) from Sc JOIN C on Sc.cno = C.cno where C.teacher='王森莉' group by cno ; --隱式內連接 select Sc.cno,Avg(Sc.grade) from Sc , C where Sc.cno = C.cno and C.teacher='王森莉' group by cno ; --8.求選修 數學 課程的學生的平均年齡。 select Avg(sage) from S where S.sno in (select Sc.sno from Sc join C on Sc.cno=C.cno where C.cname='數學') --9.統計有學生選修的課程門數。 select count(distinct cno) from Sc; --測試二 --1. 查詢選修課程名稱為 數學 的學員學號和姓名 select sno,sname from S where sno in (select sno from Sc inner join C where C.cno = Sc.cno and C.cname='數學'); --2. 查詢選修課程編號為 01 的學員姓名和年齡 select S.sname,S.sage from S inner join Sc on S.sno =Sc.sno where Sc.cno ='01'; --3. 查詢不選修課程編號為 01 的學員姓名和年齡 select S.sname,S.sage from S inner join Sc on S.sno =Sc.sno where Sc.cno !='01'; --4. 查詢只選修了一門課程的學員姓名和年齡 select sname,sage from S where sno in (select S.sno from S inner join Sc on S.sno =Sc.sno group by Sc.sno having count(*)>1); --5. 查詢選修了課程的學員人數 select count(distinct sno) as '數量' from Sc; --6. 查詢選修課程超過5門的學員學號和姓名 select sno,sname from S where sno in (select S.sno from S inner join Sc on S.sno =Sc.sno group by Sc.sno having count(distinct Sc.cno)>5); --測試三 --1. 找出沒有選修過“孟家美”老師講授課程的所有學生姓名 select sname from S where sno not in (select Sc.sno from Sc inner join C on C.cno =Sc.cno where C.teacher='孟家美' ); --2. 列出有二門以上(含兩門)不及格課程的學生姓名及其平均成績 select S.sname, Avg(Sc.grade) from S inner join Sc on S.sno=Sc.sno where Sc.grade<60 group by Sc.sno,S.sname having count(distinct cno)>=2; --3. 列出既學過“ 01 ”號課程,又學過“ 02 ”號課程的所有學生姓名 select S.sname from S inner join Sc on S.sno=Sc.sno where Sc.cno='01' and S.sno in (select S.sno from S inner join Sc on S.sno=Sc.sno where Sc.cno='02'); --4. 列出“ 01 ”號課成績比“ 02 ”號課程成績高的所有學生的學號 select s1.sno from Sc s1 inner join Sc s2 on s1.sno = s2.sno where s1.cno='01' and s2.cno='02' and s1.grade >s2.grade; --5. 列出“ 01 ”號課成績比“ 02 ”號課成績高的所有學生的學號及其“ 1 ”號課和“ 2 ”號課的成績 select Sc1.sno,Sc1.grade as '01號課成績',Sc2.grade as '02號課成績' from Sc Sc1,Sc Sc2 where Sc1.sno =Sc2.sno and Sc1.cno='01' and Sc2.cno='02' and Sc1.grade >Sc2.grade; --S表(學生表):sno(學號)、sname(學生姓名)、sage、ssex --C表(課程表):cno(課程號)、cname(課程名)、teacher(老師名) --Sc表(成績表):sno、cno、grade(成績) --測試四 --1. 在基本表 SC 中修改 4 號課程的成績,若成績小於等於 75 分時提高 5% , 若成績大於 75 分時提高 4% (用兩個 UPDATE 語句實現)。 update Sc set grade=grade * 1.05 where grade <=75 and cno='4'; update Sc set grade=grade * 1.04 where grade >75 and cno='4'; --2. 把低於總平均成績的女同學成績提高 5% --注:不需要關聯查詢 update Sc set grade=grade * 1.05 where grade < (select Avg(grade) from Sc) and sno in (select sno from S where ssex='女'); --3. 把選修數據庫原理課不及格的成績全改為空值 --注:不需要關聯查詢 update Sc set grade=null where Sc.grade<60 and cno in (select cno from C where cname='數據庫原理'); --4. 把WANG 同學的學習選課和成績全部刪去。 --注:不需要關聯查詢 delete from Sc where sno in (select sno from S where sname='WANG'); --5. 在基本表 SC 中刪除尚無成績的選課元組。 delete from Sc where grade is null; --6. 在基本表 S 中檢索每一門課程成績都大於等於 80 分的學生學號、姓名和性別,並把檢索到的值送往另一個已存在的基本表 S1 (sno,sname,ssex) insert into S1(sno,sname,ssex) (select S.sno,S.sname,S.ssex from S inner join Sc on S.sno = Sc.sno group by Sc.sno having Min(Sc.grade>=80)); --或 INSERT INTO S1(sno,sname,ssex) SELECT sno,sname,ssex FROM S WHERE NOT EXISTS(SELECT * FROM Sc WHERE grade<80 AND S.sno = Sc.sno or S.sno = Sc.sno and grade is null) and sno in (select sno from Sc)
2.練習2
--相關表
--UserInfor表: id name age salary
--相關表
--Student_Info(學生表):sno、sname、sex、birthDate、homeAddress、remark
--Curriculum(課程表):cno、cname、cscore(學分)
--Grade(成績表):sno、cno、grade(成績)
練習題分享:(待補充)

--測試1 --相關表 --UserInfor表: id name age salary --1. 刪除姓名、年齡重復的記錄,只保留Id最大的一條. --分析:根據姓名、年齡分組,取出每組的Id最大值,然后將Id最大值之外的排除。 --錯誤寫法:不能先select出同一表中的某些值,再update這個表(在同一語句中),即不能依據某字段值做判斷再來更新某字段的值!!! delete from UserInfor where id not in (select Max(id) from UserInfor group by name,age); --正確寫法: delete from UserInfor where id not in (select * from (select Max(id) from UserInfor group by name,age) a); --測試2 --相關表 --Student_Info(學生表):sno、sname、sex、birthDate、homeAddress、remark --Curriculum(課程表):cno、cname、cscore(學分) --Grade(成績表):sno、cno、grade(成績) --1. 在GRADE表中查找80-90分的學生學號和分數(ok) select sno,grade from Grade where grade between '80' and '90'; --2.在GRADE 表中查找課程編號為003學生的平均分(ok) select Avg(grade) as '平均分' from Grade where cno ='003'; --3.在GRADE 表中查詢學習各門課程的人數(ok) select cno,count(*) from Grade group by cno; --4.查詢所有姓張的學生的學號和姓名(ok) select sno,sname from Student_Info where sname like '張%'; --5.查詢和學號’0001’的這位同學性別相同的所有同學的姓名和出生年月(ok) select sname,birthDate from Student_Info where sex =(select sex from Student_Info where sno='0001'); --6.查詢所有選修課程編號為0002 和0003的學生的學號、姓名和性別(ok) select S.sno,S.sname,S.sex from Student_Info S inner join Grade G on S.sno = G.sno where G.cno in ('0002','0003'); --7.查詢出學號為0001的學生的分數比0002號學生最低分高的課程的課程編號和分數(ok) select cno,grade from Grade where grade >(select Min(grade) from Grade where sno='0002') and sno='0001'; --8.查詢分數在80-90分的學生的學號、姓名、分數(ok) select S.sno,S.sname,G.grade from Student_Info S inner join Grade G on S.sno = G.sno where G.grade between 80 and 90; --9.查詢學習了’C語言’課程的學生學號、姓名和分數(ok) select S.sno,S.sname,G.grade from Student_Info S inner join Grade G on S.sno = G.sno inner join Curriculum C on G.cno=C.cno where C.cname='C語言'; --10.查詢所有學生的總成績,要求列出學號、姓名、總成績,沒有選課的學生總成績為空。 (有問題) select S.sno,S.sname, Sum(G.grade) as '總成績' from Student_Info S left join Grade G on S.sno = G.sno group by G.sno;
3. 練習3
--相關表
-- Course(課程表):course_id(課程號),name(課程名),teacher_id
-- Score(成績表):sid(學號)、course_id(課程號)、score(分數)
-- Student(學生表):sid、sname、birthDay
-- Teacher(老師表):teacher_id, name
-- Dept(部門表):dept_id,name
練習題分享:

--涉及的表 -- Course(課程表):course_id(課程號),name(課程名),teacher_id -- Score(成績表):sid(學號)、course_id(課程號)、score(分數) -- Student(學生表):sid、sname、birthDay -- Teacher(老師表):teacher_id, name -- Dept(部門表):dept_id,name --1. 查看所有英語成績超過數學成績的學生的學號和姓名 Select aa.sid,aa.sname from (Select a1.sid,sname,score from Student a1 inner join Score b1 on a1.sid=b1.sid inner join Course c1 on c1.course_id=b1.course_id where c1.`name`='數學') aa inner join (Select a2.sid,sname, score from Student a2 inner join score b2 on a2.sid=b2.sid inner join Course c2 on c2.course_id=b2.course_id where c2.`name`='英語') bb on aa.sid=bb.sid Where aa.score < bb.score; --2. 查看平均成績大於等於60的所有學生的姓名和平均成績 select Student.sid,Student.sname,Avg(Score.score) from Student inner join Score on Student.sid = Score.sid GROUP BY Student.sid,Student.sname HAVING Avg(Score.score) >=60; --3. 查詢所有同學的學號、姓名、選課數、總成績 select S1.sid,S1.sname,count(S2.course_id),sum(S2.score) from Student S1 inner join Score S2 on S1.sid = S2.sid group by S1.sid,S1.sname; --4. 查詢姓zhang的老師的個數 select count(*) from Teacher where name like 'zhang%'; --5. 查詢沒有學過zhangsan老師課程的學生的學號和姓名 select sid ,sname from Student where sid not in (select Student.sid from Student inner join Score on Student.sid=Score.sid inner join Course on Course.course_id=Score.course_id inner join Teacher on Course.course_id=Teacher.teacher_id where Teacher.`name`='zhangsan' ); --6. 查詢既學過英語也學過語文的學生的學號和姓名 Select a.sid,sname from Student a inner join Score b on a.sid=b.sid Inner join Course c on b.course_id=c.course_id Where c.name in ('英語', '語文') Group by Sid,sname Having count(*)>=2; --7. 查詢有學生的單科成績小於60的姓名和課程名稱 select Student.sname,Course.`name` from Student inner join Score on Student.sid = Score.sid inner join Course on Course.course_id=Score.course_id where Score.score<60; --8. 按平均成績從高到低顯示所有學生的姓名和語文、數學、英語三科成績 Select c.sname, avg(score) score_avg, sum(case when b.name='語文'then a.score else 0 end) a1, sum(case when b.name='數學' then a.score else 0 end) a2, sum(case when b.name='英語' then a.score else 0 end) a3 From Score a inner join Course b on a.course_id=b.course_id inner join Student c on c.sid=a.sid Group by a.sid order by avg(a.score) desc; --9. 查詢各科成績中的最高分和最低分 select course_id,max(score),min(score) from Score group by course_id; --10. 計算各科平均成績和及格率的百分比 Select course_id,avg(score) as '平均分', sum(case when score>=60 then 1 else 0 end)/count(*)*100 as '及格率' From Score Group by course_id --11. 查詢不同老師所教的不同課程按照平均分從高到低排列(這里假設一個老師就教一門課) Select c.name '老師名',b.name '課程名',avg(score) '平均分' From Score a inner join Course b on a.course_id=b.course_id Inner join teacher c on b.teacher_id=c.teacher_id Group by c.name,b.name Order by avg(score) desc --12. 查詢英語和數學課程成績排名第5到第10的學生的姓名和成績 (select Student.sname,Score.score from Score inner join Student on Score.sid = Student.sid inner join Course on Course.course_id = Score.course_id where Course.`name` ='英語' order by Score.score desc limit 4,6) union all (select Student.sname,Score.score from Score inner join Student on Score.sid = Student.sid inner join Course on Course.course_id = Score.course_id where Course.`name` ='數學' order by Score.score desc limit 4,6) --13. 統計按照各科成績,分段統計每個課程在90分以上、80-90、60-80、低於60分的人數 Select b.name, sum(case when score>=90 then 1 else 0 end), sum(case when score<90 and score>=80 then 1 else 0 end), sum(case when score<80 and score>=60 then 1 else 0 end), sum(case when score<60 then 1 else 0 end) From Score a inner join Course b on a.course_id=b.course_id Group by b.name; --14. 查看每門課程被選修的學生數 select course_id,count(*) from Score group by course_id; --15. 查看只學習了一門課程的學生的姓名和學號 select Student.sid,Student.sname from Student inner join Score on Student.sid =Score.sid group by Student.sid,Student.sname having count(*)=1; --16. 查詢名字相同的學生名單和個數 select sname,count(*) from Student group by sname having count(*)>1; --17. 查詢85年之后出生的學生人數 select count(*) from Student where birthDay > '1985-01-01'; --18. 查詢每門課程的平均成績、按升序排序,如果平均成績相同,按課程ID降序 select course_id,Avg(score) from Score group by course_id order by Avg(score) asc,course_id desc; --19. 查詢有不及格學生的課程和不及格學生個數 Select course_id,count(*) From Score Where score<60 Group by course_id; --20. 將所有學生姓名中前后的空格去掉 Update Student set sname=ltrim(rtrim(sname)); --21. 將所有學生的考試成績展示為課程名:成績樣式 Select a.sid,concat(b.name,':',a.score) From Score a inner join Course b on a.course_id=b.course_id; --22. 將所有老師的名字差分成姓 和 名 兩個字段顯示 Select name, substring(name,1,locate(' ',name)-1),substring(name,locate(' ',name)+1,50) from Teacher;
!
- 作 者 : Yaopengfei(姚鵬飛)
- 博客地址 : http://www.cnblogs.com/yaopengfei/
- 聲 明1 : 如有錯誤,歡迎討論,請勿謾罵^_^。
- 聲 明2 : 原創博客請在轉載時保留原文鏈接或在文章開頭加上本人博客地址,否則保留追究法律責任的權利。