一. 數(shù)據(jù)庫(kù)與SQL語(yǔ)言簡(jiǎn)介簡(jiǎn)介
1.1 SQL:與數(shù)據(jù)庫(kù)交互的語(yǔ)言
SQL的全稱是結(jié)構(gòu)化查詢語(yǔ)言(Structured Query Language)。它屬于領(lǐng)域特定語(yǔ)言(Domain Specific Language)范疇,用于管理關(guān)系型數(shù)據(jù)庫(kù)管理系統(tǒng)。SQL基于關(guān)系代數(shù)理論建立的,包括數(shù)據(jù)定義語(yǔ)言和數(shù)據(jù)操縱語(yǔ)言。SQL的范圍包括數(shù)據(jù)插入,查詢,更新和刪除,數(shù)據(jù)庫(kù)模式創(chuàng)建和修改,以及數(shù)據(jù)訪問(wèn)控制。
一般來(lái)說(shuō),我們會(huì)將數(shù)據(jù)按照一定的范式進(jìn)行設(shè)計(jì),然后將其設(shè)計(jì)為具有相互聯(lián)系的表格,然后將其存放到數(shù)據(jù)庫(kù)中。能夠被這樣設(shè)計(jì)的數(shù)據(jù),我們稱為“結(jié)構(gòu)化數(shù)據(jù)”。還有一些數(shù)據(jù)是無(wú)法結(jié)構(gòu)化的,我們稱為非結(jié)構(gòu)化數(shù)據(jù),比如圖像,視頻和音頻等等。
除了后端開(kāi)發(fā)工程師要跟數(shù)據(jù)打交道,SQL也是數(shù)據(jù)分析師必備的基礎(chǔ)技能之一。SQL語(yǔ)義容易理解,分析師可以直接訪問(wèn)數(shù)據(jù),不需要拿個(gè)U盤拷來(lái)拷去。數(shù)據(jù)存放在數(shù)據(jù)庫(kù)中易于審核和復(fù)制。分析師可以通過(guò)復(fù)雜的查詢語(yǔ)句一次查詢和分析多個(gè)表,分析更復(fù)雜的問(wèn)題。企業(yè)使用SQL則可以進(jìn)行數(shù)據(jù)完整性檢查,可以快速訪問(wèn)數(shù)據(jù)。而且企業(yè)的多個(gè)團(tuán)隊(duì)可以并發(fā)使用,容易共享有價(jià)值的數(shù)據(jù)。有大量的工作要用到SQL技能,Top 10有:
- 商業(yè)分析師
- 高級(jí)軟件工程師
- 高級(jí)SQL Server數(shù)據(jù)庫(kù)管理員
- 質(zhì)量保障測(cè)試
- 網(wǎng)絡(luò)開(kāi)發(fā)者
- 系統(tǒng)管理員
- 軟件工程師
- SQL Server開(kāi)發(fā)者
- 質(zhì)量保障分析師
- 高級(jí)Oracle數(shù)據(jù)庫(kù)管理員
那么DB是如何存儲(chǔ)數(shù)據(jù)的呢?數(shù)據(jù)被存儲(chǔ)于類似Excel電子表格的表中,同一列中所有數(shù)據(jù)必須是同一數(shù)據(jù)類型,這樣有助于實(shí)現(xiàn)快速訪問(wèn)數(shù)據(jù)。業(yè)界流行的DB有:
- MySQL
- Oracle
- Microsoft SQL Server
- Postgres
- SQLite
1.2 重要概念:實(shí)體關(guān)系圖
實(shí)體關(guān)系圖(ERD)是查看數(shù)據(jù)庫(kù)中數(shù)據(jù)的常用方式。數(shù)據(jù)庫(kù)中的表并不是孤立的,相互之間會(huì)通過(guò)主鍵-外鍵建立聯(lián)系。這些圖可以幫助你可視化正在分析的數(shù)據(jù),它包括三個(gè)元素:
- 表的名稱
- 每個(gè)表中的列名
- 表配合工作的方式,即主鍵和外鍵之間的對(duì)應(yīng)關(guān)系
在開(kāi)始查詢數(shù)據(jù)前通過(guò)ERD圖理解數(shù)據(jù)庫(kù)表之間的關(guān)系,有助于使用SQL語(yǔ)言進(jìn)行各種復(fù)雜的查詢,解決各種復(fù)雜的問(wèn)題。上圖為課程中用到的Parch & Posey數(shù)據(jù)庫(kù)。這是一家銷售紙張的虛構(gòu)公司,銷售普通紙,海報(bào)紙和銅版紙。他們有些客戶,都是通過(guò)在線廣告招徠的:
- web_events:在線廣告渠道數(shù)據(jù)
- accounts:客戶數(shù)據(jù)
- orders:訂單數(shù)據(jù)
- sales_reps:銷售代表數(shù)據(jù)
- region:銷售區(qū)域數(shù)據(jù)
ERD使用Crow's Foot Notation來(lái)表示表之間的關(guān)系。PK的意思是Primary Key,即主鍵,F(xiàn)K表示Foreign Key,即外鍵。某個(gè)表的主鍵是另一個(gè)表的外鍵,比如accounts表中的主鍵id就是orders表中的外鍵account_id,意味著我可以通過(guò)這個(gè)關(guān)聯(lián)查找某個(gè)客戶的訂單數(shù)據(jù)。復(fù)雜的數(shù)據(jù)查詢就是這么建立起來(lái)的,下面我們就以這個(gè)數(shù)據(jù)庫(kù)為例,分三個(gè)階段,由淺入深入門SQL語(yǔ)言。
二. SQL語(yǔ)言入門
2.1 基本的SQL查詢
SELECT語(yǔ)句是SQL中的查詢語(yǔ)句。其基本語(yǔ)法格式為:
SELECT 逗號(hào)分隔的列名列表 FROM 表名;
例如,從訂單表(orders)中查詢id和發(fā)生時(shí)間(ocurred_at),則可以這么寫(xiě):
SELECT id, occurred_at FROM orders;
如果想要查詢所有的列,也可以不用顯式地列出字段集的名字,而是用*號(hào)代替。FROM子句用于指明數(shù)據(jù)的來(lái)源,即表名。
關(guān)于SQL語(yǔ)句的格式,這里還要特別說(shuō)明一下。對(duì)于SQL命令(SELECT,F(xiàn)ROM),我們選擇大寫(xiě),而其他內(nèi)容比如列名選擇小寫(xiě)。SQL本身并不區(qū)分大小寫(xiě),然而適當(dāng)?shù)挠么笮?xiě)來(lái)區(qū)分命令和其他內(nèi)容,能夠增加SQL語(yǔ)句的可讀性。另外表和變量名的命名不要有空格,用下劃線來(lái)代替。每個(gè)SQL語(yǔ)句寫(xiě)完之后要寫(xiě)一個(gè)分號(hào)。關(guān)鍵字的順序也很重要,有時(shí)候?qū)戝e(cuò)順序,SQL在執(zhí)行的時(shí)候就會(huì)報(bào)錯(cuò)。
這里推薦一種以SQL命令開(kāi)頭的寫(xiě)法。這種寫(xiě)法以多行完成一個(gè)SQL語(yǔ)句。能夠進(jìn)一步提高可讀性:
SELECT account_id
FROM orders;
對(duì)于做后端開(kāi)發(fā)的同學(xué)而言,還有一種最佳實(shí)踐,是給字段加上反引號(hào),這么做是因?yàn)镾QL是不區(qū)分大小寫(xiě)的,萬(wàn)一不小心列名與SQL的某個(gè)關(guān)鍵字重復(fù)了,使用反引號(hào)能指明這是列名,而不是關(guān)鍵字,能夠避免SQL執(zhí)行錯(cuò)誤:
SELECT `id`, `account_id`
FROM orders;
查詢語(yǔ)句中還有其他一些比較常見(jiàn)的子句,這些子句是可選的,但這些可選的子句很多時(shí)候能夠make your life much better。下面我們就來(lái)一一介紹一下。
2.1.1 LIMIT
LIMIT子句用于查詢前幾行。有時(shí)候我們查詢某個(gè)表格只是為了觀察下它有哪些列,并不需要全部查詢出來(lái),特別是表格數(shù)據(jù)量很大的時(shí)候,如果像上面那樣編寫(xiě)會(huì)輸出全部的數(shù)據(jù),滾屏輸出要花很長(zhǎng)時(shí)間,使用LIMIT子句,我們可以只查看前5行:
SELECT id, account_id
FROM orders
LIMIT 5;
2.1.2 ORDER BY
ORDER BY子句可使我們按任意順序?qū)Σ樵兘Y(jié)果進(jìn)行排序。默認(rèn)的排序是升序,如果需要降序排列,則可以在之后添加DESC,比如查看最新的10個(gè)訂單數(shù)據(jù),可以這么寫(xiě):
SELECT *
FROM orders
ORDER BY occurred_at DESC
LIMIT 10;
ORDER BY子句可以實(shí)現(xiàn)多列排序。比如想要將查詢結(jié)果先按照賬戶進(jìn)行排序,然后對(duì)于每個(gè)賬戶的訂單數(shù)據(jù)從大到小排序。這樣就能迅速看出每個(gè)賬戶最大的一筆訂單了:
SELECT account_id, total_amt_usd
FROM orders
ORDER BY account_id, total_amt_usd DESC;
注意這里排列的順序很重要,這里的DESC只對(duì)total_amt_usd起作用。
2.1.3 WHERE
用于在查詢時(shí)指定結(jié)果集必須滿足的條件。相當(dāng)于使用篩選功能。WHERE 語(yǔ)句中使用的常用符號(hào)包括:
- >(大于)
- <(小于)
- >=(大于或等于)
- <=(小于或等于)
- =(等于)
- !=(不等于)
舉例,查詢某個(gè)客戶(ID=4251)最新的10個(gè)訂單數(shù)據(jù):
SELECT *
FROM orders
WHERE account_id = 4251
ORDER BY occurred_at
LIMIT 10;
比較運(yùn)算符可以與非數(shù)值數(shù)據(jù)字段一起使用。我們可以使用=
和!=
,分別表示“等于”和“不等于”,文本數(shù)據(jù)注意使用單引號(hào)。舉例,查詢名稱為United Technologies的客戶資料信息:
SELECT *
FROM accounts
WHERE name = 'United Technologies';
2.1.4 算術(shù)運(yùn)算符
還可以通過(guò)算數(shù)運(yùn)算符,將現(xiàn)有的列進(jìn)行組合,得到派生列。常見(jiàn)運(yùn)算包括:
- *(乘法)
- +(加法)
- -(減法)
- /(除法)
與在做數(shù)據(jù)分析時(shí)根據(jù)已有特征創(chuàng)建新特征是一個(gè)道理。使用派生列的時(shí)候需要對(duì)它進(jìn)行命名,可以使用AS命令。舉例,從訂單數(shù)據(jù)中查詢賬戶名,時(shí)間,標(biāo)準(zhǔn)紙購(gòu)買數(shù)量,銅版紙購(gòu)買數(shù)量,海報(bào)紙購(gòu)買數(shù)量和非標(biāo)準(zhǔn)類紙購(gòu)買數(shù)量(即銅版紙和海報(bào)紙數(shù)量之和):
SELECT account_id,
occurred_at,
standard_qty,
gloss_qty,
poster_qty,
gloss_qty + poster_qty AS nonstandard_qty
FROM orders;
2.1.5 邏輯運(yùn)算符
邏輯運(yùn)算符包括:
- LIKE:模糊查找和模糊匹配
- IN :用于多個(gè)條件的情況,相當(dāng)于從某個(gè)集合中選取
- NOT:這與 IN 和 LIKE 一起使用,用于選擇 NOT LIKE 或 NOT IN 某個(gè)條件的所有行
- AND & BETWEEN:可用于組合所有組合條件必須為真的運(yùn)算
- OR:可用于組合至少一個(gè)組合條件必須為真的運(yùn)算。
LIKE運(yùn)算符用于處理文本,我們經(jīng)常在WHERE子句中使用。LIKE運(yùn)算符經(jīng)常與%
一起使用。后者相當(dāng)于通配符,表示任意數(shù)量的任意字符。舉例:查詢引用頁(yè)URL中包含“google”的數(shù)據(jù):
SELECT *
FROM web_events
WHERE referrer_url LIKE '%google%';
IN運(yùn)算符用于從若干可能的值集合中篩選數(shù)據(jù)。比如如果我們只想看沃爾瑪和蘋(píng)果公司的賬戶信息,可以這么查詢:
SELECT *
FROM accounts
WHERE name IN ('Walmart', 'Apple');
NOT 可以與之前介紹的兩個(gè)運(yùn)算符 IN 和 LIKE 一起運(yùn)算??梢圆檎业剿胁环咸囟l件的行。舉例,查找除321500和321570以外的其他銷售代表:
SELECT sales_rep_id, name
FROM accounts
WHERE sales_rep_id NOT IN (321500, 321570)
ORDER BY sales_rep_id;
或者查找引用頁(yè)URL中不包含“google”的數(shù)據(jù):
SELECT *
FROM web_events
WHERE referer_url NOT LIKE '%google%';
AND運(yùn)算符表達(dá)邏輯與運(yùn)算,用于 WHERE 語(yǔ)句中,可一次性同時(shí)滿足多個(gè)邏輯子句。使用 AND 語(yǔ)句進(jìn)行連接時(shí)要指定感興趣的列。比如查詢2016年4月1日后,2016年10月1日前的訂單數(shù)據(jù):
SELECT *
FROM orders
WHERE occurred_at >= '2016-04-01' AND occurred_at <= '2016-10-01'
ORDER BY occurred_at DESC;
可以用BETWEEN運(yùn)算符改寫(xiě),請(qǐng)參考:
SELECT *
FROM orders
WHERE occurred_at BETWEEN '2016-04-01' AND '2016-10-01'
ORDER BY occurred_at DESC;
與 AND 運(yùn)算符類似,OR 運(yùn)算符可以組合多個(gè)語(yǔ)句。但是與AND不同,OR組合的多個(gè)條件中只至少有一個(gè)滿足即可。比如從訂單數(shù)據(jù)中查詢不是三種紙張都采購(gòu)的訂單:
SELECT account_id,
occurred_at,
standard_qty,
gloss_qty,
poster_qty
FROM orders
WHERE standard_qty = 0 OR gloss_qty = 0 OR poster_qty = 0;
SQL的基本用法就介紹到這里。但SQL的強(qiáng)大遠(yuǎn)不止如此。對(duì)于比較復(fù)雜的分析問(wèn)題而言,我們需要進(jìn)行更復(fù)雜的連表查詢才能回答。因此下面我們來(lái)重點(diǎn)研究下如何使用SQL進(jìn)行表連接復(fù)雜查詢。
2.2 SQL的JOIN運(yùn)算
SQL的強(qiáng)大之處在于它可以同時(shí)處理多張表格。關(guān)系型數(shù)據(jù)庫(kù)中的表都是相互聯(lián)系的,我們需要通過(guò)表連接運(yùn)算從多個(gè)表格中整合信息。
為什么關(guān)系型數(shù)據(jù)庫(kù)要分表,有多種原因。其中一個(gè)重要原因是我們可以把表中每一行的數(shù)據(jù)看作一個(gè)對(duì)象,而對(duì)象是有不同類型的,比如訂單和賬戶就是不同類型的數(shù)據(jù)(對(duì)象),分開(kāi)的話更容易組織。另外,多表格結(jié)構(gòu)可以保證更快的查詢。創(chuàng)建數(shù)據(jù)庫(kù)并進(jìn)行表格設(shè)計(jì)時(shí),一定要思考如何存儲(chǔ)數(shù)據(jù)。設(shè)計(jì)新的數(shù)據(jù)庫(kù)要理解數(shù)據(jù)庫(kù)的規(guī)范化:
- 表格存儲(chǔ)了邏輯分組的數(shù)據(jù)嗎?
- 我能在一個(gè)位置進(jìn)行更改,而不是在多個(gè)表格中對(duì)同一信息作出更改嗎?
- 我能快速高效地訪問(wèn)和操縱數(shù)據(jù)嗎?
這篇文章詳細(xì)介紹了與數(shù)據(jù)庫(kù)規(guī)范化設(shè)計(jì)有關(guān)的知識(shí)。
2.2.1 INNER JOIN
我們先看看被稱為INNER JOIN的內(nèi)連接運(yùn)算。后面我們還會(huì)接觸其他類型的連接運(yùn)算。無(wú)論什么類型的連接運(yùn)算,它們的目標(biāo)都是一次性從多個(gè)表格中獲取數(shù)據(jù),所以你可以把JOIN看作是第二條FROM子句。比如我們要連表查詢,匹配的條件是accounts表中的id字段和orders表中的account_id字段:
SELECT orders.*, accounts.*
FROM orders
JOIN accounts
ON orders.account_id = accounts.id;
那么這個(gè)查詢的目的是什么呢?它的目的是同時(shí)從orders和accounts表中查詢數(shù)據(jù),既然是兩個(gè)表的數(shù)據(jù)共同構(gòu)成的結(jié)果集,那每條結(jié)果總要以一定的條件拼接在一起,條件就是:orders表中的account_id與accounts表中的id相等。
如果重新翻看一下前面提到的實(shí)體關(guān)系圖,我們就能發(fā)現(xiàn),accounts表中的id是主鍵,它對(duì)應(yīng)orders表中的account_id列,是外鍵。主鍵是特定表格的唯一列,外鍵是另一個(gè)表格中的主鍵。如果想要連接更多的表格,同樣可以采取相同的邏輯:
FROM web_events
JOIN accounts
ON web_events.account_id = accounts.id
JOIN orders
ON accounts.id = orders.account_id
連表查詢時(shí)一個(gè)突出的問(wèn)題是經(jīng)常要打出多個(gè)表格的全名,寫(xiě)出來(lái)的語(yǔ)句往往又很復(fù)雜,這樣會(huì)很累。我們完全可以在寫(xiě)連表查詢語(yǔ)句時(shí)使用表的別名縮寫(xiě),讓我們的工作輕松些:
SELECT o.*, a.*
FROM orders o
JOIN accounts a
ON o.account_id = a.id;
2.2.2 其他類型的JOIN
除了INNER JOIN,我們還有OUTER JOIN運(yùn)算。OUTER JOIN又分為左連接LEFT JOIN和右連接RIGHT JOIN。要理解它們的區(qū)別,需要從集合論的角度去理解,別忘了,我們的關(guān)系型數(shù)據(jù)庫(kù)是基于關(guān)系代數(shù)理論構(gòu)建的。INNER JOIN運(yùn)算實(shí)際上只會(huì)返回兩個(gè)表格中都有的行,也就是求交集運(yùn)算,如下圖:
如果我們要查詢的數(shù)據(jù)并未同時(shí)存在與多張表格中,就要用到外連接運(yùn)算。要理解其他類型的連接運(yùn)算,首先要學(xué)會(huì)區(qū)分左表和右表的概念:
SELECT
FROM left table
LEFT JOIN right table
直接看韋恩圖就能明白左連接和右連接的區(qū)別了,其實(shí)就是包含的元素比內(nèi)連接多了一些:
其實(shí)還有一種被稱為FULL OUTER JOIN的:
數(shù)據(jù)分析中常用的連接運(yùn)算就這些。如果你想更深入理解SQL提供的豐富的連接運(yùn)算,那么首先你可以學(xué)習(xí)一下SQL的各種JOIN,然后你可能要理解為什么不能有多對(duì)多的關(guān)系,以及完全外連接和完全外連接罕見(jiàn)用法,以及一些比較特殊的連接運(yùn)算,比如CrossJOIN和SelfJOIN。另外,SQL JOIN可視化也是不錯(cuò)的資源。
2.3 SQL的聚合運(yùn)算
學(xué)會(huì)了連表查詢,下面我們可以真正開(kāi)始用SQL語(yǔ)句來(lái)進(jìn)行基于聚合運(yùn)算的數(shù)據(jù)分析了。熟練掌握了這些技能,我們以后就能做知識(shí)遷移。當(dāng)你在使用Python的pandas庫(kù)進(jìn)行數(shù)據(jù)分析,或者使用Spark進(jìn)行分布式數(shù)據(jù)分析時(shí),你會(huì)遇到一些和這里介紹的知識(shí)似曾相識(shí)的概念。你會(huì)觸類旁通很快掌握。
2.3.1 計(jì)數(shù)運(yùn)算
COUNT運(yùn)算可以計(jì)算某列一共有多少行。但我們要注意SQL中的一種數(shù)據(jù)類型:NULL,它表示沒(méi)有數(shù)據(jù),它們經(jīng)常在聚合函數(shù)中被忽略,這個(gè)一定要注意。比如COUNT運(yùn)算就只計(jì)數(shù)不為NULL的項(xiàng)。舉例,計(jì)算accounts表格中有多少行數(shù)據(jù):
SELECT COUNT(*)
FROM accounts;
2.3.2 求和運(yùn)算
與 COUNT 不同,我們只能針對(duì)數(shù)值列使用 SUM,因?yàn)榍蠛捅旧碇粚?duì)數(shù)值才有意義。注意SUM會(huì)將NULL值當(dāng)作0處理。舉例,統(tǒng)計(jì)每種紙張類型的總銷量:
SELECT SUM(standard_qty) AS standard,
SUM(gloss_qty) AS gloss,
SUM(poster_qty) AS poster
FROM orders;
2.3.3 最值運(yùn)算
最值運(yùn)算有兩種,MIN表示求最小值,MAX表示求最大值。比如我們要統(tǒng)計(jì)訂單中每種紙張類型的最大購(gòu)買量和最小購(gòu)買量:
SELECT MIN(standard_qty) AS standard_min,
MIN(gloss_qty) AS gloss_min,
MIN(poster_qty) AS poster_min,
MAX(standard_qty) AS standard_max,
MAX(gloss_qty) AS gloss_max,
MAX(poster_qty) AS poster_max
FROM orders;
MIN和MAX與COUNT相似,它們都可以用在非數(shù)字列上。MIN將返回最小的數(shù)字、最早的日期或按字母表排序的最之前的非數(shù)字值,具體取決于列類型。MAX則正好相反,返回的是最大的數(shù)字、最近的日期,或與“Z”最接近(按字母表順序排列)的非數(shù)字值。
2.3.4 均值運(yùn)算
AVG運(yùn)算返回的是數(shù)據(jù)的平均值,即某數(shù)值列之和除以數(shù)量。該聚合函數(shù)會(huì)忽略分子和分母中的NULL值。舉例,統(tǒng)計(jì)各種類型紙張的平均銷量:
SELECT AVG(standard_qty) AS standard_avg,
AVG(gloss_qty) AS gloss_avg,
AVG(poster_qty) AS poster_avg
FROM orders;
2.3.5 分組聚合
上述聚合函數(shù)真正厲害的地方在于它們可以和分組運(yùn)算結(jié)合起來(lái)。把數(shù)據(jù)按照某些條件進(jìn)行分組然后進(jìn)行組內(nèi)聚合運(yùn)算。GROUP BY運(yùn)算可以創(chuàng)建在聚合運(yùn)算時(shí)相互獨(dú)立的分組。要注意,SELECT語(yǔ)句中沒(méi)有被聚合的字段都應(yīng)該顯式出現(xiàn)在GROUP BY中,比如我們要按照account_id來(lái)分組統(tǒng)計(jì)不同類型紙張的購(gòu)買量:
SELECT account_id,
SUM(standard_qty) AS standard_sum,
SUM(gloss_qty) AS gloss_sum,
SUM(poster_qty) AS poster_sum,
FROM orders
GROUP BY account_id
ORDER BY account_id;
我們還可以用多列分組的方式,把數(shù)據(jù)切割稱更細(xì)粒度的數(shù)據(jù)塊。舉例:統(tǒng)計(jì)每個(gè)賬戶ID的每個(gè)渠道事件:
SELECT account_id,
channel,
COUNT(id) AS events
FROM web_events
GROUP BY account_id, channel
ORDER BY account_id, channel;
2.3.6 唯一值
DISTINCT可用于僅返回特定列的唯一值的函數(shù)。比如,檢查每個(gè)客戶是不是對(duì)應(yīng)了多個(gè)渠道:
SELECT DISTINCT account_id,
channel
FROM web_events
ORDER BY account_id;
2.3.7 聚合列的條件判斷函數(shù)
HAVING之于GROUP BY,就好比WHERE之于FROM。對(duì)于聚合創(chuàng)建的查詢中的元素執(zhí)行WHERE條件時(shí),就要用到HAVING。HAVING總是緊隨GROUP BY語(yǔ)句之后,總是與聚合函數(shù)一起使用,是聚合字段條件篩選。舉個(gè)例子,查詢所有銷售額超過(guò)25萬(wàn)的賬戶:
SELECT account_id, SUM(total_amt_usd) AS total_amt_usd
FROM orders
GROUP BY 1
HAVING SUM(total_amt_usd) > 250000
ORDER BY 2 DESC;
2.3.8 日期運(yùn)算
在 SQL 中,直接按照日期列分組聚合通常不實(shí)用,因?yàn)楹芏嗲闆r下這些列可能包含小到一秒的交易數(shù)據(jù)。按照如此細(xì)粒度的級(jí)別保存信息,直接分組聚合就沒(méi)有意義了。有很多 SQL 內(nèi)置函數(shù)可以幫助我們改善日期處理體驗(yàn)。在數(shù)據(jù)庫(kù)中,日期一般是按照YYYY-MM-DD HH:MM:SS的格式存儲(chǔ)的。
DATE_TRUNC函數(shù)能夠?qū)⑷掌诮厝〉饺掌跁r(shí)間列的特定部分。常見(jiàn)的截取依據(jù)包括日期
、月份
和 年份
。這篇文章 詳細(xì)介紹了此函數(shù)的強(qiáng)大功能。
DATE_PART 可以用來(lái)獲取日期的特定部分,但是注意獲取 month
或 dow
意味著無(wú)法讓年份按順序排列。而是按照特定的部分分組,無(wú)論它們屬于哪個(gè)年份。要了解其他日期函數(shù),請(qǐng)參閱這篇介紹文檔。
舉例,按天匯總標(biāo)準(zhǔn)紙張的數(shù)量:
SELECT DATE_TRUNC('day', occurred_at) AS day,
SUM(standard_qty) AS standard_qty_sum
FROM orders
GROUP BY DATE_TRUNC('day', occurred_at)
ORDER BY DATE_TRUNC('day', occurred_at);
2.3.9 條件運(yùn)算
SQL中的CASE語(yǔ)句就相當(dāng)于其他語(yǔ)言中的IF語(yǔ)句,按照WHEN-THEN-ELSE-END的格式書(shū)寫(xiě)。舉例,根據(jù)訂單大小給訂單打分組標(biāo)簽:
SELECT account_id,
occurred_at,
total,
CASE WHEN total > 500 THEN 'Over 500'
WHEN total > 300 THEN '301 - 500'
WHEN total > 100 THEN '101 - 300'
ELSE '100 or under' END AS total_group
FROM orders;
在CASE的基礎(chǔ)上進(jìn)行聚合舉例,將訂單分為總銷售額超過(guò)500和500及以下的分組:
SELECT CASE WHEN total > 500 THEN 'Over 500'
ELSE '500 or under' END AS total_group,
COUNT(*) AS order_count
FROM orders
GROUP BY 1;
2.4 SQL子查詢和臨時(shí)表
SQL還有一種更高級(jí)的應(yīng)用,是從已得到的查詢結(jié)果中再進(jìn)行查詢,被稱為子查詢。子查詢的使用比較靈活,可以用在各種子句中:表名,列名,單個(gè)值,條件邏輯等等。
編寫(xiě)子查詢時(shí),要學(xué)會(huì)把復(fù)雜的步驟拆分成幾個(gè)不同的部分。比如要計(jì)算出每個(gè)渠道每天的平均事件數(shù),我們要把這個(gè)任務(wù)拆分成兩個(gè)部分:首先查詢得到第一個(gè)表格,提供每個(gè)渠道每天的事件次數(shù);然后再在這個(gè)表格上進(jìn)行第二次查詢,對(duì)這些值求平均,先寫(xiě)出第一個(gè)查詢
SELECT DATE_TRUNC('day', occurred_at) AS day,
channel,
COUNT(*) AS event_count
FROM web_events
GROUP BY 1, 2
可以直接運(yùn)行一下上述查詢,看下得到的中間結(jié)果是不是自己想要的。然后在這個(gè)的基礎(chǔ)上寫(xiě)出第二個(gè)查詢:
SELECT channel,
AVG(event_count) AS avg_event_count
FROM
(SELECT DATE_TRUNC('day', occurred_at) AS day,
channel,
COUNT(*) AS event_count
FROM web_events
GROUP BY 1, 2
) sub
GROUP BY 1
ORDER BY 2 DESC;
子查詢實(shí)際上可以在很多地方用到。它可以用在任何地方,甚至條件邏輯。比如,想要查詢與首個(gè)訂單相同月份的訂單,我們來(lái)一步一步的計(jì)算,首先編寫(xiě)一個(gè)帶MIN函數(shù)的子查詢得到首個(gè)訂單:
SELECT MIN(occurred_at) AS min
FROM orders;
運(yùn)行一下沒(méi)問(wèn)題,用DATE_TRUNC函數(shù)得到月份:
SELECT DATE_TRUNC('month', MIN(occurred_at)) AS min_month
FROM orders;
OK,接下來(lái)查詢與首個(gè)訂單月份相同的訂單數(shù)據(jù):
SELECT *
FROM orders
WHERE DATE_TRUNC('month', occurred_at) =
(SELECT DATE_TRUNC('month', MIN(occurred_at)) AS min_month
FROM orders)
ORDER BY occurred_at;
2.4.1 公用表表達(dá)式
子查詢的一個(gè)問(wèn)題是查詢語(yǔ)句看起來(lái)復(fù)雜又冗長(zhǎng),難以理解。公用表表達(dá)式可以將查詢分解成單獨(dú)的各個(gè)部分,使查詢邏輯易于閱讀。比如上面的這個(gè)例子:
SELECT channel,
AVG(event_count) AS avg_event_count
FROM
(SELECT DATE_TRUNC('day', occurred_at) AS day,
channel,
COUNT(*) AS event_count
FROM web_events
GROUP BY 1, 2
) sub
GROUP BY 1
ORDER BY 2 DESC;
可以用WITH語(yǔ)句改寫(xiě)為:
WITH events AS (SELECT DATE_TRUNC('day', occurred_at) AS day,
channel,
COUNT(*) AS event_count
FROM web_events
GROUP BY 1, 2)
SELECT channel,
AVG(event_count) AS avg_event_count
FROM events
GROUP BY 1
ORDER BY 2 DESC;
相比之下要清晰很多。如果我們要?jiǎng)?chuàng)建第二個(gè)表格來(lái)從中獲取數(shù)據(jù)。我們可以按照以下方式來(lái)創(chuàng)建額外的表格并從中獲取數(shù)據(jù):
WITH table1 AS (
SELECT *
FROM web_events),
table2 AS (
SELECT *
FROM accounts)
SELECT *
FROM table1
JOIN table2
ON table1.account_id = table2.id;
然后,你可以按照相同的方式使用WITH語(yǔ)句添加越來(lái)越多的表格。WITH語(yǔ)句為運(yùn)行時(shí)間長(zhǎng)的查詢創(chuàng)建臨時(shí)表,然后在臨時(shí)表的基礎(chǔ)上做其他查詢。這么做的優(yōu)勢(shì)在于對(duì)于計(jì)算代價(jià)高的查詢,只做一次,并且使冗長(zhǎng)難以閱讀的子查詢變得容易理解。