1. 程式人生 > 資料庫 >JAVA新手入門05~MYSQL提高篇

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程式設計知識: