SQL心法:從基礎(chǔ)到進(jìn)階

對(duì)于數(shù)據(jù)分析師而言,把數(shù)取對(duì)是一項(xiàng)最基本的能力,因?yàn)樗械臄?shù)據(jù)加工、分析工作都依賴(lài)于這一步。在互聯(lián)網(wǎng)公司筆試中SQL的考察也是不可少的一道坎。

對(duì)于產(chǎn)品經(jīng)理和運(yùn)營(yíng)而言,具備基本的取數(shù)能力則能夠節(jié)省很多繁瑣的溝通,同時(shí)對(duì)解讀數(shù)據(jù)看板、溝通數(shù)據(jù)需求、合理設(shè)計(jì)項(xiàng)目的KPI也大有裨益。工作中偶爾會(huì)有運(yùn)營(yíng)同學(xué)問(wèn)有沒(méi)有SQL的學(xué)習(xí)鏈接,其實(shí)SQL學(xué)起來(lái)并不難,這個(gè)系列將用3天時(shí)間讓你上手SQL實(shí)操。

全文目錄:

--【1】全量表和增量表
--【2】SQL的語(yǔ)法句式
------【2.1】select...from...where...
------【2.2】group by 和 having
------【2.3】case..when..then..else..end

--【3】SQL的連接方法
------【3.1】...left join...
------【3.2】...inner join...
------【3.3】...full join...

--【4】SQL內(nèi)置函數(shù)
------【4.1】聚合函數(shù)
------------【場(chǎng)景1】對(duì)賣(mài)家當(dāng)日訂單進(jìn)行聚合統(tǒng)計(jì)
------【4.2】窗口函數(shù)
------------【場(chǎng)景2】顯示每個(gè)訂單中的賣(mài)家當(dāng)日銷(xiāo)售情況
------------【場(chǎng)景3】查詢(xún)每個(gè)品類(lèi)下總實(shí)收入排名前10的賣(mài)家
------------【場(chǎng)景4】查詢(xún)連續(xù)2天下單的人
------------【場(chǎng)景5】每個(gè)品類(lèi)訂單數(shù)量月環(huán)比
------------【場(chǎng)景6】對(duì)各品類(lèi)下的賣(mài)家按GMV分桶、顯示排名和累計(jì)百分比
------【4.3】日期函數(shù)
------【4.4】數(shù)學(xué)函數(shù)
------【4.5】字符串函數(shù)
------【4.6】其他函數(shù)
------------【場(chǎng)景7】從不同流量表合并當(dāng)日登錄的用戶(hù)ID
------------【場(chǎng)景8】多列合并到1列
------------【場(chǎng)景9】1行拆分成多行

備注:不同的數(shù)據(jù)庫(kù)環(huán)境,函數(shù)及其使用方法不太一致,總體上大同小異。

【1】全量表和增量表

在企業(yè)的數(shù)據(jù)倉(cāng)庫(kù)中,存在著許多從生產(chǎn)表生成的抽表,這些抽表在物理上存放在不同的分區(qū),分區(qū)就好像抽屜一樣,我們?cè)谑褂肧QL取數(shù)時(shí),必須告訴系統(tǒng),我們要從哪些“抽屜”中取數(shù)據(jù)。
一般最常見(jiàn)的是按照天為分區(qū),每日刷新數(shù)據(jù),分析師在抽表的基礎(chǔ)上根據(jù)業(yè)務(wù)需求進(jìn)行加工提煉出基于某個(gè)主題的加工表應(yīng)用于分析。數(shù)倉(cāng)的抽表可分為兩種類(lèi)型:全量表和增量表。

全量表是截至某個(gè)日期的全量狀態(tài)拍照,每個(gè)分區(qū)數(shù)據(jù)量級(jí)只增不減;
增量表記錄某個(gè)日期新增加的業(yè)務(wù)記錄,每個(gè)分區(qū)數(shù)據(jù)量級(jí)有增有減

舉例而言,用戶(hù)表是全量表,訂單表是增量表。
如果以天為分區(qū),用戶(hù)表中,最新的分區(qū)記錄著到目前為止全部的注冊(cè)用戶(hù)信息,而訂單表最新的分區(qū)僅記錄當(dāng)日的訂單。

