數據庫筆記(4.5 - 4.8)


源自南京大學軟件學院劉嘉老師的《數據庫開發》課程,課堂筆記

1. 字符串處理

1.3 統計字符出現的次數

  • 問題:統計字符傳中有多少個逗號?

    selete(length('10,CLARK,MANAGER')-
    	length(replace('10,CLARK,MANAGER',',','')))/length(',')
    	as cnt
    from t1
    

    現將字符串中所有的,都轉變成空格,然後用原來的長度減去轉化後的長度(要記得除以所有減去的字符的長度)

1.4 刪除不想要的字符

  • 問題:從數據裏刪除指定的字符,從左邊的結果集中的數據裏刪除所有的0和元音字母(AEIOU),並且將刪除後的值顯示在STRIPPED2列中

    selete ename
    	replace(
    	replace(
    	replace(
    	replace(
    	replace(ename,'A',''),'E',''),'I',''),'O',''),'U','')
    	as stripped1,
    	sal,
    	replace(sal,0,'')stripped2
    from emp
    
  • mysql只能用這種笨方法

1.5 分離數字和字符數據

  • 問題:把數據中的數字數據和字符數據分開,怎麼辦?

  • oracle——使用replace和translate解決,先把所有的字母換成小寫的z,然後把所有的小寫z刪除。

    selete replace(
    	translate(data,'0123456789','0000000000','0')ename,
    	to_number(
    		replace(
    			translate(lower(data),
    				'qwertyuiopasdfghjklzxcvbnm',
    				rpad('z',26,'z')),'z'))sal
    from(
    	selete ename||sal data
    	from emp
    )
    
  • mysql只能用26個replace()函數進行嵌套

    • 或者在程序體內將字符串先轉換好,別在mysql語句裏面幹這種蠢事。

1.6 判斷含有字母和數字的字符

  • 問題:從表中篩選出部分行數據,篩選條件是隻包含字母和數字字符。

  • mysql直接使用正則表達式^0-9a-zA-Z來匹配所有的數字和字母,比較方便

    selete data  
    from V
    where data regexp'[^0-9a-zA-Z]'=0 # 如果是字母或者數字就會返回0,不是字母或者數字就會返回1.
    
  • oracle解決思路:找到所有的可能出現的非字母以及數字的形式;先把所有的字符和數字都轉化爲一個單一的字符,然後就能把它們當成一個字符,然後就可以分離出來,全部刪掉。還是使用translate函數

    • 把所有的字母和數字全部變成a,如果到某一行所有的內容都是a,那麼這一行就是我們需要找的行
    selete data 
    from V
    where translate(lower(data),'0123456789qwertyuiopasdfghjklzxcvbnm',rpad('a',36,'a'))
    	= rpad('a',length(data),'a')
    

1.7 提取姓名的首字母

  • 問題:你想把姓名變成首字母縮寫形式

  • mysql:使用

    • concat_ws
    • substr
    • substring_index
    • length
  • oracle:使用

    selete replace(
    	replace(
    		translate(replace('Stewie Griffin','.',''),
    			'qwertyuiopasdfghjklzxcvbnm',
    			rpad('#',26,'#')),'#',''),'','.')||'.'
    from t1
    

2. 數值處理

2.0 示例表結構

  • emp表

    • EMPNO
    • ENAME
    • JOB
    • MGR
    • HIREDATE
    • SAL
    • COMM
    • DEPTNO
  • dept表

    • DEPTNO
    • DNAME
    • LOC
  • 爲什麼要在sql裏面寫這些數值計算的東西?

    • 因爲這樣可以大大減小與數據庫的交互次數,大大減少了吞吐量
    • sql原則——儘量單一sql,完成所有任務

2.1 計算平均值

selete avg(sal) as avg_sal from emp
  • 遇到空值怎麼辦?——使用coalesce

    selete avg(coalesce(sal,0)) from emp
    

2.2 計算最大值和最小值

max() min()

空值對於尋找最大值和最小值沒有影響

2.3 求和

sum() 同樣對null值不影響

2.4 計算行數

  • count(*)來計算行數,所有的空值與非空值都會被記入總數

  • 但是如果只針對某一列的行數,那麼就不一樣了,不會計算空值(count(*)和count(某一列)是不一樣的)

    selete count(comm)
    from emp
    

    這樣就會把空值排除出去。

  • 分組計算不同組的行數:

    selete deptno,count(*)
    from emp
    group by deptno
    

2.5 累計求和(Running Total)

  • 保留加的過程的中間結果

  • Mysql & Oracle —— sum over

    selete ename, sal
    	sum(sal) over (order by sal, empno)
    		as running_total
    from emp
    order by 2 # order by empno
    

2.6 計算衆數

  • Oracle

    selete max(sal)
    	keep(dense_rank first order by cnt desc)sal
    from(
    	select sal, count(*)cnt
    	from emp 
    	where deptno=20
    	group by sal
    )
    
  • Mysql

    select sal
    from emp 
    where deptno=20
    group by sal
    having count(*) >= all (select count(*)
    	from emp 
    	where deptno = 20
    	group by sal)
    

2.7 計算中位數

  • mysql

    selete avg(sal)
    from (
    	select e.sal
    	from emp e, emp d
    	where e.deptno = d.deptno
    		and e.deptno = 20
    		group by e.sal
    		having sum(case when e.sal = d.sal then 1 else 0 end)
    			>= abs(sum(sign(e.sal - d.sal)))
    )
    
  • Oracle——直接有中位數接口

    select median(sal)
    from emp 
    where deptno=20
    

2.8 計算百分比

  • mysql

    select(sum(
    	case when deptno = 10 then sal end)/sum(sal)
    	)*100 as pct
    from emp
    
  • Oracle

    select distinct(d10/total)*100 as pct  
    from (
    select deptno, 
    	sum(sal)over() total,
    	sum(sal)over(partition by deptno)d10
    from emp
    )x
    where deptno = 10
    

2.9 計算平均值時去掉最大值和最小值

  • mysql

    select avg(sal)
    from emp 
    where sal not in(
    	(select min(sal) from emp),
    	(select max(sal) from emp)
    )
    
  • Oracle

    select avg(sal)
    from(
    select sal, min(sal) over() min_sal,max(sal) over() max_sal from emp
    )x
    where sal not in (min_sal, max_sal)
    

2.10 修改累計值

  • 問題,你想一句另一列的值來修改累計值。有這樣一個場景,你希望顯示一個信用卡賬戶的交易歷史,並顯示每一筆交易完成後的餘額。

3. 日期處理

3.1 年月日加減法

  • 問題,以員工CLARK的hiredate爲例,計算入職的前後五天,入職的前後五個月,以及入職前後5年的日期,hiredate=‘09-JUN-1981’

  • Oracle 通過add_months()操作來以月爲單位進行計算

    select hiredate - 5								as hd_minus_5D,
    			 hiredate + 5       				as hd_plus_5D,
    			 add_months(hiredate,-5)		as hd_minus_5M,
    			 add_months(hiredate,5)			as hd_plus_5M,
    			 add_months(hiredate,-5*12)	as hd minus_5Y,
    			 add_months(hiredate,5*12)	as hd plus_5Y
    from emp 
    where deptno = 10
    
  • mysql 使用date_add()函數或者interval

    select date_add(hiredate,interval -5 day)								as hd_minus_5D,
    			 date_add(hiredate,interval 5 day)								as hd_plus_5D,
    			 date_add(hiredate,interval -5 month)							as hd_minus_5M,
    			 date_add(hiredate,interval 5 month)							as hd_plus_5M,
    			 date_add(hiredate,interval -5 year)							as hd minus_5Y,
    			 date_add(hiredate,interval -5 year)							as hd plus_5Y
    from emp 
    where deptno = 10
    
    ------------------ or ------------------
    
    select hiredate - interval 5 day								as hd_minus_5D,
    			 hiredate + interval 5 day       					as hd_plus_5D,
    			 hiredate - interval 5 month							as hd_minus_5M,
    			 hiredate + interval 5 month							as hd_plus_5M,
    			 hiredate - interval 5 year								as hd minus_5Y,
    			 hiredate - interval 5 year								as hd plus_5Y
    from emp 
    where deptno = 10
    

3.2 計算兩個日期之間的天數

  • Mysql & SQL Server

    select datediff(ward_hd,allen_hd)
    from (
    	select hiredate as ward_hd
      from emp
      where ename='WARD'
    )x,
    (
      select hiredate as allen_hd
      from emp
      where ename='ALLEN'
    )y
    
  • Oracle

    select ward_hd-allen_hd
    from(
    select hiredate as ward_hd
      from emp 
      where ename = 'WARD'
    )x,
    (
    select hiredate as allen_hd
      from emp 
      where ename = 'ALLEN'
    )y
    

3.3 計算兩個日期之間的工作日天數

  • Mysql——使用數據透視表

    select sum (case when date_fromat)
    ……(詳見ppt)
    
  1. 計算出兩個日期之間相隔多少天
  2. 排除掉週末(檢索有多少條滿足“工作日”條件的記錄)

3.4 計算當前記錄和下一條記錄之間的日期差

  • 計算deptno = 10的部門的每一個員工入職時間相差多少天

  • Mysql

    select x.*, 
    	datediff(x.next_hd, x.hiredate)diff
    from(
    	select e.deptno, e.ename, e.hiredate,
      	(select min(d.hiredate) from emp d
        where d.hiredate > e.hiredate)next_hd
      from emp e
      where e.deptno = 10
    )x
    

3.5 判斷閏年

  • Mysql——需要從當前日期開始,酸楚當前日期是第幾天,然後用date_add函數計算current_date - dayofyear 再加上1來找到1月1號,然後用date_add繼續加一個月,再調用last_day找到最後一天

    select day(
    	last_day(
    	date_add(
    	date_add(
    	date_add(current_date)day),
    	interval 1 day),
    	interval 1 month)))dy
    from t1
    

