1. 程式人生 > 其它 >c++ log函式_資料分析系列:SQL筆試總結之神奇的視窗函式

c++ log函式_資料分析系列:SQL筆試總結之神奇的視窗函式

技術標籤:c++ log函式datatable的資料進行組內排序

f009ab4932de8eca0fedd7e964df87f8.png 什麼是視窗函式?

視窗函式就是類似於group by聚合函式,但又不同於聚合函式。聚合函式是將組內多個數據聚合成一個值,而視窗函式除了可以將組內資料聚合成一個值,還可以保留原始的每條資料。

比如說求平均數就是avg() over(partition by ** order by **)

其中over()函式的作用是將聚合函式的結果顯示在每條單獨的記錄當中。

partition by()函式的作用與group by()相似,在over()中運用partition by是指定哪一列進行分組,然後聚合函式就會在分好的組裡面進行聚合運算。

order by()函式就是順序聚合了。

下面簡單講一下序列函式:

ntile函式:

這是個切片分組函式,如ntile(3) over(partition by classroom order by student_id)

就是在每個班級內進行分組,同時按照學號排序,將每個班級的同學分為三組。

row_number,rank,dense_rank函式:

row_number:順序排序,按照排列不管數值相等於否,都是1,2,3,4,5,6

dense_rank:連續排序,如果兩個數值相同就會給相同的等級,如1,2,2,3,4,5

rank:跳躍排序,雖然有兩個相同的數值依然會給相同等級,但是過了這村沒這店了,該等級就跳過去了,如1,2,2,4,5,6

lag()和lead()函式:

這就是提前和滯後函數了。

如lag(student,1) over(partition by classroom )

就是以班級這一列分組,將班級中同學的學號整體向後移動一位。

first_value(),last_value函式:

顧名思義,這就是第一個值和最後一個值

如first_value(cale_date) over(partition by shapname order by sale_date)

先按照商店名分組,再在組內按照銷售時間排序,最後用first_value求出每個店鋪的最早銷售日期。

下面舉一些栗子,幾乎都是要用到視窗函式的。

f009ab4932de8eca0fedd7e964df87f8.png 一:商品訂單資料

資料表:

訂單表orders,

欄位有('order_id'訂單號,

'user_id‘使用者編號’,

'order_pay‘訂單金額’ ,

'order_time‘下單時間’,

'商品一級類目commodity_level_I',

'商品二級類目commodity_level_2')

1.求最近7天內每一個一級類目下成交總額排名前3的二級類目:

思路:先找出最近7天的一級二級類目,以及總交易額,再在此基礎上用row_number排序,再按照排序找到前三即可。注意縮排哦。

select commodity_level_1,        commodity_level_2,        total_payfrom        (select commodity_level_1,                commodity_level_2,                total_pay,                row_number() over(partition by commodity_level_1 order by a.total_pay desc)                            as rank         from                (select commodity_level_1,                        commodity_level_2,                        sum(order_pay) as total_pay                from orders                where datediff(now() , order_time) <= 7                group by commodity_level_1,commodity_level_2                ) a        ) bwhererank<=3

2.提取8.1-8.10每一天消費金額排名在101-195的user_id

思路:先找出8.1-8.10的資料以及總消費金額,再用row_number排序,最後提取相應要求的排名即可,記住要轉化日期和縮排哦。

select user_id,        order_date,        total_payfrom        (select user_id,                total_pay,                row_number() over(partition by order_date order by a.total_pay desc) as rank        from                (select user_id,                        sum(order_pay) as total_pay,                        convert(order_time,date) as order_date                from orders                where convert(order_time,date) between '20180801' and '20180810'                group by convert(order_time,date),user_id                ) a        ) bwhere rank between '101' and '195'
f009ab4932de8eca0fedd7e964df87f8.png 二:活動運營資料分析

資料表

表1——訂單表orders,

欄位有(user_id‘使用者編號’,

order_pay‘訂單金額’ ,

order_time‘下單時間’)

表2——活動報名表act_apply,

欄位有(act_id‘活動編號’,

user_id‘報名使用者’,

act_time‘報名時間’)

1.活動運營資料分析-統計每個活動對應所有使用者在報名後產生的總訂單金額,總訂單數

思路:不難,自己想想就行

select user_id,       count(*) as '總訂單數',       sum(order_pay) as '總訂單金額'from orders left join act_applyon orders.user_id = act_apply.user_idwhere order_time >= act_timegroup by user_id

2.統計每個活動從開始後到當天(考試日)平均每天產生的訂單數,活動開始時間定義為最早有使用者報名的時間。(涉及到時間的資料型別均為:datetime)