在我們進(jìn)行查詢(xún)之前,理清楚所用到的表是全量表還是增量表十分重要,全量表只需要取最新日期的分區(qū)即可。如果把全量表誤認(rèn)為是增量表,一次取過(guò)去30天的數(shù)據(jù),數(shù)據(jù)會(huì)大量重復(fù),加之全量表本身數(shù)據(jù)量十分龐大,如果再跟其他表做關(guān)聯(lián)查詢(xún),則極大可能把系統(tǒng)資源跑崩掉。——這也是新人常見(jiàn)易犯的錯(cuò)誤。
備注:本文中的案例使用MySQL,沒(méi)有涉及分區(qū)的概念

從上篇:SQL心法:MySQL安裝使用中,我們已經(jīng)在MySQLWorkbench中導(dǎo)入數(shù)據(jù)生成了以下表,dt字段代表業(yè)務(wù)記錄生成的日期(格式為:"yyyy-mm-ss"):

一個(gè)用戶(hù)全量表usertb,字段:
dt,user_id
一個(gè)訂單增量表ordertb,字段:
dt,user_id,order_id,seller_id,cat_id,payment
一個(gè)賣(mài)家全量表sellertb,字段:
dt,seller_id,cat_id
一個(gè)品類(lèi)全量表categorytb,字段:
dt,category_id

【2】SQL的語(yǔ)法句式

SQL是不分大小寫(xiě)的,select和SELECT都是一樣可用,看個(gè)人習(xí)慣。SQL語(yǔ)法其實(shí)非常直白易懂,這里不說(shuō)建表和寫(xiě)入表操作,只單純講解數(shù)據(jù)查詢(xún)技巧。查詢(xún)的語(yǔ)法結(jié)構(gòu)如下:

select 字段1[,字段2...字段N]
from tb
where 條件1
group by 字段a
order by 字段b
having 條件2
【2.1】select...from...where...

這是最簡(jiǎn)單的查詢(xún)語(yǔ)句了,表示從表中查找符合條件的記錄行。
比如我們要查表usertb中2015年2月注冊(cè)的所有用戶(hù):

select * 
from usertb
where dt like '2015-02%'
備注:%代表任意長(zhǎng)度的字符串,點(diǎn)擊閃電?圖標(biāo)即可運(yùn)行代碼
【2.2】group by 和 having

我們常對(duì)周期內(nèi)的業(yè)務(wù)數(shù)據(jù)進(jìn)行匯總,這時(shí)需要用到分組,比如:統(tǒng)計(jì)每周訂單數(shù),找出其中訂單數(shù)在1000+的周:

select weekofyear(dt), count(distinct order_id) as order_cnt
from ordertb
group by weekofyear(dt)
having order_cnt>1000
【2.3】case..when..then..else..end

當(dāng)我們需要根據(jù)條件自定義一個(gè)字段時(shí),可以用case when 語(yǔ)句。
比如,取2012年高中低消費(fèi)等級(jí)的訂單數(shù)。
這里高中低消費(fèi)等級(jí)是我們新定義的:
100以上為高消費(fèi),30-100為中消費(fèi),30以下為低消費(fèi)。
那么,按照新定義的消費(fèi)等級(jí)字段進(jìn)行分組,即可得到結(jié)果:

select 
    case when payment>100 then '高消費(fèi)' 
        when payment>30 then '中消費(fèi)'
        else '低消費(fèi)'
    end as pay_level, 
    count(distinct order_id) as order_cnt
from ordertb
where dt between '2012-01-01' and '2012-12-31'
group by 
case when payment>100 then '高消費(fèi)' 
    when payment>30 then '中消費(fèi)'
    else '低消費(fèi)'
end


注意:在case when 語(yǔ)句中,前排的語(yǔ)句優(yōu)先級(jí)大于后排,如果其他語(yǔ)句不變,單把“>100”和“>30”換個(gè)位置,試試看會(huì)發(fā)生什么?——“>100”那行語(yǔ)句不會(huì)被執(zhí)行了,輸出如下:

【3】SQL的連接方法

網(wǎng)絡(luò)上各種連接方法都能搜索到,但我個(gè)人覺(jué)得少即是多,熟練運(yùn)用以下三種就足夠了:

【3.1】...left join...

select * from A left join B on A.uid=B.uid
表A中所有記錄行都在結(jié)果中,而表B只有uid跟A表匹配的記錄行才出現(xiàn),不能與A.uid匹配的記錄行為空。
比如,查詢(xún)2015-02-05日注冊(cè)用戶(hù)當(dāng)日在品類(lèi)id:28下單的記錄,結(jié)果中,左邊表的數(shù)據(jù)會(huì)完整保留,而當(dāng)日沒(méi)有下單的用戶(hù)右邊表的取值為null:

select 
t1.*,
t2.*
from usertb t1
left join ordertb t2
    on t1.user_id=t2.user_id
    and t2.dt ='2015-02-05'
    and t2.cat_id=28
where t1.dt ='2015-02-05'
【3.2】...inner join...


inner join 中的inner可一省掉不寫(xiě):select * from A join B on A.uid=B.uid
只有兩個(gè)表的uid相匹配的記錄行才出現(xiàn)在結(jié)果中。

上面同一個(gè)例子,如果把left join換成inner join,則結(jié)果僅會(huì)保留兩個(gè)表中都共同存在的用戶(hù)的記錄:

select 
t1.*,
t2.*
from usertb t1
inner join ordertb t2
    on t1.user_id=t2.user_id
    and t2.dt ='2015-02-05'
    and t2.cat_id=28
where t1.dt ='2015-02-05'
【3.3】...full join...

select * from A full join B on A.uid=B.uid
表A和表B在uid匹配的記錄行合并數(shù)據(jù),不能匹配的記錄行左右各自保留數(shù)據(jù),另一方的記錄為空,所以結(jié)果里面包含了兩個(gè)表所有的數(shù)據(jù)。如果加一條where A.uid is null or B.uid is null,那么就把兩個(gè)表uid匹配的行刪掉,剩下的是僅其中一方有的。

【4】SQL內(nèi)置函數(shù)

SQL提供了很多方便易用的內(nèi)置函數(shù),來(lái)處理數(shù)據(jù)格式和進(jìn)行數(shù)據(jù)加工計(jì)算,接下來(lái)我們將結(jié)合使用場(chǎng)景來(lái)研究SQL的基本和進(jìn)階用法。五角星??的個(gè)數(shù)代表難度級(jí)別指數(shù)。

聚合函數(shù)

聚合函數(shù)比較簡(jiǎn)單,因此不用詳細(xì)介紹,

avg ??
max ??
min ??
count ??
sum ??

【場(chǎng)景1】對(duì)賣(mài)家當(dāng)日訂單進(jìn)行聚合統(tǒng)計(jì)
查詢(xún)2012-09-10日訂單的均實(shí)付客單價(jià)、最大/最小實(shí)付客單價(jià)、總下單人數(shù)、總實(shí)收入:

select seller_id
    avg(payment),
    max(payment),
    min(payment),
    count(distinct user_id),
    sum(payment)
from ordertb
where dt='2012-04-10'
group by seller_id

窗口函數(shù)

avg ????
count ????
max ????
min ????
sum ????

【場(chǎng)景2】顯示每個(gè)訂單中的賣(mài)家當(dāng)日銷(xiāo)售情況
2012-09-10日每個(gè)訂單的實(shí)付客單,以及對(duì)應(yīng)的賣(mài)家的均實(shí)付客單價(jià),最大/最小實(shí)付客單價(jià)、總下單人次、總實(shí)收入(比較一下這種寫(xiě)法跟用group by哪里不同?):

select
    order_id,
    payment,
    seller_id,
    avg(payment) over(partition by seller_id),
    max(payment) over(partition by seller_id),
    min(payment) over(partition by seller_id),
    count(distinct user_id) over(partition by seller_id),
    sum(payment) over(partition by seller_id)
from ordertb
where dt='2012-09-10'

row_number ??????
用途:分組在組內(nèi)對(duì)記錄逐行進(jìn)行排序
備注:窗口函數(shù)不能出現(xiàn)在同一個(gè)Select查詢(xún)的where子句下,這時(shí)候使用qualify條件語(yǔ)句篩選,而不是having。某些數(shù)據(jù)庫(kù)不支持qualify用法,則需要增加一層嵌套進(jìn)行篩選,我們這里使用嵌套方法。

【場(chǎng)景3】查詢(xún)每個(gè)品類(lèi)下總實(shí)收入排名前10的賣(mài)家

use mydata;
select
*
from(
    select 
    cat_id,
    seller_id,
    row_number() over(partition by cat_id order by gmv desc) as rk
    from
        (
        select
        cat_id,
        seller_id,
        sum(payment) as gmv
        from ordertb 
        where dt between '2013-04-01' and '2013-04-10'
        group by cat_id,
        seller_id
        ) a 
    ) b
where rk<=10

上面代碼,用row_number的話(huà),如果第10個(gè)跟第11個(gè)相等的gmv,那么排在第11位的seller_id則不會(huì)出現(xiàn)于結(jié)果中

dense_rank ??????
用途:分組在組內(nèi)對(duì)記錄進(jìn)行排序,用dense_rank的話(huà),如果第10個(gè)跟第11個(gè)相等的gmv,那么第10、11個(gè)都是同樣的排名,都出現(xiàn)于結(jié)果中

rank ??????
用途:分組在組內(nèi)對(duì)記錄進(jìn)行排序,用rank的話(huà),如果第10個(gè)跟第11個(gè)相等的gmv,那么第10、11個(gè)都是同樣的排名,都出現(xiàn)于結(jié)果中,并且第11名置空,后續(xù)記錄從第12名開(kāi)始

row_number、dense_rank、rank這三個(gè)窗口函數(shù)在使用時(shí)要結(jié)合業(yè)務(wù)使用場(chǎng)景決定需要用那個(gè),避免搞混淆。

【場(chǎng)景4】查詢(xún)連續(xù)2天下單的人
備注:關(guān)鍵在于怎么識(shí)別連續(xù)——只要把訂單日減按用戶(hù)ID分組的訂單排序,如果一個(gè)用戶(hù)連續(xù)下單的話(huà),相減得到的日期相等

select * 
from(
    select  user_id,diff,
        count(1) as days
    from (
        select 
            user_id, dt,
            row_number() over(partition by user_id order by dt asc) as rk,
            date_add(dt, interval -(row_number() over(partition by user_id order by dt asc)) day) as diff
        from (select
                user_id, dt
                from ordertb
                where dt between '2012-04-01' and '2013-04-10'
                group by user_id, dt
            )basedata
    )orderdt_minus_rownum
    group by user_id,
    diff
) conti_days
where days=2

lag & lead ????????
用途:分組在組內(nèi)找往過(guò)去/未來(lái)數(shù)第N個(gè)數(shù)據(jù)

【場(chǎng)景5】每個(gè)品類(lèi)訂單數(shù)量月環(huán)比

select
    mon,
    cat_id,
    ordernum,
    lag(ordernum,1) over(partition by cat_id order by mon) as ordernum_t7,
    ordernum/lag(ordernum,1) over(partition by cat_id order by mon)-1 as rate_t7
from
(select 
    month(dt) as mon,
    cat_id,
    count(order_id) as ordernum
from ordertb
where dt between '2012-04-01' and '2013-04-10'
group by
    month(dt),
    cat_id) t

ntile ????????
用途:分組在組內(nèi)對(duì)某列進(jìn)行分桶
percent_rank ????????
用途:分組在組內(nèi)對(duì)某列找某條記錄所處的相對(duì)排名
cume_dist ????????
用途:分組在組內(nèi)對(duì)每列找某條記錄所處的累計(jì)百分比

【場(chǎng)景6】對(duì)各品類(lèi)下的賣(mài)家按GMV分桶、顯示排名和累計(jì)百分比

select 
    cat_id,
    seller_id,
    gmv,
    ntile(4) over(partition by cat_id order by gmv desc) as buck,
    percent_rank() over(partition by cat_id order by gmv desc) as prank,
    cume_dist() over(partition by cat_id order by gmv desc) as cdist
from
    (select
    cat_id,
    seller_id,
    sum(payment) as gmv
    from ordertb
    where dt between '2012-04-01' and '2013-04-10'
    group by cat_id,
    seller_id
) t

日期函數(shù)

date_add ????
用途:日期+時(shí)間間隔

date_add('2020-01-01',interval 2 day) # date往后推2天
date_add('2020-01-01',interval -2 day) # date往前推2天

datediff????
用途:第一個(gè)日期減去第二個(gè)日期得到的天數(shù),可正可負(fù)

datediff('2020-01-01','2008-11-29')

date_format????
用途:將時(shí)間轉(zhuǎn)化成指定格式

datepart('2018-06-08 01:10:00', 'yyyy') 
輸出:2018-06-08

weekday ????
用途:返回日期所在當(dāng)前周的第幾天。周一作為一周的第一天,返回值為0,周日返回6。輸入必須是日期類(lèi)型。

select weekday('2020-01-01')
輸出:2

weekofyear ????
用途:返回日期所在周為當(dāng)年的第幾周

