第五章 子查詢與連接

數據準備

回顧

記錄操作:
寫操作:INSERT,UPDATE,DELETE
讀取操作:SELECT

這章主要學習:
子查詢
連接
多表刪除
多表更新

數據準備:
簡單的商城數據庫

Paste_Image.png

tdb_good表結構:

Paste_Image.png

插入數據:略(見下載文件中的”子查詢.txt“)

注意編碼方式,插入的時候是以utf8的形式插入的,顯示會亂碼,此時使用SET NAMES gbk;設置客戶端的編碼方式(不會影響服務器)

子查詢簡介

子查詢:
子查詢(Subquery)是指出現在其他SQL語句內的SELECT子句。
例如:
SELECT * FROM t1 WHERE col1= (SELECT col2 FROM t2);
其中SELECT * FROM t1,稱為Outer Query/Outer Statement
SELECT col2 FROM t2,稱為SubQuery

子查詢指嵌套在查詢內部,且必須始終出現在圓括號內。
子查詢可以包含多個關鍵字或條件,
如DISTINCT、GROUP BY、ORDER BY、LIMIT,函數等。
子查詢的外層查詢可以是:SELECT,INSERT,UPDATE,DELETE,SET或DO。

子查詢中的外層查詢是指SQL語句的統稱,而不僅僅是SELECT(SQL:結構化查詢語言)

子查詢返回值:子查詢可以返回標量、一行、一列或子查詢。

拿到結果后就可以在INSERT,UPDATE,SELECT,DELETE等其他的SQL語句中使用

由比較運算符引發的子查詢

子查詢分類:
使用比較運算符的子查詢
使用比較運算符的子查詢(=、>、<、>=、<=、<>、!=、<=>)
語法結構:operand comparison_operator subquery

查找平均價格
SELECT AVG(goods_price) FROM tdb_goods;
avg()聚合函數,和i有一個返回值,類似的函數還有sum(),count(),max(),min()

SELECT ROUND(AVG(goods_price),2) FROM tdb_goods;#ROUND(AVG(goods_price),2)指的是對平均值進行四舍五入,最后保留l兩位小數

SELECT goods_id,goods_name,goods_price FROM tdb_goods WHERE goods_price>=5636.36;#選擇價格大于平均價格(5636.36)的商品

將上兩條查詢合并:
SELECT goods_id,goods_name,goods_price FROM tdb_goods WHERE goods_price>=(SELECT ROUND(AVG(goods_price),2) FROM tdb_goods);#使用了比較運算符,而且使用了小括號

查詢超極本類型的價格:
SELECT goods_price FROM tdb_goods WHERE goods_cate='超極本';