思路:先找出每個活動的開始時間,再找出活動開始時間到現在的時間差,用總訂單數除以該時間差即可。

select act_id,count(*)/datediff(now(),first_act) as '平均訂單'from orders aleft join        (select user_id,                act_id,        min(act_time) over(partition by act_id ) as first_act        from act_apply        group by act_id        ) bon a.user_id = b.user_idwhere order_time>=act_timegroup by act_id,first_act
f009ab4932de8eca0fedd7e964df87f8.png 三:使用者行為路徑分析

表1——使用者行為表tracking_log,

欄位有(user_id‘使用者編號’,

opr_id‘操作編號’,

log_time‘操作時間’)

1.統計每天符合以下條件的使用者數:A操作之後是B操作,AB操作必須相鄰

思路:先把日期lag一下,新列出一列,再比較即可

select log_date,       count(*) as '使用者數'from       (select distinct user_id,              opr_id as cur_opr,              convert(log_time,date) as log_date,              lag(opr_id,1) over(partition by user_id,convert(log_time,date) order by log_time) as next_opr       from tracking_log       ) awhere a.cur_opr==A and a.next_opr==Bgroup by  log_date

2.統計使用者行為序列為A-B-D的使用者數,其中:A-B之間可以有任何其他瀏覽記錄(如C,E等),B-D之間除了C記錄可以有任何其他瀏覽記錄(如A,E等)

思路:用like+%判斷,外加group_concat函式

select count(*) as resultfrom(select user_id,       group_concat(opr_id order by log_time) as user_behavior_pathfrom tracking_loggroup by user_idhaving (user_behavior_path like '%A%B%D') and (user_behavior_path not like '%A%B%C%D')) a
f009ab4932de8eca0fedd7e964df87f8.png 四:使用者留存分析

表:使用者登陸表user_log,

欄位有(user_id‘使用者編號’,

log_date‘登陸時間’)

1.求每天新增使用者數,以及他們第2天、30天的留存率

思路一:不要視窗函式,用case when

select l.first_date as '日期',       count(distinct l.user_id) as '新增使用者數',       round(count(distinct case when (datediff(log_date,first_date) == 1 then l.user_id else null end)/       count(distinct l.user_id)),2       ) as '次日留存率',       round(count(distinct case when (datediff(log_date,first_date) == 29 then l.user_id else null end)/       count(distinct l.user_id)),2       ) as '30日留存率'from user_log lleft join(select distinct user_id,       min(log_date) as first_datefrom user_loggroup by user_id) ton t.user_id = l.user_idgroup by first_date

思路二:用視窗函式找出第一天,第二天,第30天的使用者登入,相除即可

即先找到每個使用者第一個登入的時間,用第一次登入時間聚合,統計distinct使用者,即為每天的新增使用者

計算第二天回訪的使用者量,計算當前時間與第一次登入的時間差即可

第30天使用者回訪也這麼做即可

select a.date as '日期',b.2_back/a.new as '第二天的回訪率',c.3_back/a.new as '第30天的回訪率'from(select convert(log_date,date) as 'date' ,        count(distinct user_id) as newfrom       (       select user_id,              min(log_date) over(partition by user_id) as first_date,              log_date       from user_log       ) aagroup by aa.first_date) aleft joinselect convert(log_date) as date,       count(user_id) as 2_backfrom       (       select user_id,              date_log,              min(log_date) over(partition by user_id) as first_date       from user_log       ) bbwhere datediff(bb.date_log,bb.first_date) = 1group by bb.first_date) bon a.date=b.dateleft joinselect convert(log_date) as date,       count(user_id) as 30_backfrom       (       select user_id,              date_log,              min(log_date) over(partition by user_id) as first_date       from user_log       ) ccwhere datediff(cc.date_log,cc.first_date) = 29group by bb.first_date) con a.date=c.date

2.找近90天,30天,7天的登入人數

思路:看了上面的,應該有思路了吧,用case when判斷即可

select       count(distinct case when datediff(now(),log_date()) <= 90 then user_id else null end) as 90_log_users,       count(distinct case when datediff(now(),log_date()) <= 30 then user_id else null end) as 30_log_users,       count(distinct case when datediff(now(),log_date()) <= 7 then user_id else null end) as 7_log_usersfrom user_log

3.求使用者近一個月平均登入時間間隔(按天)

思路:用視窗函式lead新建一列,再進行比較,就能知道平均登陸時間間隔,記住在每個user單次下進行的哦。

select user_id ,avg(diff)from       (       select user_id,              lead(log_time,1) over(partition by user_id order by log_time) - log_time as diff       from user_log       ) twhere datediff(now(),log_time)<=30group by user_id
f009ab4932de8eca0fedd7e964df87f8.png 五:統計(中/四分位數,眾數等)