3.6 計算一年有多少天

  • 計算下一年份的第一天和當前年份的第一天的差值(以天爲單位)

  • Oracle

    select add_months(trunc(susdate,'y'),12)-trunc(sysdate,'y')
    from dual
    
  • Mysql 找到今年的第一天是比較麻煩的事情;先查出當前的日期是當前的年份的第幾天,然後用當前日期減去那個值 加上1,就能找到今年的第一天;在此基礎上加一年,得到的結果就是當前年份有多少天了

    select datediff(curr_year + intercal 1 year),curr_year)
    from (
    	select adddate(current_date, -dateofyear(current_date)_1)curr_year
      from t1
    )x
    

3.7 找到當前月份的第一個和最後一個星期一

  • Oracle 先找到錢一個月份的最後一天,然後調用next_day函數計算緊隨的前一個月的最後一天的第一個星期一,也就是當前月份的第一個星期一。

    先用trunc函數計算當前月份的第一天,嗲用last_day函數找到這個月的最後一天,然後再減去7,然後根據減去7之後的日子爲起點,尋找第一個星期一。

    select next_day(trunc(sysdate,'mm')-1,'MONDAY')first_monday,
    	next_day(last_day(trunc(sysdate,'mm'))-7,'MONDAY')last_monday
    from dual
    
  • Mysql 先找出當前月份的第一天,然後緊接着找出第一個星期一,第七行開始的case when語句,dayofweek函數中,星期一對應的值是2(星期天對應的值是1)

    select first_monday,
    	case month(adddate(first_monday,28))
    		when mth then adddate(first_monday,28)
    			else adddate(first_monday,21)
    	end last_monday
    from (
    	select case sign(dayofweek(dy) - 2)
      	when 0 then dy 
      	when -1 then adddate(dy,abs(dayofweek(dy) - 2))
      	when 1 then adddate(dy,(7-(dayofweek(dy)-2)))
      end first_monday,
      mth
    from (
    	select adddate(adddate(current)date,-day(current_date)),1)dy,
      	month(current_date)mth
      from t1
    )x
    )y
    

