滴滴-數(shù)據(jù)分析

這是很早之前面的,第一次面數(shù)據(jù)分析的面試磨澡,當時還傻乎乎的以為數(shù)據(jù)分析和數(shù)據(jù)挖掘是一回事呢。結(jié)果才發(fā)現(xiàn)质和,數(shù)據(jù)分析崗位大多注重的是數(shù)據(jù)庫的能力稳摄,比如sql語句的考察,hive的考察饲宿,以及一些運營思維的考察厦酬,所以第一次面試就很悲劇啦,不過題目還是很有代表性的瘫想。其他的不寫了仗阅,這里只分享一個關于sql的題目。

1国夜、問題引出

現(xiàn)在有兩個數(shù)據(jù)表减噪,一個數(shù)據(jù)表記錄司機的信息,比如司機id车吹,司機姓名旋廷,司機注冊時間等等,一個數(shù)據(jù)表記錄一天的訂單情況礼搁,比如訂單ID饶碘,訂單司機id,訂單時間馒吴。寫sql語句扎运,返回每個司機今天最早的一筆訂單。兩個數(shù)據(jù)表如下圖所示:

用戶表userinfo
訂單表order

2饮戳、錯誤思路

好了豪治,模擬的數(shù)據(jù)我們準備完畢了,接下來我們就要開動腦筋解決這個問題了扯罐,想了半天负拟,腦子里蹦出這么一個想法,這不很簡單么歹河,我們先把兩個表鏈接起來掩浙,然后按照用戶進行一個分組,然后對數(shù)據(jù)排序秸歧,最后輸出第一個記錄不就好了厨姚,所以,我們寫了如下的sql語句:

select name,sex,register,ordertime from userinfo,orderinfo where userinfo.id = orderinfo.userid group by orderinfo.userid order by orderinfo.ordertime limit 1

信心滿滿地運行键菱,發(fā)現(xiàn) 報錯了谬墙!


這個only_full_group_by是什么鬼喲。百度了一下,這好像是mysql5.7版本的新特性拭抬,按照網(wǎng)上的方法部默,執(zhí)行如下的sql語句就可以取消這個模式:

SET sql_mode ='STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION';

再次執(zhí)行我們上面的sql語句,就當我們以為大功告成之時造虎,沒想到結(jié)果只輸出了一條傅蹂,而不是所有的用戶的每一條記錄:

我們來探究一下原因,是什么出現(xiàn)了問題累奈,我們一步步分析贬派,首先我們執(zhí)行表鏈接語句:

select name,sex,register,ordertime from userinfo,orderinfo where userinfo.id = orderinfo.userid 

結(jié)果正確急但,輸出如下:

隨后我們加入group by 語句:

select name,sex,register,ordertime from userinfo,orderinfo where userinfo.id = orderinfo.userid group by orderinfo.userid

在groupby語句的時候澎媒,已經(jīng)是返回每個用戶的一條記錄了:


剩下的兩不就不用解釋了,order by將上面返回的三條記錄進行一個排序波桩,最后limit 1返回了一條結(jié)果戒努。所以我們找到了問題所在,就是這個group by的問題镐躲,它只能返回每一組的一行储玫。

你可能會想,既然groupby只能返回一行萤皂,我們返回min(ordertime)不就好了:

select name,sex,register,min(ordertime) from userinfo,orderinfo where userinfo.id = orderinfo.userid group by orderinfo.userid

結(jié)果輸出為:

哇撒穷,結(jié)果是對的哎。結(jié)果真是對的么裆熙?如果我們在orderinfo里面加入了新的一列端礼,乘客姓名,將orderinfo表變?yōu)槿缦碌男问剑?/p>


實在沒有多余的腦細胞去想復雜的名字了入录,不過這已經(jīng)足以讓我們來解決問題了蛤奥。繼續(xù)運行上面的代碼,結(jié)果如下:

結(jié)果并不對,張三的第一個用戶應該是二號僚稿,細心的你可能已經(jīng)發(fā)現(xiàn)問題了凡桥,還是group by的問題,它返回的是鏈接之后分組的第一條記錄蚀同,min(ordertime)相當于是不在表中的一個新加入的字段缅刽,它的值通過min函數(shù)計算而來,所以會出現(xiàn)上面的結(jié)果蠢络。

3拷恨、看似正確的思路

那么解決這個問題的正確姿勢是什么呢?在融360面試的時候谢肾,我被問到了類似的問題腕侄,吸取在滴滴面試的教訓,我們用一個子查詢來解決這個問題:

select name,sex,register,ordertime,orderuser from userinfo,orderinfo where userinfo.id = orderinfo.userid and orderinfo.ordertime in (select min(ordertime) from orderinfo group by userid)

結(jié)果輸出如我們所預料:

可以看到,子查詢返回的每個用戶的當天最早的訂單時間冕杠,然后外層查詢用一個in微姊,返回訂單時間在最早訂單時間列表里的記錄。

當然分预,你可能會說兢交,有的司機可能沒有訂單,但我們也想要返回這個司機的信息笼痹,比如我在userinfo表里添加一條龍六的信息配喳,這時候,用如上的語句就不行了凳干,因為上面相當于內(nèi)鏈接晴裹,我們這時候要考慮左外鏈接,語句變?yōu)椋?/p>

select name,sex,register,ordertime,orderuser from userinfo left outer join orderinfo on userinfo.id = orderinfo.userid where orderinfo.ordertime in (select min(ordertime) from orderinfo group by userid)

結(jié)果輸出如下:


咦救赐,結(jié)果輸出好像跟我們之前沒有什么差別涧团?這是為什么呢?細心的你可能發(fā)現(xiàn)了经磅,我們用了where子句限定了ordertime的取值范圍泌绣,所以不會出現(xiàn)那些沒有訂單信息的用戶,所以我們還要對語句作如下修改预厌,讓ordertime可以為Null值:

select name,sex,register,ordertime,orderuser from userinfo left outer join orderinfo on userinfo.id = orderinfo.userid where orderinfo.ordertime in (select min(ordertime) from orderinfo group by userid) or orderinfo.ordertime is Null

結(jié)果正確輸出:

4阿迈、再思考

就當我信心滿滿以為這樣就結(jié)束并把帖子發(fā)出去的時候,群里的專家提出了疑問轧叽,如果有不同司機在同一時間接了訂單怎么辦苗沧?記錄可能如下:

這時候我們就會發(fā)現(xiàn),運行上面的代碼犹芹,某些用戶會返回兩條數(shù)據(jù)崎页,因為兩個用戶在同一時間接到了訂單,對于其中一個用戶來說腰埂,是當天的第一筆飒焦,對于另一個用戶來說,不是當天的第一筆屿笼,這樣就會出現(xiàn)問題了:

5牺荠、真正的正確思路

5.1 三表鏈接

大佬提供了兩種解決方案,一種是把子查詢的結(jié)果作為一個新表驴一,然后利用三表鏈接:

select name,sex,register,ordertime,orderuser from userinfo,orderinfo,(select userid,min(ordertime) as ordertime2 from orderinfo group by userid) as orderinfo2 where userinfo.id = orderinfo.userid and orderinfo.ordertime = orderinfo2.ordertime2 and orderinfo.userid = orderinfo2.userid

結(jié)果如下:

5.2 使用over函數(shù)(mysql不支持)

上面的結(jié)果是正確的休雌,不過太麻煩了吧,于是我們還有第二種解決方案肝断,使用row_number()/rank()/dense_rank() over(partition by)杈曲,這個在mysql中并沒有實現(xiàn)驰凛,在oracle或者sql server中是有實現(xiàn)的。

不過我們還是要來看一下這一語法的基本用法:

over()函數(shù):

