JAVA新手入門05~MYSQL提高篇
這一篇我們會講解下mysql的一些稍微有點難度的內容,目前的網際網路公司,面試的時候問mysql的時候也還是挺多的,不僅僅開發崗位,有些測試崗位在面試的時候,也需要會mysql,所以本篇比上一篇的基礎篇要稍微難點。
主要會講解以下內容:- 多表聯合查詢
- 索引命中規則
- 表結構設計三正規化
- 查詢優化技巧
1、聯合查詢
多個表聯合查詢,是說查詢的時候,同時查詢2個或者2個以上的表,為什麼要這麼做呢?是因為資料都是按分類儲存的,往往學生資訊單獨儲存在一張表,學生成績會儲存在另外一張表,所以要同時查詢出學生的姓名和成績的時候,就需要聯合2個表,同時查詢。也可以先看看第3節的表結構設計三正規化,加深一下理解。
繼續上一節的內容,假設我們除了有學生表,還有一張學生成績表(student_score),現在我們就有2張表,表的結構如下:
CREATE TABLE `student` ( `num` int(11) NOT NULL COMMENT '學號', `name` varchar(10) DEFAULT '' COMMENT '姓名', `age` smallint(6) DEFAULT 0 COMMENT '年齡', PRIMARY KEY (`num`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='學生資訊'; CREATE TABLE `student_score` ( `num` int(11) NOT NULL COMMENT '學號', `type` varchar(10) DEFAULT '' COMMENT '0數學 1語文 2英語', `score` smallint(6) DEFAULT 0 COMMENT '考試分數', PRIMARY KEY (`num`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='學生考試分數表';
1.1 簡單關聯
#查詢學生語文成績 select a.num, a.name, a.age, b.type, b.score from student a join student_score b on a.num = b.num where b.type = 1 #使用left join 或者right join # a left join b 是說如果a表中有num=1的學生,b表中沒有num=1的學生的成績,也有返回結果,只是有學生資訊,沒有成績 # a right join b 是正好反過來 # a join b 如果是上述情況,則不會返回資料 select a.num, a.name, a.age, b.type, b.score from student a left join student_score b on a.num = b.num #查詢男生和女生的平均成績,這裡要用到avg這個函式,但是這裡主要是想講解 #group by 的用法, select (case when a.sex = 0 then '女生' else '男生' end) as 性別, avg(b.score) as '平均成績' from student a join student_score b on a.num = b.num group by a.sex # 如果查詢語句是: select a,b,c,sum(d),avg(f) from table # 那麼必須要使用group by ,group by的列,用逗號分隔,且所有非聚合函式的列,都必須在group by的後面, # 這個例子就是a,b,c這3個列 #正確SQL: select a,b,c,sum(d),avg(f) from table where a =10 group by a,b,c
來看下left join的結果
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-IDMXLdsU-1609395437886)(/img/mysql06.png)]
我們給student_score表中插入記錄,可以看到已經可以查詢出記錄來了
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-2HF7fDiB-1609395437889)(/img/mysql07.png)]
這裡一定要學會join/left join/right join 並且知道區別
這裡有以下幾點需要注意:
-
多個表查詢,需要使用別名,就是表的名字後面跟的a、b,可以用來區分每個表
-
注意 a.num = b.num 和 b.type = 1的不同位置,可能有的地方會教大家不使用on的版本,如下:
#查詢學生語文成績 不使用on的版本,不推薦這種寫法 select a.num, a.name, a.age, b.type, b.score from student a,student_score b where a.num = b.num and b.type = 1
這種寫法是不推薦的,模糊了表的關聯條件和過濾條件,關聯條件是2個表的紐帶,on後面跟的是關聯條件,過濾條件是資料查詢出來之後,where之後的內容,儘量分開寫。
-
儘量查詢需要的欄位。
-
關聯條件一定要寫正確,如果有2個關聯條件,但是隻寫了1個,就會導致笛卡爾積(這裡不多講了),導致資料錯誤,一般會返回更多的資料行。
1.2 子查詢
mysql支援子查詢,可以做1個臨時表,例如上面的語句可以修改為:
select
a.num,
a.name,
a.age,
b.type,
b.score
from
(select num,name,age from student) a
left join
(select type,score from student_score) b
on
a.num = b.num
儘管這麼修改毫無意義,但是在某些情況下,不得不使用子查詢,例如有1個表category,裡面儲存了商品的分類資訊,表有以下4個欄位:id name level p_id,分別表示分類ID,名稱,級別(1級,2級,3級),父ID
這個時候如果要查詢這樣的結果:
1級ID 一級分類名稱 2級ID 二級分類名稱
100 數碼 100100 手機
就需要使用子查詢了:
select
a.id,
a.name,
b.id,
b.name
from
(select id,name from category where level = 1) a
join
(select id,name,p_id from category where level = 2) b
on
a.id = b.p_id
2、索引命中規則
單個索引 ,例如student表中的列,num是有索引的,那麼下面幾種將命中索引:
- num = 10
- num in (10,20) in裡面最多有多少個,取決於MYSQL允許SQL的最大有多長。
- num between 10 and 20
- num > 10
下面這幾種,將不會命中索引
- num + 1 = 10 做運算不會生效
- abs(num) = 10 函式運算之後不會生效
- name like ‘%明%’ 假設name列建了索引,用like不會生效
如果是聯合索引,例如建立了一個num,name,sex的聯合索引,索引的順序是num,name,sex,那麼命中索引的情況如下:
-
num = 10 and name = ‘小明’ and sex = 1
-
num in (10,20) and name = ‘小明’
-
num = 10 and name like ‘%小明%’ 這裡索引只會命中num列,name的不會命中
-
sex = 1 and name = ‘小明’ and num = 10 和順序無關,只要有num、name、sex就可以
如下情況則不會命中
- name = ‘小明’ 遵循左原則,單獨查詢name不會生效
- sex = 1 遵循左原則,單獨查詢sex不會生效
- name = ‘小明’ and sex = 1遵循左原則,單獨查詢name、sex不會生效
記住,聯合索引要生效,左邊的條件必須有,否則不生效。
3、表結構設計三正規化
自己體會一下吧,相信對於大家來說都不難理解。
- 第一正規化**(1st NF** -列都是不可再分)
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-9oYFstXu-1609395437892)(/img/mysql03.png)]
- 第二正規化**(2nd NF**-每個表只描述一件事情)
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-wD6r7SX8-1609395437895)(/img/mysql04.png)]
-
第三正規化**(3rd NF**- 不存在對非主鍵列的傳遞依賴**)**
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-SyAXAi0C-1609395437899)(/img/mysql05.png)]
資料庫設計三正規化,是一個設計資料庫結構的參考,遵循了這個設計規則,不一定就是好的設計。在實際的專案設計中,還是要以實際情況為準,可能會把1個表的列,弄的特別多,例如20個列,30個列,甚至100個列都有可能,也有的團隊為了以後容易擴充套件,提前設計了冗餘的列,也有的時候,一個列例如商品的簡稱,在多個表都存在,這些都是違反了設計規則的,但是確實是存在的。資料欄位的冗餘,是一種空間換時間的概念,也用的挺好。
對於新手的朋友們來說,首先要參考的就是三正規化,然後要根據實際的情況,做出調整,該違反就要違反,規則可以創新,只要有利於你的開發就行,資料庫的列,一旦定好了,很少會刪除,但是可以新增,這一點要清楚。
再說一個關於設計列的注意事項,就是要仔細考慮是否可以是NULL,NULL是程式設計屆繞不開的一個話題,關於這點,我的建議是不要為NULL,可以設定預設值,在JAVA程式碼開發中,也應該儘量給個初始值,避免用NULL表示某個特殊的含義,如果你的程式碼中出現了以下這種情況:
if(data == null) {
do something
}
if(data != null) {
do something
}
請儘量優化。
4、查詢優化技巧
- 儘量能命中索引。
- 如果有聯合查詢,小表放前面,大表在後面。
- 儘量查詢需要的欄位,不要使用select *。
- 如果有子查詢做多表聯合查詢,儘量先使用where條件降低資料量,再做關聯查詢。
- 注意索引失效的幾種方式,對列做運算或者使用函式會使索引失效。
其它 提高內容
mysql還有2個用的比較少的關鍵字HAVING EXISTS,下面簡單說一下這2個關鍵字
HAVING
一般是和group by 搭配使用的,用來分組,例如,你有一個訂單表,order_id,sku_id,amount,create_date這4列
統計訂單的金額:
select
order_id,
sum(amount) as total
from order
where
create_date = '2020-01-01'
group by order_id
現在需要加1個過濾條件,哪些訂單的金額大於100元
select
order_id,
sum(amount) as total
from order
where
create_date = '2020-01-01'
group by order_id
having total > 100
只需要記住,當使用sum()這類聚合的函式的時候,如果需要對聚合的值進行過濾,可以使用
EXISTS和NOT EXISTS
先看exist吧,exist子查詢會返回1個true或者false,如果返回了true,就顯示對應的資料,可以先來1個簡單的SQL看看
假設我們有1張表sku,儲存了sku的基本資訊,另外一個表就是剛才的訂單表order
select * from order where exists (select 1)
由於select 1總是會返回值,所以這個SQL,相當於:
select * from order where 1=1
所以我們 一般不這麼用,而是把前1個表的欄位和子查詢的欄位關聯起來,例如要查詢sku_id在1到100之間的,有訂單的SKU的資訊
select
a.*
from sku a
where
a.sku_id in
(
select sku_id from order where sku_id between 1 and 100
)
也可以使用EXISTS
select
a.*
from sku a
where
exists
(
select b.sku_id from order b where a.sku_id = b.sku_id and a.sku_id between 1 and 100
)
NOT EXISTS就是不滿足的意思,恰好相反。
下面來看一個更復雜的場景,假設我們有3張表,
- student 學生表 ,有id name2個欄位 分別表示學生的學號和姓名
- course 課程表 有 c_id c_name 2個欄位 分別表示課程的ID和名稱
- score 成績表 id,c_id,score 3個欄位 分別表示學號、課程ID、分數
剛開始的資料是,小明選了所有的3門課程,ABC,小麗選擇了AB兩門課程,沒有選擇C
假設學生只要選學了課程,就一定有分數,那麼怎麼查出來,哪些學生選學了所有的課程呢?我們的一般思維是,把課程數量算出來,select count(c_id) from course ,然後查詢成績表,看看學生的課程數量
select id,count(c_id) from score ,最後關聯學生表,查詢學生的成績,用子查詢就是:
select d.name
from
(select a.id
from
(select id,count(c_id) as _count from score group by id) a
join
(select count(c_id) as _count from course) b
where
a._count = b._count ) c join student d
on c.id = d.id
那麼用exists來怎麼寫呢?s
select name from student a
where not exists
(
select c_id from course b
where not exists
(select id from score c where a.id = c.id and b.c_id = c.c_id)
)
這個SQL有點複雜,似乎很難理解,先分析最裡面的not exists ,可以降低難度,先分析最裡面的exists是什麼意思?
select c_id from course b
where exists
(select id from score c where a.id = c.id and b.c_id = c.c_id)
查詢所有的課程,看看是不是有分數,對於小明來說,小明所有課程有分數,返回TRUE,所以如果變更為not exists,就變成FALSE了,外側的: select name from student a where not exists 正好是需要FALSE,所以小明命中,他選擇了所有課程。
對於小麗來說,內側的這個語句,很明顯,小麗的C課程沒有分數,所以NOT EXISTS返回TRUE,但是外側也是NOT EXISTS,需要返回FALSE才能命中,所以小麗不符合條件,得出,小麗沒有選擇所有的課程。
大家可以思考下,下面這個寫法,為什麼不行?(歸根到底的原因是EXISTS只有有記錄返回,就算TRUE,所以內側的EXISTS,只要選了課程,一定會返回TRUE,外側的EXISTS,也會是TRUE,這個SQL表示查詢至少選擇了1門課程的學生)
select name from student a
where exists
(
select c_id from course b
where exists
(select id from score c where a.id = c.id and b.c_id = c.c_id)
)
希望對你有幫助。
全文完。
關注我的部落格,獲取更多Java程式設計知識: