數據準備
回顧
記錄操作:
寫操作:INSERT,UPDATE,DELETE
讀取操作:SELECT
這章主要學習:
子查詢
連接
多表刪除
多表更新
數據準備:
簡單的商城數據庫
tdb_good表結構:
插入數據:略(見下載文件中的”子查詢.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關鍵字:
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;