over不能單獨使用担扑,要和分析函數(shù):rank(),dense_rank(),row_number()等一起使用恰响。
其參數(shù):over(partition by columnname1 order by columnname2)
含義:按columname1指定的字段進行分組,并按照
例如:employees表中,有兩個部門的記錄:department_id =10和20
select department_id涌献,rank() over(partition by department_id order by salary) from employees就是指在部門10中進行薪水的排名胚宦,在部門20中進行薪水排名。

row_number()函數(shù)

row_number()函數(shù)用于返回根據(jù)over函數(shù)分組排序結(jié)果的編號燕垃。例如row_number() OVER (PARTITION BY COL1 ORDER BY COL2) 表示根據(jù)COL1分組枢劝,在分組內(nèi)部根據(jù) COL2排序,而此函數(shù)計算的值就表示每組內(nèi)部排序后的順序編號(組內(nèi)連續(xù)的唯一的)
比如下面的例子中卜壕,我們按照部門進行分組您旁,然后按照薪水進行降序排序,最后一列表示排序后的組內(nèi)排名印叁。


row_number()在我們這道題目的背景下是適用的被冒,不過在其他的場景军掂,比如按照每個部分進行分組轮蜕,再按照工人的薪資進行降序排序,如果有兩個人的薪資相同蝗锥,這兩個人的row_number值不會相同跃洛,這種情況下row_number()函數(shù)就不再適用,我們可以考慮rank()或者dense_rank()函數(shù)與over函數(shù)結(jié)合使用终议。

不過汇竭,這也引出了row_number()函數(shù)另一個比較有趣的作用,根據(jù)某幾列進行去重:假設表TAB中有a,b,c三列穴张,可以使用下列語句刪除a,b,c都相同的重復行细燎。

DELETE FROM (select year,QUARTER,RESULTS,row_number() over(partition by YEAR,QUARTER,RESULTS order by YEAR,QUARTER,RESULTS) AS ROW_NO FROM SALE )   WHERE ROW_NO>1  

rank()函數(shù)

rank() over()是跳躍排序,有兩個第二名時接下來就是第四名(同樣是在各個分組內(nèi))

dense_rank()函數(shù)

dense_rank() over()是連續(xù)排序皂甘,有兩個第二名時仍然跟著第三名玻驻。相比之下row_number是沒有重復值的 .

好了,回到我們的題目偿枕,在oracle等其他數(shù)據(jù)庫中璧瞬,我們可以用下面的語法進行解決,但很遺憾渐夸,mysql不行(下面的語句沒有真正測試過嗤锉,因為在mysql環(huán)境中無法執(zhí)行,如果有錯誤墓塌,歡迎大家指正N脸馈)

select name,sex,register,ordertime,orderuser from userinfo,(select *,row_number() over(PARTITION by userid order by ordertime) as tn from orderinfo) as t1 where userinfo.id = t1.userid and t1.tn = 1

上面語句中的row_number()完全可以換做rank()或者dense_rank()奥额。同時,使用上面這種語法访诱,不僅僅是最早的一筆訂單披坏,最早的5筆,10筆都可以計算出盐数,功能十分強大棒拂。

5.3 mysql模擬實現(xiàn)rank_over

mysql沒有row_number()/rank()/dense_rank() over(partition by)這樣高級的sql語法,不過我們可以通過編程的方式來模擬實現(xiàn)類似的功能玫氢,下面給出了具體的代碼:

select u.name,u.sex,u.register,o.ordertime,o.orderuser from userinfo as u,(select orderinfo.*,if(@userid = orderinfo.userid,@rank:=@rank+1,@rank:=1) as rank,@userid:=orderinfo.userid from orderinfo,(select @rank:=0,@userid:=NULL) as b order by orderinfo.userid,orderinfo.ordertime) as o where u.id = o.userid and o.rank = 1

輸出結(jié)果正確如下:


