MySQL入门系列:查询简介(四)之类型转换和分组查询
前文回顧
之前我們介紹了一些簡單查詢的方式以及MySQL中表達式和函數的簡單使用,本篇文章繼續介紹如何使用MySQL進行查詢。
類型轉換
類型轉換的場景
只要某個值的類型與上下文要求的類型不符,MySQL就會根據上下文環境中需要的類型對該值進行類型轉換,具體可能發生轉換的場景有下邊這幾種:
把操作數類型轉換為適合操作符計算的相應類型。
比方說對于加法操作符+來說,它要求兩個操作數都必須是數字才能進行計算,所以如果某個操作數不是數字的話,會將其強制轉換為數字,比方說下邊這個例子:
1 + 2 → 3 '1' + 2 → 3 '1' + '2' → 3 復制代碼雖然'1'、'2'都是字符串,但是如果它們作為加法操作符+的操作數的話,都會被強制轉換為數字,所以上邊幾個表達式其實都會被當作1 + 2去處理的,這些表達式被當作查詢對象時的效果如下:
mysql> SELECT 1 + 2, '1' + 2, '1' + '2'; +-------+---------+-----------+ | 1 + 2 | '1' + 2 | '1' + '2' | +-------+---------+-----------+ | 3 | 3 | 3 | +-------+---------+-----------+ 1 row in set (0.00 sec)mysql> 復制代碼將函數參數轉換為該函數期望的類型。
我們拿用于拼接字符串的CONCAT函數舉例,這個函數的字符串類型的值作為參數,如果我們在調用這個函數的時候,傳入了別的類型的值作為參數,MySQL會自動把這些值的類型轉換為字符串類型的:
CONCAT('1', '2') → '12' CONCAT('1', 2) → '12' CONCAT(1, 2) → '12' 復制代碼雖然1、2都是數字,但是如果它們作為CONCAT函數的參數的話,都會被強制轉換為字符串,所以上邊幾個表達式其實都會被當作CONCAT('1', '2)去處理的,這些表達式被當作查詢對象時的效果如下:
mysql> SELECT CONCAT('1', '2'), CONCAT('1', 2), CONCAT(1, 2); +------------------+----------------+--------------+ | CONCAT('1', '2') | CONCAT('1', 2) | CONCAT(1, 2) | +------------------+----------------+--------------+ | 12 | 12 | 12 | +------------------+----------------+--------------+ 1 row in set (0.00 sec)mysql> 復制代碼存儲數據時,把某個值轉換為某個列需要的類型。
我們先新建一個簡單的表t:
CREATE TABLE t (i1 TINYINT,i2 TINYINT,s VARCHAR(100) ); 復制代碼這個表有三個列,列i1和i2是用來存儲整數的,列s是用來存儲字符串的,如果我們在存儲數據的時候填入的不是期望的類型,MySQL會為我們自動轉型:
mysql> INSERT INTO t(i1, i2, s) VALUES('100', '100', 200); Query OK, 1 row affected (0.01 sec)mysql> 復制代碼我們為列i1和i2填入的值是一個字符串值:'100',列s填入的值是一個整數值:200,雖然說類型都不對,但是由于自動轉型的存在,在插入數據的時候字符串'100'會被轉型為整數100,整數200會被轉型成字符串'200',所以最后插入成功。
類型轉換的注意事項
MySQL會盡量把值轉換為表達式中需要的類型,而不是產生錯誤。
按理說'23sfd'這個字符串無法轉換為數字,但是MySQL規定只要字符串的開頭部分包含數字,那么就把這個字符串轉換為頭部的數字,如果開頭并沒有包含數字,那么將被轉換成0,比方說這樣:
'23sfd' → 23 '2018-03-05' → 2018 '11:30:32' → 11 'sfd' → 0 復制代碼看個例子:
mysql> SELECT '23sfd' + 0, 'sfd' + 0; +-------------+-----------+ | '23sfd' + 0 | 'sfd' + 0 | +-------------+-----------+ | 23 | 0 | +-------------+-----------+ 1 row in set, 2 warnings (0.00 sec)mysql> 復制代碼不過需要注意的是,這種強制轉換不能用于存儲數據中,比方說這樣:
mysql> INSERT INTO t(i1, i2, s) VALUES('sfd', 'sfd', 'aaa'); ERROR 1366 (HY000): Incorrect integer value: 'sfd' for column 'i1' at row 1 mysql> 復制代碼由于i1和i2列需要整數,而填入的字符串'sfd'并不能順利的轉為整數,所以報錯了。
在運算時會自動提升操作數的類型。
我們知道不同數據類型能表示的數值范圍是不一樣的,在小的數據類型經過算數計算后得出的結果可能大于該可以表示的范圍。比方說t表中有一條記錄如下:
mysql> SELECT * FROM t; +------+------+------+ | i1 | i2 | s | +------+------+------+ | 100 | 100 | 200 | +------+------+------+ 1 row in set (0.00 sec)mysql> 復制代碼其中的i1列和i2列的類型都是TINYINT,而TINYINT能表示的最大正整數是127,如果我們把i1列的值和i2列的值相加會發生什么呢?請看:
mysql> SELECT i1 + i2 FROM t; +---------+ | i1 + i2 | +---------+ | 200 | +---------+ 1 row in set (0.00 sec)mysql> 復制代碼可以看到最后的結果是200,可是它已經超過TINYINT類型的表示范圍了。其實在運算的過程中,MySQL自動將整數類型的操作數提升到了BIGINT,這樣就不會產生超過原類型的取值范圍了。類似的,有浮點數的運算過程會把操作數自動轉型為DOUBLE類型。
分組數據
復雜的數據統計
前邊介紹了一些用來統計數據的聚集函數,我們可以方便的使用這些函數來統計出某列數據的行數、最大值、最小值、平均值以及整列數據的和。但是有些統計是比較麻煩的,比如說老師想根據成績表分別統計處'母豬的產后護理'和'論薩達姆的戰爭準備'這兩門課的平均分,那我們需要下邊兩個查詢:
mysql> SELECT AVG(score) FROM student_score WHERE subject = '母豬的產后護理'; +------------+ | AVG(score) | +------------+ | 73.0000 | +------------+ 1 row in set (0.00 sec)mysql> SELECT AVG(score) FROM student_score WHERE subject = '論薩達姆的戰爭準備'; +------------+ | AVG(score) | +------------+ | 73.2500 | +------------+ 1 row in set (0.00 sec)mysql> 復制代碼創建分組
如果課程增加到20門怎么辦呢?我們一共需要寫20個查詢語句,這樣神煩哎。為了在一條查詢語句中就完成這20條語句的任務,所以引入了分組的概念,就是針對某個列,將該列的值相同的記錄分到一個組中。拿subject列來說,按照subject列分組的意思就是將subject列的值是'母豬的產后護理'的記錄劃分到一個組中,將subject列的值是'論薩達姆的戰爭準備'的記錄劃分到另一個組中,如果subject列還有別的值,則劃分更多的組。其中被分組的列我們稱之為分組列。所以在student_score表中按照subject列分組后的圖示就是這樣:
![image_1c7e31ldj11th1s0v4ok1r5719bf9.png-143.7kB][1]
subject列中有多少不重復的課程,那就會有多少個分組。幸運的是,只要我們在GROUP BY子句中添加上分組列就好了,MySQL會幫助我們自動建立分組來方便我們統計信息,
mysql> SELECT subject, AVG(score) FROM student_score GROUP BY subject; +-----------------------------+------------+ | subject | AVG(score) | +-----------------------------+------------+ | 母豬的產后護理 | 73.0000 | | 論薩達姆的戰爭準備 | 73.2500 | +-----------------------------+------------+ 2 rows in set (0.01 sec)mysql> 復制代碼這個查詢的過程是把數據按照subject中的值將所有的數據分成兩組,然后對每個分組中的score列調用AVG函數進行數據統計,而不是對整個表的所有記錄的score列進行統計。
在使用分組的時候必須要意識到,分組的存在僅僅是為了方便我們統計每個分組中的信息,所以我們只需要把分組列和聚集函數放到查詢列表處就好,非分組列出現在查詢列表中是非法的!比方說上邊對subject列進行分組的例子,我們不能直接將number列放在查詢列表中,就像這樣:
mysql> SELECT number FROM student_score GROUP BY subject; ERROR 1055 (42000): Expression #1 of SELECT list is not in GROUP BY clause and contains nonaggregated column 'xiaohaizi.student_score.number' which is not functionally dependent on columns in GROUP BY clause; this is incompatible with sql_mode=only_full_group_by mysql> 復制代碼可以看到,如果直接把一個非分組列放到查詢列表中會報出一個ERROR。
小貼士:我們是想查詢按照`subject`分組后,各個分組中的記錄的`score`列的平均分是多少,你把`number`放到查詢列表是想表達個啥?正因為把這個`非分組列`擺在這與我們想要查詢的東西八竿子打不著,會讓服務器懵逼,所以會報出一個`ERROR`! 復制代碼帶有WHERE子句的分組查詢
上邊的例子是將表中每條記錄都劃分到某個分組中,我們也可以在劃分分組的時候就將某些記錄過濾掉,這時就需要使用WHERE子句了。比如老師覺得各個科目的平均分太低了,所以想先把分數低于60分的記錄去掉之后再統計平均分,就可以這么寫:
mysql> SELECT subject, AVG(score) FROM student_score WHERE score >= 60 GROUP BY subject; +-----------------------------+------------+ | subject | AVG(score) | +-----------------------------+------------+ | 母豬的產后護理 | 89.0000 | | 論薩達姆的戰爭準備 | 82.3333 | +-----------------------------+------------+ 2 rows in set (0.00 sec)mysql> 復制代碼這個過程可以分成兩個步驟理解:
將記錄進行過濾后分組。
在進行分組的時候將過濾到不符合WHERE子句的記錄,所以,最后的分組情況其實是這樣的(少于60分的記錄被過濾掉了):
![image_1c7e57i9phkbqarek21j6o97813.png-103.9kB][2]
對各個分組進行數據統計。
統計之后就產生了上述的結果。
過濾分組的統計信息
有時候分組的統計信息產生的太多了,假設subject列中有20門學科,那就會產生20個分組,如果想對這些統計信息做進一步的過濾處理,那就需要使用HAVING子句了。比方說老師想要平均分大于73分的課程,就可以這么寫:
mysql> SELECT subject, AVG(score) FROM student_score GROUP BY subject HAVING AVG(score) > 73; +-----------------------------+------------+ | subject | AVG(score) | +-----------------------------+------------+ | 論薩達姆的戰爭準備 | 73.2500 | +-----------------------------+------------+ 1 row in set (0.00 sec)mysql> 復制代碼需要注意的是,我們使用HAVING子句只是想過濾已經劃分好的分組,所以通常只根據分組的聚集函數的值組成的布爾表達式來過濾分組。比如上例中的AVG(score) > 73就是一個由聚集函數組成的布爾表達式,所有平均分不大于73分的分組都將被過濾掉。
比方說老師想過查詢最高分大于98分的課程的平均分,可以這么寫:
mysql> SELECT subject, AVG(score) FROM student_score GROUP BY subject HAVING MAX(score) > 98; +-----------------------+------------+ | subject | AVG(score) | +-----------------------+------------+ | 母豬的產后護理 | 73.0000 | +-----------------------+------------+ 1 row in set (0.00 sec)mysql> 復制代碼分組和排序
如果我們想對各個分組查詢出來的匯總數據進行排序,需要為查詢列表中有聚集函數的表達式添加別名,比如想按照各個學科的平均分從大到小降序排序,可以這么寫:
mysql> SELECT subject, AVG(score) AS avg_score FROM student_score GROUP BY subject ORDER BY avg_score DESC; +-----------------------------+-----------+ | subject | avg_score | +-----------------------------+-----------+ | 論薩達姆的戰爭準備 | 73.2500 | | 母豬的產后護理 | 73.0000 | +-----------------------------+-----------+ 2 rows in set (0.01 sec)mysql> 復制代碼嵌套分組
有時候按照某個列進行分組太籠統,也就是說一個分組內可以被繼續劃分成更小的分組。比方說對于student_info表來說,我們可以先按照department來進行分組,所以可以被劃分為2個分組:
![image_1c7e7psvl50i1icl19vpujjfd09.png-156.4kB][3]
我們覺得這樣按照department分組后,各個分組可以再按照major來繼續分組,從而劃分成更小的分組,所以再次分組之后的樣子就是這樣:
![image_1c7e9f21il3a1ao6duddp3kj3m.png-136.5kB][4]
所以現在有了2個大分組,4個小分組,我們把這種對大的分組下繼續分組的的情形叫做嵌套分組,如果你樂意,你可以繼續把小分組劃分成更小的分組。我們只需要在GROUP BY子句中把各個分組列依次寫上,用逗號,分隔開就好了。比如這樣:
mysql> SELECT department, major, COUNT(*) FROM student_info GROUP BY department, major; +-----------------+--------------------------+----------+ | department | major | COUNT(*) | +-----------------+--------------------------+----------+ | 航天學院 | 電子信息 | 1 | | 航天學院 | 飛行器設計 | 1 | | 計算機學院 | 計算機科學與工程 | 2 | | 計算機學院 | 軟件工程 | 2 | +-----------------+--------------------------+----------+ 4 rows in set (0.00 sec)mysql> 復制代碼可以看到,在嵌套分組中,聚集函數將作用在最后一個分組列上,在這個例子中就是major列。
使用分組注意事項
使用分組來統計數據給我們帶來了非常大的便利,但是要隨時提防有坑的地方:
如果分組列中含有NULL值,那么NULL也會作為一個獨立的分組存在。
如果存在多個分組列,也就是嵌套分組,聚集函數將作用在最后的那個分組列上。
如果查詢語句中存在WHERE子句和ORDER BY子句,那么GROUP BY子句必須出現在WHERE子句之后,ORDER BY子句之前。
非分組列不能單獨出現在檢索列表中(可以被放到聚集函數中)。
GROUP BY子句后也可以跟隨表達式(但不能是聚集函數)。
上邊介紹的GROUP BY后跟隨的都是表中的某個列或者某些列,其實一個表達式也可以,比如這樣:
mysql> SELECT concat('專業:', major), COUNT(*) FROM student_info GROUP BY concat('專業:', major); +-----------------------------------+----------+ | concat('專業:', major) | COUNT(*) | +-----------------------------------+----------+ | 專業:電子信息 | 1 | | 專業:計算機科學與工程 | 2 | | 專業:軟件工程 | 2 | | 專業:飛行器設計 | 1 | +-----------------------------------+----------+ 4 rows in set (0.00 sec)mysql> 復制代碼MySQL會根據這個表達式的值來對記錄進行分組,使用表達式進行分組的時候需要特別注意,檢索列表中的表達式和GROUP BY子句中的表達式必須完全一樣,而且不能使用別名。不過一般情況下我們也不會用表達式進行分組,所以基本沒啥用~
WHERE子句和HAVING子句的區別。
WHERE子句在數據分組前進行過濾,作用于每一條記錄,WHERE子句過濾掉的記錄將不包括在分組中。而HAVING子句在數據分組后進行過濾,作用于整個分組。
簡單查詢語句中各子句的順序
我們上邊介紹了查詢語句的各個子句,但是除了SELECT之外,其他的子句全都是可以省略的。如果在一個查詢語句中出現了多個子句,那么它們之間的順序是不能亂放的,順序如下所示:
SELECT [DISTINCT] 查詢列表 [FROM 表名] [WHERE 布爾表達式] [GROUP BY 分組列表 [HAVING 分組過濾條件] ] [ORDER BY 排序列表] [LIMIT 開始行, 限制條數] 復制代碼其中中括號[]中的內容表示可以省略,我們在書寫查詢語句的時候必須嚴格遵守這個子句順序,不然會報錯的!
總結
可能發生轉換的場景有下邊這幾種:
- 把操作數類型轉換為適合操作符計算的相應類型。
- 將函數參數轉換為該函數期望的類型。
- 存儲數據時,把某個值轉換為某個列需要的類型。
類型轉換時需要注意的一些地方:
-
MySQL會盡量把值轉換為表達式中需要的類型,而不是產生錯誤。
-
在運算時可能會自動提升操作數的類型。
MySQL使用GROUP BY子句創建分組,方便我們統計信息。分組的存在僅僅是為了方便我們統計每個分組中的信息,所以我們只需要把分組列和聚集函數放到查詢列表處就好。
對每個分組的統計信息進行過濾使用HAVING子句。
使用分組注意事項:
-
如果分組列中含有NULL值,那么NULL也會作為一個獨立的分組存在。
-
如果存在多個分組列,也就是嵌套分組,聚集函數將作用在最后的那個分組列上。
-
如果查詢語句中存在WHERE子句和ORDER BY子句,那么GROUP BY子句必須出現在WHERE子句之后,ORDER BY子句之前。
-
非分組列不能單獨出現在檢索列表中(可以被放到聚集函數中)。
-
GROUP BY子句后也可以跟隨表達式(但不能是聚集函數)。
-
WHERE子句在數據分組前進行過濾,作用于每一條記錄,WHERE子句過濾掉的記錄將不包括在分組中。而HAVING子句在數據分組后進行過濾,作用于整個分組。
簡單的查詢語句的格式如下:
SELECT [DISTINCT] 查詢列表 [FROM 表名] [WHERE 布爾表達式] [GROUP BY 分組列表 [HAVING 分組過濾條件] ] [ORDER BY 排序列表] [LIMIT 開始行, 限制條數] 復制代碼其中各個部分的含義如下:
-
SELECT 查詢列表:
指定要查詢的對象,可以是任意的合法表達式,不過通常我們都是指定列名為查詢對象的。
-
[DISTINCT]:
在查詢列表前指定DISTINCT,會將結果集中相同的記錄去除掉。
-
FROM 表名
指定要查詢的表。
-
WHERE 布爾表達式
對表中的記錄進行過濾,符合指定布爾表達式的記錄將被加入到結果集。
-
GROUP BY 分組列表 [HAVING 分組過濾條件]:
在統計數據的時候,可以按照分組列表中的列或者表達式將結果分成若干組,分組之后可以使用聚集函數對各組數據進行統計。如果有過濾分組的需求,可以使用HAVING子句來進行過濾。
-
ORDER BY 排序列表:
查詢語句并不保證得到的結果集中記錄是有序的,我們可以通過指定ORDER BY子句來明確要排序的列。
-
LIMIT 開始行, 限制條數:
如果最后查詢的得到的結果集中記錄太多,我們可以通過LIMIT子句來指定結果集中開始查詢的行以及一次查詢最多返回的記錄條數。
小冊
本系列專欄都是MySQL入門知識,想看進階知識可以到小冊中查看:《MySQL是怎樣運行的:從根兒上理解MySQL》的鏈接 。小冊的內容主要是從小白的角度出發,用比較通俗的語言講解關于MySQL進階的一些核心概念,比如記錄、索引、頁面、表空間、查詢優化、事務和鎖等,總共的字數大約是三四十萬字,配有上百幅原創插圖。主要是想降低普通程序員學習MySQL進階的難度,讓學習曲線更平滑一點~
轉載于:https://juejin.im/post/5c870f8d5188257dda56c372
總結
以上是生活随笔為你收集整理的MySQL入门系列:查询简介(四)之类型转换和分组查询的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【docker】【Gitlab】gitl
- 下一篇: STL标准库六大组件