Excel & SQL | 資料運算 | 03
上表儲存了id(銷售人員ID)、 name (銷售人員姓名)、sales_a( a產品銷量) 、sales_b ( b產品銷量)、price_a ( a產品價格)和price_b ( b產品價格)六個欄位。我們把上表中的資料儲存在demo資料庫的chapter7表中。
算術運算
算術運算就是我們所熟悉的加減乘除運算,是比較常見的、簡單的運算。
無論是在Excel中還是SQL中,我們都可以直接對任意兩列或多列進行相應的運算。
在SQL中,我們要對某兩列或多列進行算術運算時,直接將相應的列名與相應的運算子連線即可。現在需要獲取每個銷售產品的所有銷量,即a產品銷量+b產品銷量;a產品與b產品的銷量差﹔每個銷售產品的總銷售額,即a產品銷量×a產品價格+b產品銷量×b產品價格;a產品與b產品的價格倍數;a產品銷量的2倍。具體實現程式碼如下∶
select id, (sales_a + sales_b) as all_sales, (sales_a - sales_b) as sales_a_b, (sales_a * price_a + sales_b * price_b) as gmv, (price_a / price_b) as price_a_b, (sales_a * 2) as 2_sales_a from chapter7;
在SQL中,加減乘除運算的優先順序和數學運算中的優先順序是一樣的,即先算乘除再算加減。
在算術運算中除了加減乘除,還有整除(div)和求餘(%和mod)兩種運算。
select 7 div 2 -- 結果為3
select 7 % 2 -- 結果為1
select 7 mod 2 -- 結果為1
這裡需要特別說明一下與null相關的運算,如果null參與加減乘除的算術運算,會得到什麼結果?
-- 結果都為null
select
1+null,
1-null,
1*null,
1/null;
執行上面的程式碼,結果為4個null,這是因為null與任何數進行運算,結果都是null,類似於0乘任何數都得0。
比較運算
前面的幾個運算子讀者應該比較瞭解,後四個運算子可能不太熟悉,這四個運算子只可以在SQL中使用,而不可以在Excel中使用。
在SQL中,要實現比較運算,需要先指明待比較的具體列,然後用比較運算子將不同的列連線起來。
select
id,sales_a,sales_b,
sales_a>sales_b as "大於",
sales_a<sales_b as "小於",
sales_a=sales_b as "等於",
sales_a!=sales_b as "不等於",
sales_a is null as "空值",
sales_a is not null as "非空值"
from chapter7;
在上面的程式碼中,我們對chapter7表中的sales_a列和sales_b列進行了各種比較運算,最後得到不同的結果,結果展示與Excel有所不同,在Excel中,如果比較結果是正確的,則返回TRUE,否則返回FALSE;而在SQL中如果比較結果是正確的,則返回1,否則返回0。執行上面的程式碼,具體執行結果如下表所示。
比較運算不僅可以被用於列與列之間的比較,也可以被用於前面講的條件篩選中,只需在where後面寫明具體的比較運算即可。比如,我們要獲取a產品的銷量為15~20範圍內的id列和sales_a列,可以通過如下程式碼實現∶
select id,sales_a from chapter7
where sales_a between 15 and 20;
如果要獲取a產品銷量大於15的id列和sales_a列,則可以通過如下程式碼實現∶
select id,sales_a from chapter7
where sales_a > 15;
邏輯運算
邏輯運算子主要用來連線多個條件,有and、or、not三種。
在SQL中實現邏輯運算與在Excel中類似,比如,我們要給每個id加兩個標籤∶雙優和單優。雙優的標準是sales_a列和sales_b列均大於15,單優的標準是隻要sales_a列和sales_b列中有一列大於15即可。具體實現程式碼如下:
select id,sales_a,sales_b,
((sales_a>15)and(sales_b>15)) as "雙優",
((sales_a>15)or(sales_b>15)) as "單優"
from chapter7;
執行上面的程式碼,滿足雙優標準的id會被加上1標籤,不滿足的被加上O標籤,單優也是如此,具體執行結果如下:
數學運算
數學運算就是與數學相關的一些運算,比如三角函式、對數運算等。
求絕對值
讀者應該都知道絕對值是什麼意思,求絕對值也是比較常見的一種運算。比如,我們想要求每個id對應的sales_a列和sales_b列的絕對差值,如果直接將這兩列做差,得到的結果肯定有正有負,但我們想要求的是絕對差值,所以我們需要對直接做差後的結果求絕對值。具體實現程式碼如下∶
select id,sales_a,sales_b,
(sales_a-sales_b) as "差值",
abs(sales_a-sales_b) as "絕對差值"
from chapter7;
求最小整數值
有時候,我們會按照某個規則生成某個數對應的整數值,比如,生成不小於x的最小整數值。在SQL中,我們使用的是ceil()函式,具體實現程式碼如下︰
select ceil(2.9);
求最大整數值
與最小整數值對應的是最大整數值,比如,生成不大於x的最大整數值。在SQL中,我們使用的是floor()函式,具體實現程式碼如下:
select floor(2.1);
隨機數生成
所謂隨機數,就是隨機產生的數,在SQL中,我們使用rand()函式來生成隨機數,rand()函式返回0~1範圍內的一個隨機浮點數。
直接執行下面的程式碼,就會得到0~1範圍內的一個隨機浮點數,每次執行下面的程式碼,會得到不同的結果∶
select rand();
小數點位數調整
我們平時會經常遇到各種小數,有的小數的小數點位數比較多,我們可以根據自己的需要進行調整。在SQL中,我們使用round()函式來對小數點位數進行調整。具體實現程式碼如下:
select round(1.1111,2);
如果我們要對一整列的小數點位數進行調整,只需要把1.1111換成對應的列名,把2換成想要保留的小數點位數即可。
如果我們要給每個id對應生成一個隨機數,則可以通過如下程式碼實現︰
select id,rand() as "隨機數" from chapter7;
隨機數生成還可以用在隨機抽樣中,比如,我們現在要從9個id中隨機抽取出3個,現在每個id都有一個隨機數,那麼我們只需要把id按照隨機數大小進行排序,最後前3行的資料就是我們隨機抽樣的結果。具體實現程式碼如下∶
select id,rand() as "隨機數" from chapter7
order by rand() limit 3;
正負判斷
有時候,我們要判斷兩個數的大小關係,可以對這兩個數進行做差,然後根據差值進行正負判斷,通過正負號就可以得到這兩個數的大小關係。在SQL中,我們使用sign()函式來進行正負判斷。比如,我們要判斷每個id對應
的sales_a列和sales_b列的差值的正負,可以通過如下程式碼實現∶
select id,sales_a,sales_b,
(sales_a-sales_b) as sales_a_b,
sign(sales_a-sales_b) as "正負"
from chapter7;
執行上面的程式碼,就會得到每個id對應的sales_a列和sales_b列的差值,以及差值的正負。如果差值為正,則結果為1;如果差值為負,則結果為-1;如果差值為0,則結果為0。
字串運算
字串運算也是比較常見的一種運算,字串運算的函式如下表所示。
字串替換
有時候,我們需要對一個長字串中的某個或某些字元進行替換。在SQL中,我們使用的是replace()函式,具體實現程式碼如下∶
select repalce("AaAaAa","A","a")
執行上面的程式碼,字串AaAaAa中的所有A被替換成a,最後得到的結果為aaaaaa。
如果我們要對某一列中的每個值進行替換,比如,把chapter7表中id列的字元E替換成e,具體實現程式碼如下:
select id,replace(id,"E","e") as repalce_id from chapter7;
字串合併
字串合併就是將多個字串合併成一個字串,在SQL中,我們使用的是concat()函式。
有的表中姓和名是分為兩列儲存的,所以我們需要將姓和名合併起來組成姓名,具體實現程式碼如下︰
select concat("Hello",", ","World!");
如果將一張表中的兩列或多列合併,則直接在concat()函式的括號中指明要合併的列名即可,比如,將chapter7表中的id列和name列合併,具體實現程式碼如下∶
select id,name,concat(id,name) as id_name from chapter7;
有時候,我們想用固定的符號合併不同的字串或列,這個時候就需要用到另一個函式concat_ws() :
select id,name,concat_ws("-",id,name) as id_name from chapter7;
-- concat()也有同款效果
select id,name,concat(id,"-",name) as id_name from chapter7;
字串擷取
字串擷取就是從一個字串中擷取我們需要的部分字元,主要有左、中、右三種擷取方式。
例如,現在有一個字串2019-10-01 12:30:21,如果我們只想要日期部分,那麼可以擷取這個字串的左邊部分﹔如果我們只想要時間部分,那麼可以擷取這個字串的右邊部分;如果我們只想要月份部分,那麼就可以擷取這個字串的中間部分。
擷取字串的左邊部分使用的是left()函式︰
select left("2019-10-01 12:39:21",10);
上面的程式碼擷取的是字串左邊的10個字元,即2019-10-01。
擷取字串的右邊部分使用的是right()函式:
select right("2019-10-01 12:39:21",8);
上面的程式碼擷取的是字串右邊的8個字元,即12:30:21。
擷取字串的中間部分使用的是substring()函式∶
select substring("2019-10-01 12:39:21",6,2);
上面的程式碼表示從字串的第6位開始擷取,擷取長度為2的字元,
即10。
如果要對某一列中的每個字串進行對應的擷取,只需要把上面的日期時間字串換成對應的列名即可。
字串匹配
字串匹配常用在where中,用於篩選滿足匹配規則的資料。在SQL中用於字串匹配的是like , like在英文中除了喜歡的意思,還有長得像的意思。
like有兩種匹配符號:%和_。
%用於匹配任意長度的字元,可以是0個,而_用於匹配單個長度的字元。
比如,我們要把姓張的同學全部提取出來,假設名字列為name,正常的名字都是先姓後名的,所以想要獲取所有姓張的同學,只需要保證第一個字元是張,後面可以是任意長度的字元,具體實現程式碼如下︰
select * from chapter7 where name like "張%";
再如,我們要把name列中包含凱的名字全部提取出來,只需要保證中間字元是凱,而前面和後面可以是若干個字元,具體實現程式碼如下:
select * from where name like "%凱%";
又如,我們要獲取name列中姓張的,且姓名為兩個字的同學,可以通過如下程式碼實現∶
select * from chapter7 where name like "張_";
上面舉的例子都是獲取name列中能匹配到的資料,如果我們想要獲取匹配不到的資料,只需要把like換成notlike即可。
比如,我們要獲取非張姓同學的資訊,可以通過如下程式碼實現︰
select * from chapter7 where name not like "張%";
字串計數
字串計數就是統計一個字串中包含多少個字元。在SQL中,我們使用的是char_length()函式∶
select char_length("sql");
select char_length("我愛學習");
分別執行上面的兩行程式碼,執行第一行程式碼得到的結果為3,執行第二行程式碼得到的結果為4。
char_length()函式類似的一個函式是length(),我們來看一下同樣的字串,使用length()函式會得到什麼結果∶
select length("sql");
select length("我愛學習");
分別執行上面的兩行程式碼,執行第一行程式碼得到的結果依舊為3,但是執行第二行程式碼得到的結果卻變成
了12。
我們可以看出,對於“sql”字串,char_length()和length()函式得到的結果是一樣的。而對於“我愛學習”字串,兩個函式得到的結果卻是不一樣的。這是因為char_length()函式是基於字元計數的,而length()函式是基於位元組計數的。
那什麼是字元,什麼又是位元組呢?字元是由位元組組成的。英文字母1個字元由1個位元組組成;中文1個字元在utf-8編碼環境下是由3個位元組組成的,在g bk編碼環境下是由2個位元組組成的。這裡是utf-8編碼環境,所以使用length()函式對於“我愛學習”字串進行計數得到的結果為12。
去除字串空格
有的字串中會因為各種原因出現空格,但是空格一般不是我們實際想要的資料,空格在一定程度上會導致資料結果出現偏差,比如,空格在字元計數的時候也被算作一個字元。所以我們需要對含有空格的字串進行去除空格操作,有去除字串左邊的空格、去除字串右邊的空格、去除字串兩邊的空格三種方式。具體實現程式碼如下∶
select
length(" abcdef ") as str_length,
length(ltrim(" abcdef ")) as lstr_length,
length(rtrim(" abcdef ")) as rstr_length,
length(trim(" abcdef ")) as tstr_length;
字串" abcdef”兩邊各含有1個空格,所以該字串總長度為8;使用ltrim()函式去掉左邊的空格以後字串長度變為7;使用rtrim()函式去掉右邊的空格以後字串長度也變為7;使trim()函式去掉兩邊的空格以後,字串長度變為6。
字串重複
字串重複是將同一個字串重複若干次後合併成一個字串,在SQL中使用的是repeat()函式。具體實現形式如下∶
select repeat(”sql“,3)
上面的程式碼表示將字串Sql重複3次以後合併成一個字串輸出,最後得到的結果為SqlSqlSql.
聚合運算
聚合運算是指將多個值聚合在一起進行某種運算,比如,求和、求平均值等。
count()計數
count(函式是用來對多個非缺失值進行計數的,常用於查看錶中某列有多少非空值,比如,我們要檢視chapter7表中的id列一共有多少非空值,就可以使用count()函式來實現,具體實現程式碼如下∶
selct count(id) from chapter7;
執行上面的程式碼,最後得到的結果為9。
如果我們想要檢視chapter7表中一共有多少行,那麼只需要把括號中的id換成*即可。讀者可能會想,查看錶中隨便一列有多少行不就知道這張表一共有多少行了嗎?多數情況下是可以的,但是如果這一列中有缺失值,那麼資料就會變少,因為count()函式是對非缺失值進行計數的。
select count(*) from chapter7;
我們在前面說過,缺失值主要有三種表現形式:null、空格、空值。null和空值是不算入計數的,而空格是算入計數的。
select count(" "); -- 空格算如計數
執行上面的程式碼,最後得到的結果為1,而執行下面的程式碼,最後得到的結果為0。
select count(null); -- 空值,null不算計數
有時候,表中某些列的值可能會重複,如果我們想得到刪除重複值後的計數,則可以和前面學過的重複值處理相結合,即count()函式和distinct相結合。比如,我們想檢視chapter7表中產品a一共有幾種銷量水平,即
對sales_a列刪除重複值後的計數,具體實現程式碼如下︰
-- 去除重複值計數
selct count(distinct sales_a) from chapter7;
sum()求和
sum()函式主要用於對錶中某列的所有值進行求和彙總,比如,我們要分別獲取chapter7表中產品a和產品b的總銷量,就可以使用sum()函式,具體實現程式碼如下∶
select sum(sales_a),sum(sales_b) from chapter7;
avg()求平均值
avg()函式主要用於對錶中某列的所有值進行求平均值運算,比如,我們要分別獲取chapter7表中產品a和產品b的平均銷量,就可以使用avg()函式,具體實現程式碼如下︰
select avg(sales_a),avg(sales_b) from chapter7;
max()求最大值
max()函式主要用於獲取表中某列的最大值,比如,我們要分別獲
取chapter7表中產品a和產品b的最高銷量,就可以使用max()函式,具體實現程式碼如下︰
select max(sales_a),max(sales_b) from chapter7;
min()求最小值
min()函式與max()函式相對應,用於獲取表中某列的最小值,比如,我們要分別獲取chapter7表中產品a和產品b的最低銷量,就可以使用min()函式,具體實現程式碼如下∶
select min(sales_a),min(sales_b) from chapter7;
求方差
方差用於反映一組資料的離散程度,即波動程度,方差越大,說明資料波動越厲害,方差的計算公式如下∶
在實際工作中,一組資料的總體是比較難獲得的,也就是說,我們看到的資料只是總體資料中的一部分,這個時候的數值個數就是N-1,而不是N。如果分母是N,則表示總體方差﹔如果分母是N-1,則表示樣本方差。
在SQL中,求總體方差,使用的是var_pop()函式﹔求樣本方差,使用的是var_samp()函式。具體實現程式碼如下∶
select var_pop(sales_a),var_samp(sales_a) from chapter7;
求標準差
標準差是方差的開方,也是用於反映資料的離散程度的,讀者可能會想,不是已經用方差來反映資料的離散程度了嗎,為什麼還要用標準差呢?那是因為方差雖然可以反映資料的離散程度,但是不具有實際業務意義。因為標準差與實際資料的單位是一致的,比如,中學生身高的標準差的單位是釐米,而方差是釐米的平方就比較難理解。因為標準差是方差的開方,方差有總體方差和樣本方差,所以標準差也有總體標準差和樣本標準差。
在SQL中,求總體標準差,使用的是std()函式;求樣本標準差,使用的是stddev_samp()函式。具體實現程式碼如下:
select
std(sales_a),
stddev_samp(sales_a)
from chapter7;
聚合函式之間的運算
上面講的聚合函式都是針對某一列進行聚合的,我們平常還有有針對多列進行聚合的需求,比如,我們要獲取產品a和產品b的總銷量,就需要先對sales_a列進行求和聚合運算,然後對sales_b列進行求和聚合運算,最後把聚合運算後的兩個值進行求和聚合運算,就是產品a和產品b的總銷量。
select
sum(sales_a) as a_group,
sum(sales_b) as b_group,
sum(sales_a)+sum(sales_b) as a_b
from chapter7;
需要注意的是,我們在對聚合運算後的sales_ a列和sales_ b列進行求和聚合運算時,使用的是sum(sales_ a) +sum(sales b) ,而非a_ group + b_group ,這是因為a_ group和b_ group是聚合運算後結果的別名,而非表中實際存在的列名,如果直接對二者進行求和聚合運算,程式則會報錯,提示列名不存在。
小結
算術運算
+ - * / div mod %
SQL可以直接在select中對兩列進行結果的計算(運算結果為一個新列)
null和任何數運算,結果都是null
select
id,
(sales_a + sales_b) as all_sales,
(sales_a - sales_b) as sales_a_b,
(sales_a * price_a + sales_b * price_b) as gmv,
(price_a / price_b) as price_a_b,
(sales_a * 2) as 2_sales_a
from chapter7;
比較運算
> < >= <= != <> = between is not null is null
列與列之間的比較
select
id,sales_a,sales_b,
sales_a>sales_b as "大於",
sales_a<sales_b as "小於",
sales_a=sales_b as "等於",
sales_a!=sales_b as "不等於",
sales_a is null as "空值",
sales_a is not null as "非空值"
from chapter7;
where中進行比較
select id,sales_a from chapter7
where sales_a between 15 and 20;
select id,sales_a from chapter7
where sales_a > 15;
邏輯運算
and or not
select id,sales_a,sales_b,
((sales_a>15)and(sales_b>15)) as "雙優",
((sales_a>15)or(sales_b>15)) as "單優"
from chapter7;
數學運算
求絕對值 abs()
select id,sales_a,sales_b,
(sales_a-sales_b) as "差值",
abs(sales_a-sales_b) as "絕對差值"
from chapter7;
求最小整數值 ceil()
select ceil(2.9);
求最大整數值 floor()
select floor(2.1);
隨機數生成 rand() 生成0~1之間的浮點隨機數
select rand();
小數點位數調整 round(浮點數,位數)
select rountd(1.1111,2)
select id,rand() as "隨機數" from chapter7;
select id,rand() as "隨機數" from chapter7 order by rand() limit 3; # 實現隨機選3個id
正負判斷
sign()
select id,sales_a,sales_b,
(sales_a-sales_b) as sales_a_b,
sign(sales_a-sales_b) as "正負"
from chapter7;
字串運算
字串替換 replace()
select repalce("AaAaAa","A","a")
select id,replace(id,"E","e") as repalce_id from chapter7;
字串合併 concat() 可以連線多個
select concat("Hello ","World!");
select id,name,concat(id,"-",name) as id_name from chapter7;
字串擷取
left() right() substring()
select left("2019-10-01 12:39:21",10);
select right("2019-10-01 12:39:21",8);
select substring("2019-10-01 12:39:21",6,2);
字串匹配
like not like
% _
select * from where name like "%凱%";
select * from chapter7 where name not like "張%";
字串計數
char_length() 基於字元計數
length() 基於位元組計數
去除字串空格
ltrim() rtrim() trim()
字串重複
select repeat(”sql“,3)
聚合運算
count()
selct count(distinct sales_a) from chapter7;
sum()
select sum(sales_a),sum(sales_b) from chapter7;
avg()
max()
min()
方差 var_pop() var_samp()
select var_pop(sales_a),var_samp(sales_a) from chapter7;
標準差 std() stddev_samp()
select
std(sales_a),
stddev_samp(sales_a)
from chapter7;
聚合函式之間的運算
select
sum(sales_a) as a_group,
sum(sales_b) as b_group,
sum(sales_a)+sum(sales_b) as a_b
from chapter7;