3.8 依據特定時間單位檢索數據

  • 指定月份、星期或者其他時間單位來篩選記錄行

  • 比如:找到入職月份是February或者December,而且入職當天是星期二的所有員工

  • Mysql

    select ename 
    from emp 
    where monthname(hiredate) in ('February','December') 
    	or dayname(hiredate) = 'Tuesday'
    
  • Oracle

    select ename 
    from emp 
    where rtrim(to_char(hiredate,'month')) in ('february','december')
    	or rtrim(...)
    

3.9 識別重疊的日期區間

  • 識別兩段時間是否衝突

  • Mysql

    select a.empno, a.ename,
    	concat('project',b.proj_id,
      'overlaps project',a.proj_...)
    

4. 常見的SQL連接模式

4.1 疊加行集(Union & Union all)

  • 如果需要顯示EMP表中部門ID等於10的信息以及DEPT表中各個部門的名稱和編號

    select ename as ename_and_dname, deptno 
    from emp 
    where deptno = 10 
    union all 
    select '--------', null   # 增加分隔符
    from t1 
    union all 
    select dname, deptno 
    from dept
    

    最終顯示的結果:

  • 注意點:

    • select後面需要展示的列的數目必須保持一致而且數據類型匹配
    • 如果有重複記錄,union all會默認納入;如果希望過濾掉重複記錄,可以使用union而不是union all
    • union相比union all 效率比較低(因爲union會多一個排序操作,然後進行排序以進行刪除多餘項)