欄位:店鋪id(shop_id),

銷量(sale),

商品id(commodity_id).

1.求每個店鋪商品銷量排名的中位數

思路一:設每個店鋪銷售量組成的序列長度為cnt,

當cnt為偶數的時候,中位數所在的序號是cnt/2,cnt/2+1;

當cnt為奇數的時候,中位數所在的序號為ceiling(cnt/2)

select shop_id,avg(sale) as resultfrom(select sale,       count(1) over(partition by shop_id) as total,       cast(count(1) over(partition by shop_id) as decimal) / 2 as even_mid,       ceiling(cast(count(1) over(partition by shop_id) as decimal) / 2) as odd_mid,       row_number() over(partition by shop_id) as sale_rankfrom orders) twhere (total%2=0 and t.sale_rank in (even_mid,even_mid+1)) or (total%2=1 and t.sale_rank = odd_mid)group by shop_id

思路二:abs(rn-(cnt+1)/2)<1

解釋下上面的公式,rn就是長度為cnt的序列的排序,即為row_number,

如果是奇數,1,2,3,4,5,那他的中位數就是3,|3-(5+1)|=0;

如果是偶數,1,2,3,4,那他的中位數所在的序號就是2,3,

那麼2-(4+1)/2=-0.5,3-(4+1)/2=0.5

所以可見,不管他是技術還是偶數,abs(rn-(cnt+1)/2)<1

select shop_id,avg(sale) as medianfrom       (       select shop_id,              sale,              row_number() over(partition by shop_id order by sale) as rn,              count(1) over(partition by shop_id) as cnt       from orders       ) awhere abs(rn - (cnt + 1) / 2) < 1group by shop_id

思路三:不用視窗函式,不排序,直接利用中位數定義

有點繞,儘量理解下:

當一個數組cnt為奇數的時候,大於中位數的數值個數等於小於中位數的數值個數。

當一個數組cnt為偶數的時候,那麼中位數就等於排序後中間兩個數的平均值,如果這個陣列每個數都是唯一的,那麼就會發現對於這兩個數來說,大於他們的數值的個數和小於他們數值的個數的絕對值等於1,即為這兩個數出現的頻率。如果這個陣列的值不是唯一的,你會發現也成立。

總結一下,不管他們是長度是奇數還是偶數,中位數出現的頻率一定大於等於(大於它的數和小於它的數的絕對值之差)

步驟如下:

1.自連結

2.計算各個數字出現的頻率:sum(case when t1.sale=t2.sale then 1 else 0 end)

3.算出大於它的數和小於它的數的絕對值之差,用於比較:abs(sum(sign(t1.sale-t2.sale)))

select shop_id,avg(sale) as medianfrom       (       select t1.shop_id,              t1.sale,       from orders t1       left join orders t2       on t1.shop_id=t2.shop_id       group by t1.shop_id,t2.shop_id       having sum(case when t1.sale=t2.sale then 1 else 0 end) >=       abs(sum(sign(t1.sale-t2.sale)))       ) tgroup by t.shop_id

2.求每個店鋪訂購商品的眾數

思路:用視窗函式,求出每個店鋪商品的數量,再找出其中的最大值,就是眾數了

select .shop_id,commodity_id,order_numfrom       (       select shop_id,              commodity_id,              order_num,              max(order_num) over(partition by shop_id) as max_order_num       from              (              select shop_id,                     commodity_id,                     count(*) as order_num              from orders              group by shop_id,commodity_id              ) t1       ) t2where order_num = max_order_num

3.求四分位數

三個表

T1:good_id,cate_id(分類)

T2:mall_id(店鋪), good_id

T3:mall_id, credit_score(信用分)

問,在不同分類中,店鋪的信用分的top25%

思路:先將三個表連起來,再用row_number排序,再用count找出每個分類的個數,最後提出前25%即可。

select cate_id,mall_id,credit_scorefrom(select cate_id,mall_id,credit_score,row_number() over(partition by cate_id order by credit_score desc) as score_rn,count(*) over(partition by cate_id) as mall_totalfrom       (       select t1.cate_id,              t2.mall_id,              t3.credit_score       from t1 left join (t2 left join t3 on t2.mall_id=t3.mall_id) on t1.good_id=t2.good_id       group by t1.cate_id,t2.mall_id       ) t)where score_rn <= mall_total*0.25
f009ab4932de8eca0fedd7e964df87f8.png 六:GMV周同比統計

欄位:時間(sale_date),

店鋪類別(cate_id),

店鋪數量(mall_num),gmv

1.拼多多618前後一週內各店鋪類別gmv的日均提升幅度和比例