查詢價格大于超極本價格的商品:
SELECT goods_id,goods_name,goods_price FROM tdb_goods WHERE goods_price>(SELECT goods_price FROM tdb_goods WHERE goods_cate='超極本';#錯誤,因為子查詢中返回的不是一個數據而是三條記錄,WHERE中應該告訴系統大于哪個數據

用ANY、SOME或ALL修飾的比較運算符

  • operand comparison_operator ANY (subquery)
  • operand comparison_operator SOM(subquery)
  • operand comparison_operator ALL(subquery)

ANY、SOME是等價的,只要符合其中的一個就行,ALL是要符合全部

ANY、SOME、ALL關鍵字:


Paste_Image.png

ANY演示:
`SELECT goods_id,goods_name,goods_price FROM tdb_goods WHERE goods_price> ANY(SELECT goods_price FROM tdb_goods WHERE goods_cate='超極本');

ALL演示:
`SELECT goods_id,goods_name,goods_price FROM tdb_goods WHERE goods_price> ALL(SELECT goods_price FROM tdb_goods WHERE goods_cate='超極本');

等于ALL演示:
SELECT goods_id,goods_name,goods_price FROM tdb_goods WHERE goods_price = ANY(SELECT goods_price FROM tdb_goods WHERE goods_cate='超極本');#選擇的其實就是子查詢里查詢的結果

由[NOT] IN/EXISTS引發的子查詢

使用[NOT] IN的子查詢
語法結構:
operand comparison_operator [NOT] IN (subquery)=ANY 運算符與IN等效。
!=ALL或<>ALL運算符與NOT IN等效

演示:
SELECT goods_id,goods_name,goods_price FROM tdb_goods WHERE goods_price <> ALL(SELECT goods_price FROM tdb_goods WHERE goods_cate='超極本');
等價于
SELECT goods_id,goods_name,goods_price FROM tdb_goods WHERE goods_price NOT IN (SELECT goods_price FROM tdb_goods WHERE goods_cate='超極本');

使用[NOT] EXISTS的子查詢
如果子查詢返回任何行,EXISTS將返回TRUE;否則返回FALSE。

用的較少,子查詢返回了結果EXISTS返回TRUE,否則返回FALSE

使用INSERT...SELECT插入記錄

之前講過INSERT 和 INSERT SET的區別是INSERT SET 可以使用子查詢(SET 可以使XX=XX引發子查詢)

tdb_goods表中有很多弊,存在這很多重復的信息,如品牌有很多索尼,分類中有很多筆記本配件,字符串比數字占的字節數多,如果記錄越來越多,數據表就會越來越龐大,查找時速度就會變慢,最好的方法是使用外鍵,需要兩張數據表。

創建分類表:

CREATE TABLE IF NOT EXISTS tdb_goods_cates(
cate_id SMALLINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
cate_name VARCHAR(40) NOT NULL
);

不用一條一條分類的插入,應該使用子查詢
SELECT goods_cate FROM tdb_goods GROUP BY goods_cate;#一共有7類,需要寫入分類表中
使用INSERT...SELECT將查詢寫入數據表:

INSERT...SELECT
將查詢結果寫入數據表
INSERT [INTO] tbl_name [(col_name,...)] SELECT ...

實現:
INSERT tdb_goods_cates(cate_name) SELECT goods_cate FROM tdb_goods GROUP BY goods_cate;#不能省略插入表的列名,不用寫VALUES
使用SELECT * FROM tdb_goods_cates;查看已經插入成功

但是還是沒有使用外鍵,應該參照分類表來更新商品表

多表更新

UPDATE table_references
SET col_name1={expr1|DEFAULT}
[,col_name2={expr2|DEFAULT}]...
[WHERE where_condition]

CREATE...SELECT
創建數據表同時將查詢結果寫入到數據表
CREATE TABLE [ID NOT EXISTS] tbl_name
[(create_definition,...)]
select_statement

FROM子句中的子查詢
語法結構
SELECT...FROM(subquery) [AS] name...
說明:
名稱為必選項,且子查詢的列名稱必須唯一。

連接
MySQL在SELECT語句、多表更新、多表刪除語句中支持JOIN操作

表的連接條件,第一張表+連接類型+第二張表+連接條件
語法結構
table_reference
{[INNER | CROSS] JOIN | {LEFT | RIGHT} [OUTER] JOIN}
table_reference
ON conditional_expr

表的參照可以給表賦予別名也可以不賦予別名

連接類型
INNER JOIN,內連接
在MySQL中,JOIN,CROSS JOIN和INNER JOIN是等價的。
LEFT [OUTER] JOIN,左外連接
RIGHT [OUTER] JOIN,右外連接

UPDATE tdb_goods INNER JOIN tdb_goods_cates ON goods_cate = cate_name SET goods_cate = cate_id;#tdb_goods表中的goods_cate已經被更新

多表更新之一步到位

以上更新操作參照別的表更新了本表,一共使用了三步:
1.創建表
2.通過使用INSERT...SELECT把記錄寫入新表
3.多表更新

把三步合并為一步:
可以使用CREATE...SELECT實現:
CREATE...SELECT
創建數據表同時將查詢結果寫入到數據表
CREATE TABLE [IF NOT EXISTS] tbl_name
[(create_definition,...)]
select_statement

將表中的品牌也獨立出一張表,創建表的同時將查詢的數據寫入:

CREATE TABLE tdb_goods_brands(
brand_id SMALLINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
brand_name VARCHAR(49) NOT NULL
)
SELECT brand name FROM tdb_goods GROUP BY brand_name;

查看tdb_goods_brands表可以看到數據寫入成功
還有一步應該參照品牌表更新商品表中的品牌:
UPDATE tdb_goods INNER JOIN tdb_goods_brands ON brand_name = brand_name SET brand_name = brand_id;#會報錯,提示brand_name含義不明確,因為兩張表中都有brand_name,系統不知道那兩個brand_name是哪張表中的

要解決這個問題,只能給表起別名或者在字段前邊加上表名
通常是給表起別名:
UPDATE tdb_goods AS g INNER JOIN tdb_goods_brands AS b ON g.brand_name = b.brand_name SET g.brand_name = b.brand_id;#更新成功
使用SELECT * FROM tdb_goods\G查看表中的記錄,看到表中的brand_name已經被更新

此時,使用SHOW COLUMNS FROM tdb_goods;查看表結構發現表中的goods_cate和brand_name仍然是varchar類型,表中的數字代表的是字符而不是tdb_goods_cate和tdb_goods_brands中的id(數字型)

修改標結構:

ALTER TABEL tdb_goods
CHANGE goods_cate cate_id SMALLINT UNSIGNED NOT NULL,
CHANGE brand_name brand_id SMALLINT UNSIGNED NOT NULL;

再使用SHOW COLUMNS FROM tdb_goods;查看表結構看到表結構已經修改成功goods_cate和brand_name修改成了數字類型

關于使用外鍵:不一定要使用FORIGN KEY物理外鍵,可以用這種外鍵,稱為事實外鍵,通常較多使用事實外鍵,物理外鍵用的不多

在分類表和品牌表中插入一些記錄:

INSERT tdb_goods_cates(cate_name) VALUES('路由器'),('交換機'),('網卡');
INSERT tdb_goods_brands(brand_name) VALUES('海爾'),('清華同方'),('神舟');

插入三個不同的分類和三個不同的品牌

在商品表中插入記錄:
INSERT tdb_goods(goods_name,cate_id,brand_id,goods_price) VALUES(' LaserJet Pro P1606dn 黑白激光打印機','12','4','1849');#此時插入數據成功,但是有一個小錯誤,goods_cate寫的是12,但是tdb_goods_cate表中并沒有id為12的分類
把表中的記錄查詢出來呈現出來,存儲時cate_id和brand_id存儲的是其他表中的id,顯示的時候就不能這樣直接顯示了,應該顯示商品品牌和分類而不是id,這時就需要使用到連接了

連接的語法結構

連接:
MySQL在SELECT語句、多表更新語句中支持JOIN操作

三種連接:內連接,左外連接,右外連接
A表+連接類型+B表+連接條件
語法結構
table_reference
{[INNER | CROSS] JOIN | {LEFT | RIGHT} [OUTER] JOIN}
table_reference
ON conditional_expr

數據表參照:
table_reference
tbl_name [[AS] alias] | table_subquery [AS] alias
數據表可以使用tbl_name AS alias_name或tbl_name alias_name賦予別名。
table_subquery可以作為子查詢使用在FROM子句中,這樣的子查詢必須為其賦予別名。

內連接INNER JOIN

連接類型:
INNER JOIN,內連接
在MySQL中,JOIN,CROSS JOIN和INNER JOIN是等價的。
LEFT [OUTER] JOIN,左外連接
RIGHT [OUTER] JOIN,右外連接

連接條件:
使用ON關鍵字來設定連接條件,也可以使用WHERE來代替。
通常使用ON關鍵字來設定連接條件,
使用WHERE關鍵字進行結果集記錄的過濾。

內鏈接:顯示左表及右表符合連接條件的記錄

內連接

實例:
SELECT goods_id,goods_name,cate_name FROM tdb_goods INNER JOIN tdb_goods_cates ON tdb_goods.cate_id = tdb_goods_cates.cate.id;
可以看到22條記錄,并沒有剛剛添加的第23條記錄,因為第23條記錄不符合連接條件,剛才添加的cate_id是12,在tdb_goods_cate表中并不存在id為12的記錄,而且剛剛新添加的幾個分類也沒有顯示出來,這就是內連接(兩張表都會有的才會顯示出來),僅顯示符合連接條件的記錄

外連接OUTER JOIN

左外連接:顯示左表的全部記錄及右表符合連接條件的記錄

左外連接

演示:SELECT goods_id,goods_name,cate_name FROM tdb_goods LEFT JOIN tdb_goods_cates ON tdb_goods.cate_id = tdb_goods_cates.cate.id;#OUTER可以寫,也可以不寫
得到23條記錄,但是第23條記錄的cate_name為空,左外連接指的是左表中的全部和右表中符合條件的,如果右表中沒有符合條件的會顯示為NULL

右外連接:顯示右表的全部記錄及左表符合連接條件的記錄

右外連接

演示:SELECT goods_id,goods_name,cate_name FROM tdb_goods RIGHT JOIN tdb_goods_cates ON tdb_goods.cate_id = tdb_goods_cates.cate.id;#OUTER可以寫,也可以不寫
得到25條記錄,沒有了那個cate_id為12的記錄,又多了三條分類的記錄,右外連接指的是顯示右表中的全部和左表中符合連接條件的記錄

這三種連接中內連接用的想對較多

多表連接

商品表中存在商品分類和品牌
實現三張表的連接:
演示:

SELECT goods_id,goods_name,cate_name,brand_name,goods_price FROM tdb_goods AS g 
INNER JOIN tdb_goods_cates AS c ON g.cate_id = c.cate_id 
INNER JOIN tdb_goods_brands AS b ON g.brand_id = b.brand_id;

可以看到這是有恢復了我們最初原始的結果,不一樣的是這次是通過多張表的連接實現的,以前是通過一張表查詢出來的

其實表的連接就像是外鍵的逆向操作,外鍵把表分開存儲,連接把多張表連接起來查詢

關于連接的幾點說明

A LEFT JOIN B join_condition

  • 數據表B的結果集依賴數據表A。
  • 數據表A的結果集根據左連接條件依賴所有數據表(B表除外)。
  • 左外連接條件決定如何檢索數據表B(在沒有指定WHERE條件的情況下)。
  • 如果數據表A的某條記錄符合WHERE條件,但是在數據表B不存在符合連接條件的記錄,將生成一個所有列為空的額外的B行。

查找到的結果為NULL但是含有約束NOT NULL的情況:
如果使用內連接查找的記錄在連接數據表中不存在,并且在WHERE子句中嘗試以下操作:col_namd IS NULL時,如果col_name被定義為NOT NULL,MySQL將在找到符合連接執行條件的記錄后停止搜索更多的行。

無限極分類表設計

查看tdb_goods_cates表的記錄,這些分類遠遠達不到現實中分類的要求,很多網站中,這些分類有很多級分類,一級分類、二級分類、三級分類……這種分類就是無限分類,數據表應該怎樣設計,可以設計很多張表,隨著分類的增多,表的數目也會逐漸增多,查找起來就不方便了,所以,一般都采用在表中增加父分類的id字段實現:

實例:

CREATE TABLE tdb_goods_types(
type_id SMALLINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
type_name VARCHAR(20) NOT NULL,
parent_id SMALLINT UNSIGNED NOT NULL DEFAULT 0
);#parent_id為父分類的id,為0表示沒有父分類,為一級分類

然后插入數據:

  INSERT tdb_goods_types(type_name,parent_id) VALUES('家用電器',DEFAULT);
  INSERT tdb_goods_types(type_name,parent_id) VALUES('電腦、辦公',DEFAULT);
  INSERT tdb_goods_types(type_name,parent_id) VALUES('大家電',1);
  INSERT tdb_goods_types(type_name,parent_id) VALUES('生活電器',1);
  INSERT tdb_goods_types(type_name,parent_id) VALUES('平板電視',3);
  INSERT tdb_goods_types(type_name,parent_id) VALUES('空調',3);
  INSERT tdb_goods_types(type_name,parent_id) VALUES('電風扇',4);
  INSERT tdb_goods_types(type_name,parent_id) VALUES('飲水機',4);
  INSERT tdb_goods_types(type_name,parent_id) VALUES('電腦整機',2);
  INSERT tdb_goods_types(type_name,parent_id) VALUES('電腦配件',2);
  INSERT tdb_goods_types(type_name,parent_id) VALUES('筆記本',9);
  INSERT tdb_goods_types(type_name,parent_id) VALUES('超級本',9);
  INSERT tdb_goods_types(type_name,parent_id) VALUES('游戲本',9);
  INSERT tdb_goods_types(type_name,parent_id) VALUES('CPU',10);
  INSERT tdb_goods_types(type_name,parent_id) VALUES('主機',10);

那么問題來了:如何查詢這張表

可以通過自身連接查詢:
自身連接:同一個數據表對其自身進行連接

示例演示:
一張表做自身連接必須要起別名,要不就分不清這兩個相同名稱的字段從哪來的了
想象一下有兩張相同的表,左邊是父表,右邊是子表
SELECT s.type_id,s.type_name,p.type_name FROM tdb_goods_types AS s LEFT JOIN tdb_goods_types AS p ON s.parent_id = p.type_id;#就可以查到子類的id,子類的名字以及父類的名字

查找子類,父類以及父類下的子類:
左邊是子表,右邊是父表
SELECT p.type_id,p.type_name,s.typename FROM tdb_goods_types AS p LEFT JOIN tdb_goods_types AS s ON s.parent_id = p.type_id;
含有重復的父類,使用GROUP BY分組:
SELECT p.type_id,p.type_name,s.typename FROM tdb_goods_types AS p LEFT JOIN tdb_goods_types AS s ON s.parent_id = p.type_id GROUP BY p.type_name ;
可以看到只有15個分類了,按照id排序:

SELECT p.type_id,p.type_name,s.typename FROM tdb_goods_types AS p LEFT JOIN tdb_goods_types AS s ON s.parent_id = p.type_id GROUP BY p.type_name ORDER BY p.type_id;

不要子類的名字,需要子類的數目:

SELECT p.type_id,p.type_name,count(s.typename) AS child_count FROM tdb_goods_types AS p LEFT JOIN tdb_goods_types AS s ON s.parent_id = p.type_id GROUP BY p.type_name ORDER BY p.type_id;

就可以看到子類的數量了

多表刪除

DELETE tbl_name[.] [,tbl_name[.]]...
FROM table_references
[WHERE where_condition]

使用SELECT * FROM tdb_goods;查看表中的記錄,看到第18、19和第21、22條記錄是重復的,這是,想要把重復的記錄刪除,保留id較小的記錄。
可以通過多表刪除實現,也就是采用一張表模擬兩張表實現
演示:
SELECT goods_id,goods_name FROM tdb_goods GROUP BY goods_name;#從23條記錄中得到了21個商品,因為有些記錄是相同的

我們只想要相同商品名稱超過兩個以上的記錄
SELECT goods_id,goods_name FROM tdb_goods GROUP BY goods_name HAVING COUNT(goods_name) > 1;#得到重復商品的記錄,這就是我們將要刪除或者要保留的記錄
可以通過這張表來刪除原表中的數據:
DELETE t1 FROM tdb_goods AS t1 LEFT JOIN (SELECT goods_id,goods_name FROM tdb_goods GROUP BY goods_name HAVING count(goods_name) >= 2) AS t2 ON t1.goods_name=t2.goods_name WHERE t1.goods_id > t2.goods_id;

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,572評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,071評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,409評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,569評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,360評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,895評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,979評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,123評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,643評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,559評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,742評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,250評論 5 356
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,981評論 3 346
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,363評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,622評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,354評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,707評論 2 370

推薦閱讀更多精彩內容