4.2 查找只存在於一張表的數據(差函數)

  • 從源表找到那些目標表之中不存在的值

  • DEPT表中DEPTNO = 40的數據並不存在於EMP表中,怎麼把它找出來?

  • Oracle

    select deptno from dept 
    minus # minus要求參與運算的兩者必須有相同的數據類型和數據個數,而且不會反悔重複值;null值不會返回任何東西
    select deptno from emp
    
  • DB2

    select deptno from dept 
    except 
    select deptno from emp
    
  • Mysql

    select deptno 
    from dept 
    where deptno not in (select deptno from emp)  
    
    # 如果DEPTNO不是主鍵(不唯一)  
    select distinct deptno 
    from dept 
    where deptno not in (select deptno from emp)  
    
    
    
    # 如果想要用原始的方式進行查詢:
    select deptno from dept 
    where deptno not in(10,50,null)
    # 這樣是查不出結果的。因爲 in 本質上是一種 “all”運算。應該這樣寫:
    
    select deptno 
    from dept 
    where not (deptno = 10 or deptno = 50 or deptno = null)
    

4.3 從一個表檢索另一個表不相關的行(外鏈接)

  • Oracle

    select d.* 
    from dept d, emp e 
    where d.deptno = e.deptno(+) 
    	and e.deptno is null
    
  • Mysql

    select d.* 
    from dept d left outer join emp e 
    	on (d.deptno = e.deptno)
    where e.deptno is null
    

4.4 確定兩個表是否有相同的數據

  • 問題:想知道兩個表是否有相同的數據

    要求:sql不僅能找到不同的數據,還能找到重複的數據(怎麼判斷兩個表是否有相同的數據?怎麼把不同的數據提取出來?)

  • 先找出存在於員工表而不存在於視圖V的行,然後再找出存在於視圖V而不存在於員工表中的行,然後再將其union all

  • Oracle

    # 找出V中emp沒有的
    (select empno, ename, job, mgr, hiredate, sal, comm, deptno, 
    	count(*) as cnt
    from V 
    group by emptno, ename, job, mgr, hiredate, sal, comm, deptno 
     
    minus 
    
    select emptno,... 
     	count(*) as cnt 
    from emp 
    group by empno,...
    )
    
    # 加上
    union all 
    
    # 找出emp中V沒有的
    (
    select empno,... 
    	count(*) as cnt 
    from emp 
    group by emptno,...
      
    minus 
      
    select empno,...
      count(*) as cnt
    from v
    group by empno,...
    )
    
  • Mysql

    詳見https://teaching.applysquare.com/S/Course/index/cid/6319#S-Lesson-view_media-id-68834 18:00處

4.5 從多個表中返回缺失值

  • FULL OUTER JOIN

    select d.deptno, d.dname, e.ename 
    from dept d full outer join emp e 
    	on (d.deptno = e.deptno) 
    
  • union

    select d.deptno, d.dname, e.ename 
    from dept d right outer join emp e 
    	on (d.deptno = e.deptno) 
    	
    union  
    
    select d.deptno, d.dname, e.ename 
    from dept d left outer join emp e 
    	on (d.deptno = e.deptno) 
    

4.6 連接和聚合函數的使用

  • 考慮新增加一張bonus表,注意,存在重複記錄(有員工重複獲得bonus)

  • 這往往是不得不使用distinct的情形:

    • 比如要將工資表、獎金錶、員工表進行連接;然後再進行求和,假如有一個人他拿了兩次獎金,那麼他的工資也會被連接兩次;結果就是工資到最後會出問題。
  • 解決方案:調用聚合函數的時候使用distinct;或者調用聚合 函數之前使用distinct

    select deptno, 
    	sum(distinct sal) as total_sal, 
    	sum(bonus) as total_bonus
    from(
    	select e.empno, 
      	e.ename, 
      	e.sal, 
      	e.deptno, 
      	e.sal*case when eb.type = 1 then .1
      				when eb.type = 2 then .2
      				else .3
    					# 這裏的eb.type是約定的sal的一種計算方式,可以不用深究,要結合具體例子
      		end as bonus
     	from emp e, emp_bonus eb 
      where e.empno = eb.empno 
      and e.deptno = 10
    )x 
    group by deptno
    
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章