注:以下解法只適用於資料連續情況,如果資料不連續,例如對於上一週沒有星期六,星期日的資料,lead(gmv,7)這樣規定移動視窗必然會出現錯誤。

思路:用lead視窗函式就行

select cate_id,       avg(gmv_diff),       avg(gmv_rate)from       (       select sale_date,              cate_id,              mall_num,              gmv,              (lead(gmv,7) over(partition by cate_id order by sale_date) - gmv) as gmv_diff ,              (lead(gmv,7) over(partition by cate_id order by sale_date) - gmv)/gmv as gmv_rate       from T       where convert(sale_date,date) between '20190611' and '20190624'       ) twhere convert(sale_date,date) between '20190611' and '20190624'group by cate_id

2.在618前一週gmv top20%,20-40%等這5類商鋪在618後一週內gmv日均提升幅度和比例

思路:1.算出每個店鋪的sum(gmv)

2.用case when標出gmv_quantile

3.和上面一樣,用lead算出gmv_diff和gmv_rate,用上面的gmv_quantile判斷是否在範圍內

4.程式碼太長我懶得寫了,hahah

f009ab4932de8eca0fedd7e964df87f8.png 七:連續區間問題

關於這題,我看到過一個絕佳的栗子,和大家分享下:

題目:有一張使用者簽到表【user_attendence】,標記每天使用者是否簽到(說明:該表包含所有使用者所有工作日的出勤記錄)

包含三個欄位:日期【date】,

使用者id【user_id】,

使用者當天是否簽到【user_sign_in:0否1是】;

問題1:請計算截至當前每個使用者已經連續簽到的天數(輸出表僅包含當天簽到的所有使用者,計算其連續簽到天數)

輸出表【t_user_consecutive_days】:使用者id【fuser_id】,使用者聯絡簽到天數【fconsecutive_days】

思路:先找使用者最近一次未簽到日期,再用今天減那個日期

create table t_user_consecutive_days asselect user_id,       datediff(now(), max_sign_date_0) as fconsecutive_daysfrom       (       select user_id,              max(date) as max_sign_date_0       from user_attendence       where user_sign_in = 0       group by user_id       ) tgroup by user_id

問題2:請計算每個使用者歷史以來最大的連續簽到天數(輸出表為使用者簽到表中所有出現過的使用者,計算其歷史最大連續簽到天數)

輸出表【user_max_days】:使用者id【user_id】,使用者最大連續簽到天數【max_days】

思路:把所有使用者的簽到轉化成一個0-1序列,找出其中連續1的最長序列的len()

create table user_max_days asselect user_id,       max(len(cut_record)) as max_daysfrom       (       select user_id,              record,              cut_record,       from              (              select user_id,                     group_concat(user_sign_in) as record              from user_attendence              group by user_id              ) t1       lateral view explode(split(record,'0')) t as cut_record       ) t2where cut_record <> ' 'group by user_id
f009ab4932de8eca0fedd7e964df87f8.png 八:學生成績分析

表:Enrollments

欄位:student_id,course_id,grade

1.查詢每位學生獲得的最高成績和它所對應的科目,若科目成績並列,取 course_id 最小的一門。查詢結果需按 student_id 增序進行排序。

思路一:用視窗函式dense_rank來排序grade,再用row_number來排序course_id

select a.student_id,a.course_id,a.gradefrom       (       select student_id,course_id,grade,              row_number() over(partition by student_id order by course_id) as course_rank       from              (              select student_id,course_id,grade,                     dense_rank() over(partition by student_id order by grade) as grade_rank              from Enrollments              ) t       where t.grade_rank = 1       ) awhere a.course_rank = 1order by student_id

思路二:用in解法,更快捷簡單

select student_id,       min(course_id) as course_idfrom Enrollmentswhere (student_id,grade) in       (select student_id ,              max(grade)       from Enrollments       group by stucent_id)group by student_idorder by student_id
f009ab4932de8eca0fedd7e964df87f8.png 九:學生做題情況分析

表t:做題日期(time),學生id(stu_id),題目id(exer_id)

統計10.1-10.10每天做新題的人的數量,重點在每天

思路:新題的判斷是先找出每位同學在做每道題的第一次時間,即為新題。再distinct後看是否連續做了十天即可

select count(1) as result(select stu_id,       count(distinct a.first_time) as cntfrom       (       select stu_id,              exer_id,              time,              min(time) over(partition by stu_id,exer_id) as first_time       from T       where convert(time,date) between '20191001' and '10191010'       ) awhere a.time = T.timegroup by stu_idhaving cnt = 10) b

參考文獻:

https://zhuanlan.zhihu.com/p/116012207

https://zhuanlan.zhihu.com/p/117498021