weekofyear(to_date('20190421','yyyymmdd'))
weekofyear('2020-01-01')
輸出:1

數(shù)學(xué)運(yùn)算函數(shù)

round ??
用途:四舍五入
floor ??
用途:向下取整

round(118.815,2),
floor(118.815)
輸出:
118.82  
118 

字符串處理函數(shù)

concat & concat_ws????
用途:合并字符串,前者不接受空值,后者接受

select CONCAT('11','22','33')
>112233
select CONCAT('11','22',NULL)
>NULL
select CONCAT_WS(',','11','22','33')
>11,22,33
select CONCAT_WS(',','11','22',NULL)
>11,22

json_extract????
用途:從json格式中摘取變量值
備注:hive中用get_json_object()

場(chǎng)景:從{"orderId":"4","PayInfo":"[{"pay_id":1,"money":1650}]"}中獲取orderId和money的值。

select 
json_extract('{"orderId":4,"PayInfo":{"pay_id":1,"money":1650}}','$.orderId'),
json_extract('{"orderId":4,"PayInfo":{"pay_id":1,"money":1650}}','$.PayInfo.money')
輸出:
4
1650

md5 ??
用途:計(jì)算輸入字符串value的md5值,在需要對(duì)手機(jī)號(hào)碼加密時(shí)用到

select md5('13X13697810')
輸出:faf3cae4ba50be8b85ae80203f6dba96

substr ??
用途:按照指定開(kāi)始位置和長(zhǎng)度截取字符串,字符的位置從1開(kāi)始數(shù)

select substr('2019-04-10 10:10:10 ',1,10)
輸出:2019-04-10

其他函數(shù)

coalesce????
用途:在幾個(gè)變量中取第一個(gè)非空的值

select coalesce(null,123,'abc')
輸出:123

場(chǎng)景:有2個(gè)流量表,分別是一個(gè)產(chǎn)品在APP、第三方H5頁(yè)面的用戶(hù)訪(fǎng)問(wèn)情況,要求合并流量表得到當(dāng)天登錄的用戶(hù)ID(我們可以使用union方法,除此之外,還可以使用outer join)

select
coalesce(t1.dt,t2.dt) as dt,
coalesce(t1.user_id,t2.user_id) as user_id
from apppv_tb t1
full join h5pv_tb t2
on t1.user_id=t2.user_id
and t1.dt=t2.dt

greatest????
least ????
用途:若干變量中的最大最小值

select 
greatest(1,4,3,9), 
least(1,4,3,9)
輸出:
9
1

trans_cols??????
用途:根據(jù)關(guān)鍵字將一行的拆成多行

場(chǎng)景:某個(gè)表中mobile1和 mobile2字段分別記錄用戶(hù)的主用手機(jī)號(hào)碼和備用手機(jī)號(hào)碼,要求將mobile1和 mobile2放入一個(gè)字段,且分行顯示
輸入:

輸入:
user_id      mobile1         mobile2
小明          139XXXX0101     138XXXX0111
小王          135XXXX0101     135XXXX0111
select trans_cols(1, user_id, mobile1, mobile2) as (user_id, mobile)
輸出:
user_id   mobile1
小明      139XXXX0101
小明      138XXXX0111
小王      135XXXX0101
小王      135XXXX0111

trans_array??????
用途:根據(jù)關(guān)鍵字將一行的拆成多行,跟trans_cols不一樣的地方是,需要拆成多行的字段只有一個(gè),值之間用分隔符分開(kāi)。

場(chǎng)景:某個(gè)表中mobile字段記錄用戶(hù)的使用過(guò)的手機(jī)號(hào)碼,要求每個(gè)手機(jī)號(hào)碼分行顯示

輸入:
user_id   mobile                   recent_calltime
小明      139XXXX0101;138XXXX0111   20190120;20190220
小王      135XXXX0101;135XXXX0111   20180120;20190323
select trans_array(1, ';',user_id, mobile, recent_calltime) as (user_id, mobile, recent_calltime)
輸出:
user_id   mobile         recent_calltime
小明      139XXXX0101     20190120
小明      138XXXX0111     20190220
小王      135XXXX0101     20180120
小王      135XXXX0111     20190323
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
禁止轉(zhuǎn)載,如需轉(zhuǎn)載請(qǐng)通過(guò)簡(jiǎn)信或評(píng)論聯(lián)系作者。