上面的代碼中用到了mysql變量的知識帚屉,首先,我們大體講一下mysql中變量的相關知識漾峡。

mysql變量

mysql中變量不用事前申明毅人,在用的時候直接用“@變量名”使用就可以了。
第一種用法:set @num=1; 或set @num:=1; //這里要使用變量來保存數(shù)據(jù)住册,直接使用@num變量
第二種用法:select @num:=1; 或 select @num:=字段名 from 表名 where ……
注意上面兩種賦值符號趁矾,使用set時可以用“=”或“:=”,但是使用select時必須用“:=賦值”

使用變量添加行號

我們可以設置一個初始行號槽袄,接下來在 select語句中不斷改變行號的值即可:

set @i = 0;
select   (@i:=@i+1)  as   i,userinfo.*   from   userinfo

如果使用一句話烙无,我們可以將設置初始值的過程放在from后面:

select   (@i:=@i+1)  as   i,userinfo.*   from   userinfo,(select   @i:=0)   as  it 

運行效果如下:

上面的效果得以實現(xiàn),得益于mysql中變量在select被循環(huán)賦值的特性遍尺,即每取出一行截酷,i的值都會變化一次,而在sql server中乾戏,i不會被循環(huán)賦值迂苛,所有列的值都是最后一次的i值。

if語句

mysql中if語句的語法如下:

IF(expr1,expr2,expr3)

如果 expr1 是TRUE (expr1 <> 0 and expr1 <> NULL)鼓择,則 IF()的返回值為expr2; 否則返回值則為 expr3三幻。IF() 的返回值為數(shù)字值或字符串值,具體情況視其所在語境而定呐能。
比如下面的例子,我們根據(jù)司機的注冊時間劃分司機類型:

select name,if(register > '2017-08-05','A','B') as type from userinfo

輸出結(jié)果為:

有了上面的知識儲備之后念搬,我們之前實現(xiàn)的mysql語句也就不難理解了,我們首先在內(nèi)部生成了一個新表o催跪,新表o對司機進行了分組锁蠕,并按照接單時間先后進行了排序:

select orderinfo.*,if(@userid = orderinfo.userid,@rank:=@rank+1,@rank:=1) as rank,@userid:=orderinfo.userid from orderinfo,(select @rank:=0,@userid:=NULL) as b

可以看到,內(nèi)部查詢的輸出如下懊蒸,接下來就是簡單的兩表鏈接和篩選了:

6荣倾、總結(jié)

學習是一個不斷循環(huán)迭代的過程,這道題從最初的在滴滴面試中幼稚的group by 想法骑丸,到融360面試時自認為正確的解法舌仍,再到被大佬質(zhì)疑從而繼續(xù)思考妒貌,最終到基本get到解題的正確姿勢,經(jīng)歷了一系列迭代的過程铸豁。

希望我們在以后的學習過程中灌曙,能夠不斷的舉一反三,將知識一步步的掌握扎實节芥。

參考文章:

ROW_NUMBER() OVER()函數(shù)用法:http://www.cnblogs.com/alsf/p/6344197.html
mysql實現(xiàn)oracle分析函數(shù)功能 over:http://blog.csdn.net/mengtianyalll/article/details/45767603
MySql 申明變量以及賦值:
http://www.cnblogs.com/qixuejia/archive/2010/12/21/1913203.html
mysql數(shù)據(jù)庫 實現(xiàn)類似標記序號的偽列:
http://blog.csdn.net/ystyaoshengting/article/details/6904627
MySQL的if在刺,case語句使用總結(jié):
http://outofmemory.cn/code-snippet/1149/MySQL-if-case-statement-usage-summary

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市头镊,隨后出現(xiàn)的幾起案子蚣驼,更是在濱河造成了極大的恐慌,老刑警劉巖相艇,帶你破解...
    沈念sama閱讀 212,816評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件颖杏,死亡現(xiàn)場離奇詭異,居然都是意外死亡坛芽,警方通過查閱死者的電腦和手機留储,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,729評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來咙轩,“玉大人获讳,你說我怎么就攤上這事〕裟” “怎么了赔嚎?”我有些...
    開封第一講書人閱讀 158,300評論 0 348
  • 文/不壞的土叔 我叫張陵膘盖,是天一觀的道長胧弛。 經(jīng)常有香客問我,道長侠畔,這世上最難降的妖魔是什么结缚? 我笑而不...
    開封第一講書人閱讀 56,780評論 1 285
  • 正文 為了忘掉前任,我火速辦了婚禮软棺,結(jié)果婚禮上红竭,老公的妹妹穿的比我還像新娘。我一直安慰自己喘落,他們只是感情好茵宪,可當我...
    茶點故事閱讀 65,890評論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著瘦棋,像睡著了一般稀火。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上赌朋,一...
    開封第一講書人閱讀 50,084評論 1 291
  • 那天凰狞,我揣著相機與錄音篇裁,去河邊找鬼。 笑死赡若,一個胖子當著我的面吹牛达布,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播逾冬,決...
    沈念sama閱讀 39,151評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼黍聂,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了身腻?” 一聲冷哼從身側(cè)響起分冈,我...
    開封第一講書人閱讀 37,912評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎霸株,沒想到半個月后雕沉,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,355評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡去件,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,666評論 2 327
  • 正文 我和宋清朗相戀三年坡椒,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片尤溜。...
    茶點故事閱讀 38,809評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡倔叼,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出宫莱,到底是詐尸還是另有隱情丈攒,我是刑警寧澤,帶...
    沈念sama閱讀 34,504評論 4 334
  • 正文 年R本政府宣布授霸,位于F島的核電站巡验,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏碘耳。R本人自食惡果不足惜显设,卻給世界環(huán)境...
    茶點故事閱讀 40,150評論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望辛辨。 院中可真熱鬧捕捂,春花似錦、人聲如沸斗搞。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽僻焚。三九已至允悦,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間溅呢,已是汗流浹背澡屡。 一陣腳步聲響...
    開封第一講書人閱讀 32,121評論 1 267
  • 我被黑心中介騙來泰國打工猿挚, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人驶鹉。 一個月前我還...
    沈念sama閱讀 46,628評論 2 362
  • 正文 我出身青樓绩蜻,卻偏偏與公主長得像,于是被迫代替她去往敵國和親室埋。 傳聞我的和親對象是個殘疾皇子办绝,可洞房花燭夜當晚...
    茶點故事閱讀 43,724評論 2 351

推薦閱讀更多精彩內(nèi)容

  • 分析函數(shù),也稱為窗口函數(shù)姚淆,通常被認為僅對數(shù)據(jù)倉庫SQL有用孕蝉。使用分析函數(shù)的查詢,基于對數(shù)據(jù)行的分組來計算總量值腌逢。與...
    貓貓_tomluo閱讀 3,317評論 3 18
  • 2017/3/14 RDBMS:關系型數(shù)據(jù)庫管理系統(tǒng) 關系模型獨立于語言 SQL有幾種不同類型的語言:數(shù)據(jù)定義語言...
    ancherl閱讀 1,608評論 0 6
  • //Clojure入門教程: Clojure – Functional Programming for the J...
    葡萄喃喃囈語閱讀 3,636評論 0 7
  • 一降淮、字符轉(zhuǎn)換函數(shù) 1、ASCII() 返回字符表達式最左端字符的ASCII碼值搏讶。在ASCII()函數(shù)中佳鳖,純數(shù)字的字...
    輝格食品閱讀 1,634評論 0 3
  • 春節(jié)過后的小伙伴們有沒有“每逢佳節(jié)胖三斤”呢?作為一個來自東北的女漢子媒惕,我直到今年過年都沒有萌生過想要減肥的想法系吩,...
    形上之人閱讀 280評論 0 0