文章目錄
源自南京大學軟件學院劉嘉老師的《數據庫開發》課程,課堂筆記
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)
- 計算出兩個日期之間相隔多少天
- 排除掉週末(檢索有多少條滿足“工作日”條件的記錄)
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