MySQL必知必會1-20章讀書筆記


MySQL備忘


目錄

使用MySQL

  • mysql -u<usr> -p -h<host> -P <port> 分別指明用戶名,主機名,端口號

  • show columns from <table> 對每個字段返回一行,其中的信息分別是字段名,數據類型,是否允許為空,鍵信息,默認值及其他信息

    describe <table> 與上同

Snipaste_2020-04-26_21-49-29

  • show status 顯示廣泛的服務器狀態信息
  • SHOW CREATE DATABASE <database>SHOW CREATE TABLE <table>,分別用來顯示創建特定數據庫或表的MySQL語句
  • SHOW GRANTS,用來顯示授予用戶(所有用戶或特定用戶)的安全權限
  • SHOW ERRORSSHOW WARNINGS用來顯示服務器錯誤或警告消息

檢索數據

​ 通過select * from somewhere選擇所有的列時,列的順序一般情況下是列在表定義中出現的順序,但有時也不是,表的模式的變化(如添加或刪除列)可能會導致順序的變化。

一般情況不要使用通配符,除了的確需要,使用通配符會降低檢索和應用程序的性能

  • select distinct <variable> from <table> 來指示MySQL只返回不同的值

注意:

不能部分使用DISTINCT DISTINCT關鍵字應用於所有列而不僅是前置它的列。如果給出SELECT DISTINCT vend_id, prod_price,除非指定的兩個列都相同,否則所有行都將被檢索出來。也就是說select distinct a, b, c from table相當於select a, b, c from table group by a, b, c會選出所有a, b, c的不同組合。

  • select <variable> from <table> limit <n> 使用LIMIT子句來返回結果的第一行或前幾行

​ 限制返回結果不多於5行,此外,還可以指定喲檢索的開始行和行數。

行0 檢索出來的第一行為行0而不是行1。因此,LIMIT 1, 1將檢索出第二行而不是第一行。

在行數不夠時 LIMIT中指定要檢索的行數為檢索的最大行數。如果沒有足夠的行(例如,給出LIMIT 10, 5,但只有13行),MySQL將只返回它能返回的那么多行。

MySQL 5的LIMIT語法 LIMIT 3, 4的含義是從行4開始的3行還是從行3開始的4行?如前所述,它的意思是從行3開始的4行,這容易把人搞糊塗。由於這個原因,MySQL 5支持LIMIT的另一種替代語法。LIMIT 4 OFFSET 3意為從行3開始取4行,就像LIMIT 3, 4一樣

排序檢索數據

​ 檢索出的數據並不是以純粹的隨機順序顯示的。如果不排序,數據一般將以它在底層表中出現的順序顯示。這可以是數據最初添加到表中的順序。但是,如果數據后來進行過更新或刪除,則此順序將會受到MySQL重用回收存儲空間的影響。因此,如果不明確控制的話,不能(也不應該)依賴該排序順序。關系數據庫設計理論認為,如果不明確規定排序順序,則不應該假定檢索出的數據的順序有意義。

子句(clause) SQL語句由子句構成,有些子句是必需的,而有的是可選的。一個子句通常由一個關鍵字和所提供的數據組成。子句的例子有SELECT語句的FROM子句

  • select <variables> from <table> order by <variable> [desc] 為了明確地排序用select語句檢索出的數據,可以使用order by子句。 order by 子句取一個或者多個列的名字,據此對數據的數據進行排序。

通過非選擇列進行排序 通常,ORDER BY子句中使用的列將是為顯示所選擇的列。但是,實際上並不一定要這樣,用非檢索的列排序數據是完全合法的。

​ 按列降序排列:

DESC關鍵字只應用到直接位於其前面的列名。

在多個列上降序排序 如果想在多個列上進行降序排序,必須對每個列指定DESC關鍵字。

區分大小寫和排序順序 在對文本性的數據進行排序時,A與a相同嗎?a位於B之前還是位於Z之后?這些問題不是理論問題,其答案取決於數據庫如何設置。在字典(dictionary)排序順序中,A被視為與a相同,這是MySQL(和大多數數據庫管理系統)的默認行為。但是,許多數據庫管理員能夠在需要時改變這種行為(如果你的數據庫包含大量外語字符,可能必須這樣做)。這里,關鍵的問題是,如果確實需要改變這種排序順序,用簡單的ORDER BY子句做不到。你必須請求數據庫管理員的幫助。

​ 這里通過order by 和limit的組合使用來得到價格最高的商品。

ORDER BY子句的位置 在給出ORDER BY子句時,應該保證它位於FROM子句之后。如果使用LIMIT,它必須位於ORDER BY之后。使用子句的次序不對將產生錯誤消息。請注意,order by子句必須是select語句中的最后一個子句。否則將產生錯誤信息。

過濾數據

​ 在SELECT語句中,數據根據WHERE子句中指定的搜索條件進行過濾。WHERE子句在表名(FROM子句)之后給出

SQL過濾與應用過濾 數據也可以在應用層過濾。為此目的,SQL的SELECT語句為客戶機應用檢索出超過實際所需的數據,然后客戶機代碼對返回數據進行循環,以提取出需要的行。通常,這種實現並不令人滿意。因此,對數據庫進行了優化,以便快速有效地對數據進行過濾。讓客戶機應用(或開發語言)處理數據庫的工作將會極大地影響應用的性能,並且使所創建的應用完全不具備可伸縮性。此外,如果在客戶機上過濾數據,服務器不得不通過網絡發送多余的數據,這將導致網絡帶寬的浪費

WHERE子句操作符
操作符 說明
= 等於
<> 不等於
!= 不等於
< 小於
<= 小於等於
> 大於
>= 大於等於
BETWEEN 在指定的兩個值之間

​ 接着看一個特例:

檢查WHERE prod_name=‘fuses’語句,它返回prod_name的值為Fuses的一行。MySQL在執行匹配時默認不區分大小寫,所以fuses與Fuses匹配。

何時使用引號 如果仔細觀察上述WHERE子句中使用的條件,會看到有的值括在單引號內(如前面使用的'fuses'),而有的值未括起來。單引號用來限定字符串。如果將值與串類型的列進行比較,則需要限定引號。用來與數值列進行比較的值不用引號。

​ 為了檢查某個范圍的值,可使用BETWEEN操作符。其語法與其他WHERE子句的操作符稍有不同,因為它需要兩個值,即范圍的開始值和結束值。例如,BETWEEN操作符可用來檢索價格在5美元和10美元之間或日期在指
定的開始日期和結束日期之間的所有產品。

從這個例子中可以看到,在使用BETWEEN時,必須指定兩個值——所需范圍的低端值和高端值。這兩個值必須用AND關鍵字分隔。BETWEEN匹配范圍中所有的值,包括指定的開始值和結束值。

​ 在創建表時,表設計人員可以指定其中的列是否可以不包含值。在一個列不包含值時,稱其為包含空值NULL。

NULL 無值(no value),它與字段包含0、空字符串或僅僅包含空格不同。

​ 通過IS NULL子句過濾含空值的行:

NULL與不匹配 在通過過濾選擇出不具有特定值的行時,你可能希望返回具有NULL值的行。但是,不行。因為未知具有特殊的含義,數據庫不知道它們是否匹配,所以在匹配過濾或不匹配過濾時不返回它們。因此,在過濾數據時,一定要驗證返回數據中確實給出了被過濾列具有NULL的行。

​ 也就是說,當我們需要某一行時,尤其是哪一行某個屬性為空值,需要特別注意。

數據過濾

​ MySQL中的邏輯操作符有ANDOR, NOT。可以通過組合使用與或非和WHERE子句來獲得更復雜的條件。與絕大多數語言一樣,有優先級

優先級順序 運算符
1 !(按位非)
2 -(負號),~(按位反)
3 ^(按位異或)
4 *, /, %, DIV, MOD
5 -, +
6 <<, >>(移位運算符)
7 &(按位與)
8 |(按位或)
9 =, <=>, >=, >, <=, <, <>, !=, IS, LIKE, REGEXP, IN
10 BETWEEN,CASE, WHEN, THEN, ELSE
11 NOT
12 &&, AND
13 ||, OR, XOR
14 :=

參考:[菜鳥教程](https://www.runoob.com/mysql/mysql-operator.html)

備注:<=>用以比較和NULL是否相等,比如NULL <=> NULL返回1,而只當一個操作碼為NULL的時候,返回0。注意,不用<=>時,NULL表現出傳染性。

​ 同樣的,類似於絕大多數語言,在條件表達式含糊不清時,使用( )來顯示的指明運算優先次序。

  • select <variables> from <table> where <variable> in <tuple> order by <variable> 通過使用in (...)操作符來指定條件范圍。

​ 為什么要使用IN操作符呢?

  1. 在使用長的合法選項清單時,IN操作符的語法更清楚且更直觀。
  2. 在使用IN時,計算的次序更容易管理(因為使用的操作符更少)。
  3. IN操作符一般比OR操作符清單執行更快。(IN操作符是O(log(N))級別的復雜度,而OR是O(N)級別的復雜度)
  4. IN的最大優點是可以包含其他SELECT語句

​ WHERE子句中的NOT操作符有且只有一個功能,那就是否定它之后所跟的任何條件。

MySQL中的NOT MySQL支持使用NOT 對IN 、BETWEEN 和EXISTS子句取反,這與多數其他DBMS允許使用NOT對各種條件取反有很大的差別。

用通配符進行過濾

  • select <variables> from <table> where <variable> like <pattern>pattern中的%通配符表示任何字符出現任意次數。

區分大小寫 根據MySQL的配置方式,搜索可以是區分大小寫的。如果區分大小寫,'jet%'與JetPack 1000將不匹配。

​ 這個時候可以在like后加binary來表示區分大小寫。

注意:

  1. 這里的binary不是操作符,而是類型轉換運算符,將后面的字符串轉換成ASCII碼,以此來區分大小寫。
  2. 如果%使用在查詢單詞的開頭,這個sql語句將不走索引,查詢效率會降低。

注意尾空格 尾空格可能會干擾通配符匹配。例如,在保存詞anvil 時, 如果它后面有一個或多個空格, 則子句WHERE prod_name LIKE '%anvil'將不會匹配它們,因為在最后的l后有多余的字符。解決這個問題的一個簡單的辦法是在搜索模式最后附加一個%。一個更好的辦法是使用函數去掉首尾空格。

注意NULL 雖然似乎%通配符可以匹配任何東西,但有一個例外,即NULL。即使是WHERE prod_name LIKE '%'也不能匹配用值NULL作為產品名的行。(可以理解成NA的傳染性)

  • select prod_id, prod_name from products where prod_name like '_ton anvil' 這里出現的_通配符用途與%一致,但下划線只匹配單個字符而不是多個字符。

​ MySQL的通配符很有用,但是這種有用是有代價的:通配符搜索的處理一般要比前面討論的其他搜索要花的時間更長。

一些使用通配符的技巧:

  1. 不要過度使用通配符。如果其他操作符能達到相同的目的,應該使用其他操作符。
  2. 在確實需要使用通配符時,除非絕對有必要,否則不要把它們用在搜索模式的開始處。把通配符置於搜索模式的開始處,搜索起來是最慢的。
  3. 仔細注意通配符的位置。如果放錯地方,可能不會返回想要的數據。

用正則表達式進行搜索

​ 正則表達式是用來匹配文本的特殊的串(字符集合)。MySQL用WHERE子句對正則表達式提供了初步的支持,允許你指定正則表達式,過濾SELECT檢索出的數據。

  • select <variables> from <table> where <variable> rlike/regexp <pattern> 其中RLIKE或者REGEXP后所跟的模式即為正則表達式。

​ 例如select prod_name from products where prod_name regexp '1000' order by prod_name篩選出所有prod_name包含1000的行。

LIKE與REGEXP 在LIKE和REGEXP之間有一個重要的差別。請
看以下兩條語句:

如果執行上述兩條語句,會發現第一條語句不返回數據,而第二條語句返回一行。為什么?正如第8章所述,LIKE匹配整個列。如果被匹配的文本在列值中出現,LIKE將不會找到它,相應的行也不被返回(除非使用通配符)。而REGEXP在列值內進行匹配,如果被匹配的文本在列值中出現,REGEXP將會找到它,相應的行將被返回。這是一個非常重要的差別。那么,REGEXP能不能用來匹配整個列值(從而起與LIKE相同的作用)?答案是肯定的,使用^和$定位符(anchor)即可

匹配不區分大小寫 MySQL中的正則表達式匹配(自版本3.23.4后)不區分大小寫(即,大寫和小寫都匹配)。為區分大小寫,可使用BINARY關鍵字,如WHERE prod_name REGEXP BINARY 'JetPack .000'。

匹配\ 為了匹配反斜杠(\)字符本身,需要使用\\。

\或\? 多數正則表達式實現使用單個反斜杠轉義特殊字符,以便能使用這些字符本身。但MySQL要求兩個反斜杠(MySQL自己解釋一個,正則表達式庫解釋另一個)。

匹配字符類

存在找出你自己經常使用的數字、所有字母字符或所有數字字母字符等的匹配。為更方便工作,可以使用預定義的字符集,稱為字符類(character class)。

字符類

定位符

定位元字符

使REGEXP起類似LIKE的作用 利用定位符,通過用^開始每個表達式,用$結束每個表達式,可以使REGEXP的作用與LIKE一樣。

簡單的正則表達式測試 可以在不使用數據庫表的情況下用SELECT來測試正則表達式。REGEXP檢查總是返回0(沒有匹配)或1(匹配)。可以用帶文字串的REGEXP來測試表達式,並試
驗它們。相應的語法如下:

select 'help' regexp '[0-9]'

這個例子顯然將返回0(因為文本hello中沒有數字)。

創建計算字段

​ 存儲在表中的數據往往都不是應用程序所需要的。我們需要直接從數據庫中檢索出轉換、計算或格式化過的數據;而不是檢索出數據,然后再在客戶機應用程序或報告程序中重新格式化。計算字段是在select語句內創建的。

客戶機與服務器的格式 可在SQL語句內完成的許多轉換和格式化工作都可以直接在客戶機應用程序內完成。但一般來說,在數據庫服務器上完成這些操作比在客戶機中完成要快得多,因為DBMS是設計來快速有效地完成這種處理的。

  • select Concat(RTrim(vend_name), '(', RTrim(vend_country), ')') from vendors order by vend_name 通過Concat()函數連接字符串與變量,通過Trim()函數去除字符創兩端的空格。

  • select Concat(RTrim(vend_name), '(', RTrim(vend_country), ')') as vend_title from vendors order by vend_name 使用AS關鍵字來指定別名。

    此外,還可以直接在select語句中創建計算字段。如:

使用數據處理函數

函數沒有SQL的可移植性強 能運行在多個系統上的代碼稱為可移植的(portable)。相對來說,多數SQL語句是可移植的,在SQL實現之間有差異時,這些差異通常不那么難處理。而函數的可移植性卻不強。幾乎每種主要的DBMS的實現都支持其他實現不支持的函數,而且有時差異還很大。為了代碼的可移植,許多SQL程序員不贊成使用特殊實現的功能。雖然這樣做很有好處,但不總是利於應用程序的性能。如果不使用這些函數,編寫某些應用程序代碼會很艱難。必須利
用其他方法來實現DBMS非常有效地完成的工作。如果你決定使用函數,應該保證做好代碼注釋,以便以后你(或其他人)能確切地知道所編寫SQL代碼的含義。

大多數SQL實現支持以下類型的函數:

  • 用於處理文本串(如刪除或填充值,轉換值為大寫或小寫)的文本函數。
  • 用於在數值數據上進行算術操作(如返回絕對值,進行代數運算)的數值函數。
  • 用於處理日期和時間值並從這些值中提取特定成分(例如,返回兩個日期之差,檢查日期有效性等)的日期和時間函數。
  • 返回DBMS正使用的特殊信息(如返回用戶登錄信息,檢查版本細節)的系統函數。

一些常用的文本處理函數:

函數 說明
Left() 返回串左邊的字符
Length() 返回串的長度
Locate() 找出串的一個子串
Lower() 將串轉化為小寫
LTrim() 去掉串左邊的空格
Right() 返回串右邊的字符
RTrim() 去穿串右邊的空格
Soundex() 返回串的SOUNDEX值[1]
SubString() 返回子串的字符
Upper() 將串轉化為大寫
Reverse() 翻轉字符串

[1] SOUNDEX是一個將任何文本串轉換為描述其語音表示的字母數字模式的算法。SOUNDEX考慮了類似的發音字符和音節,使得能對串進行發音比較而不是字母比較。雖然SOUNDEX不是SQL概念,但MySQL(就像多數DBMS一樣)都提供對SOUNDEX的支持

一個使用Soundex()的例子:

image-20200428223506547

日期和時間常用的處理函數:

函數名稱 函數功能說明
ADDDATE() 添加日期
ADDTIME() 添加時間
CONVERT_TZ() 轉換不同時區
CURDATE() 返回當前日期
CURRENT_DATE()CURRENT_DATE 等同於 CURDATE()
CURRENT_TIME()CURRENT_TIME 等同於 CURTIME()
CURRENT_TIMESTAMP()CURRENT_TIMESTAMP 等同於 NOW()
CURTIME() 返回當前時間
DATE_ADD() 添加兩個日期
DATE_FORMAT() 按指定方式格式化日期
DATE_SUB() 求解兩個日期的間隔
DATE() 提取日期或日期時間表達式中的日期部分
DATEDIFF() 求解兩個日期的間隔
DAY() 等同於 DAYOFMONTH()
DAYNAME() 返回星期中某天的名稱
DAYOFMONTH() 返回一月中某天的序號(1-31)
DAYOFWEEK() 返回參數所定影的一周中某天的索引值
DAYOFYEAR() 返回一年中某天的序號(1-366)
EXTRACT 提取日期中的相應部分
FROM_DAYS() 將一個天數序號轉變為日期值
FROM_UNIXTIME() 將日期格式化為 UNIX 的時間戳
HOUR() 提取時間
LAST_DAY 根據參數,返回月中最后一天
LOCALTIME()LOCALTIME 等同於 NOW()
LOCALTIMESTAMPLOCALTIMESTAMP() 等同於 NOW()
MAKEDATE() 基於給定參數年份和所在年中的天數序號,返回一個日期
MAKETIME MAKETIME()
MICROSECOND() 返回參數所對應的毫秒數
MINUTE() 返回參數對應的分鍾數
MONTH() 返回傳入日期所對應的月序數
MONTHNAME() 返回月的名稱
NOW() 返回當前日期與時間
PERIOD_ADD() 為年-月組合日期添加一個時段
PERIOD_DIFF() 返回兩個時段之間的月份差值
QUARTER() 返回日期參數所對應的季度序號
SEC_TO_TIME() 將描述轉變成 'HH:MM:SS' 的格式
SECOND() 返回秒序號(0-59)
STR_TO_DATE() 將字符串轉變為日期
SUBDATE() 三個參數的版本相當於 DATE_SUB()
SUBTIME() 計算時間差值
SYSDATE() 返回函數執行時的時間
TIME_FORMAT() 提取參數中的時間部分
TIME_TO_SEC() 將參數轉化為秒數
TIME() 提取傳入表達式的時間部分
TIMEDIFF() 計算時間差值
TIMESTAMP() 單個參數時,函數返回日期或日期時間表達式;有2個參數時,將參數加和
TIMESTAMPADD() 為日期時間表達式添加一個間隔 INTERVAL
TIMESTAMPDIFF() 從日期時間表達式中減去一個間隔 INTERVAL
TO_DAYS() 返回轉換成天數的日期參數
UNIX_TIMESTAMP() 返回一個 UNIX 時間戳
UTC_DATE() 返回當前的 UTC 日期
UTC_TIME() 返回當前的 UTC 時間
UTC_TIMESTAMP() 返回當前的 UTC 時間與日期
WEEK() 返回周序號
WEEKDAY() 返回某天在星期中的索引值
WEEKOFYEAR() 返回日期所對應的星期在一年當中的序號(1-53)
YEAR() 返回年份
YEARWEEK() 返回年份及星期序號

具體用法參照:極客學院

應該總是使用4位數字的年份 支持2位數字的年份,MySQL處理00-69為2000-2069,處理70-99為1970-1999。雖然它們可能是打算要的年份,但使用完整的4位數字年份更可靠,因為
MySQL不必做出任何假定。

數值處理函數

函數名稱 函數說明
ABS() 返回數值表達式的絕對值
ACOS() 返回數值表達式的反余弦值。如果參數未在[-1, 1]區間內,則返回 NULL
ASIN() 返回數值表達式的反正弦值。如果參數未在[-1, 1]區間內,則返回 NULL
ATAN() 返回數值表達式的反正切值
ATAN2() 返回兩個參數的反正切值
BIT_AND() 返回表達式參數中的所有二進制位的按位與運算結果
BIT_COUNT() 返回傳入的二進制值的字符串形式
BIT_OR() 返回表達式參數中的所有二進制位的按位或運算結果
CEIL() 返回值為不小於傳入數值表達式的最小整數值
CEILING() CEIL()返回值為不小於傳入數值表達式的最小整數值
CONV() 轉換數值表達式的進制
COS() 返回所傳入數值表達式(以弧度計)的余弦值
COT() 返回所傳入數值表達式的余切值
DEGREES() 將數值表達式參數從弧度值轉變為角度值
EXP() 返回以e(自然對數的底數)為底,以所傳入的數值表達式為指數的冪
FLOOR() 返回不大於所傳入數值表達式的最大整數
FORMAT() 將數值表達式參數四舍五入到一定的小數位
GREATEST() 返回傳入參數的最大值
INTERVAL() 比較所傳入的多個表達式:expr1expr2expr3……,如果 expr1 < expr2,則返回0;如果 expr1 < expr3,則返回1……以此類推
LEAST() 返回傳入參數中的最小值
LOG() 返回傳入數值表達式的自然對數
LOG10() 返回傳入數值表達式的常用對數(以10為底的對數)
MOD() 返回參數相除的余數
OCT() 返回傳入數值表達式的八進制數值的字符串表現形式。如果傳入值為 NULL,則返回 NULL
PI() 返回 π 值
POW() 返回兩個參數的冪運算結果,其中一個參數為底,另一個參數為它的指數。
POWER() 返回兩個參數的冪運算結果,其中一個參數為底,另一個參數為它的指數。
RADIANS() 將參數由角度值轉換成弧度值
ROUND() 將所傳入數值表達式四舍五入為整數。也可以用來將參數四舍五入到一定的小數位
SIN() 返回參數(以弧度計)的正弦值
SQRT() 返回參數的非負平方根
STD() 返回參數的標准方差值
STDDEV() 返回參數的標准方差值
TAN() 返回參數(以弧度計)的正切值
TRUNCATE() 將數值參數 expr1 的小數位截取到 expr2 位如果 expr2 為0,則結果沒有小數位。

具體用法:極客學院

匯總數據

​ 我們經常需要匯總數據而不用把它們實際檢索出來,為此MySQL提供了專門的函數。使用這些函數,MySQL查詢可用於檢索數據,以便分析和報表生成。

以下是MySQL的五個聚集函數:

函數 說明
AVG() 返回某列的平均值
COUNT() 返回某列的行數
MAX() 返回某列的最大值
MIN() 返回某列的最小值
SUM() 返回某列值之和

標准偏差 MySQL還支持一系列的標准偏差聚集函數。詳情戳:MySQL 5.7 Reference Manual

因為上面五個函數都是語義清晰的,因此只講一些注意事項:

AVG()函數:

只用於單個列 AVG()只能用來確定特定數值列的平均值,而且列名必須作為函數參數給出。為了獲得多個列的平均值,必須使用多個AVG()函數。

NULL值 AVG()函數忽略列值為NULL的行。

COUNT()函數的兩種使用方法:

  • 使用COUNT(*)對表中行的數目進行計數,不管表列中包含的是空值(NULL)還是非空值
  • 使用COUNT(column)對特定列中具有值的行進行計數,忽略NULL值。

MAX()函數:

對非數值數據使用MAX() 雖然MAX()一般用來找出最大的數值或日期值,但MySQL允許將它用來返回任意列中的最大值,包括返回文本列中的最大值。在用於文本數據時,如果數據按相應的列排序,則MAX()返回最后一行。

MIN()函數:

對非數值數據使用MIN() MIN()函數與MAX()函數類似,MySQL允許將它用來返回任意列中的最小值,包括返回文本列中的最小值。在用於文本數據時,如果數據按相應的列排序,則MIN()返回最前面的行。

SUM()函數:

​ 除了統計某一列的變量的和,還可以用來合計計算值。如:

image-20200501113024646

在多個列上進行計算 如上所示,利用標准的算術操作符,所有聚集函數都可用來執行多個列上的計算。

聚集不同值:

對以上的5個聚集函數,都有如下選項:

  • 對所有的行執行計算,指定ALL參數或不給參數(因為ALL是默認行為);
  • 只包含不同的值,指定DISTINCT參數。

ALL為默認 ALL參數不需要指定,因為它是默認行為。如果不指定DISTINCT,則假定為ALL。

重要:

注意 如果指定列名,則DISTINCT只能用於COUNT()。DISTINCT不能用於COUNT(*),因此不允許使用COUNT(DISTINCT),否則會產生錯誤。類似地,DISTINCT必須使用列名,不能用
於計算或表達式。

​ 理由想想就知道:COUNT(*)的目的是統計表中有多少行,而DISTINCT選項會忽略掉一些NULL值,是沖突的。同樣對於計算或表達式,可能不同的計算式或表達式會產生相同的結果,如果不加考慮的直接去重,會丟失很多數據。

將DISTINCT用於MIN()和MAX() 雖然DISTINCT從技術上可用於MIN()和MAX(),但這樣做實際上沒有價值。一個列中的最小值和最大值不管是否包含不同值都是相同的。

重要:

取別名 在指定別名以包含某個聚集函數的結果時,不應該使用表中實際的列名。雖然這樣做並非不合法,但使用唯一的名字會使你的SQL更易於理解和使用(以及將來容易排除故障)。

​ 聚集函數用來匯總數據。MySQL支持一系列聚集函數,可以用多種方法使用它們以返回所需的結果。這些函數是高效設計的,它們返回結果一般比你在自己的客戶機應用程序中計算要快得多。

分組數據

​ MySQL通過SELECT語句中的GROUP BY子句創建分組。如:

select <variables> from <table> GROUP BY <flag> 其中 是某一列名。

一些規定:

  • GROUP BY子句可以包含任意數目的列。這使得能對分組進行嵌套,為數據分組提供更細致的控制。例如經常使用的:group by year, month, day按天分組
  • 如果在GROUP BY子句中嵌套了分組,數據將在最后規定的分組上進行匯總。換句話說,在建立分組時,指定的所有列都一起計算(所以不能從個別的列取回數據)。i.e.: group by year, month, day按天使用聚集函數
  • GROUP BY子句中列出的每個列都必須是檢索列或有效的表達式(但不能是聚集函數)。如果在SELECT中使用表達式,則必須在GROUP BY子句中指定相同的表達式。 不能使用別名。(應該是說GROUP BY子句中不允許使用列的別名,但是select語句中是可以指定列的別名的)。
  • 除聚集計算語句外,SELECT語句中的每個列都必須在GROUP BY子句中給出。
  • 如果分組列中具有NULL值,則NULL將作為一個分組返回。如果列中有多行NULL值,它們將分為一組。
  • GROUP BY子句必須出現在WHERE子句之后,ORDER BY子句之前。

也就是說,根據我們已經學的MySQL表達式,一套比較完整的流程是:

select var1, var2, COUNT(*) as n
from table
where Condition(var)
group by var1, var2
order by var1, var2 DESC

i.e.:我們必須先篩選,再分組,最后對分組進行排序。

此外:

使用ROLLUP 使用WITH ROLLUP關鍵字,可以得到每個分組以及每個分組匯總級別(針對每個分組)的值,如下所示:

select vend_id, count(*) as num_prods
from products
group by vend_id with rollup;

image-20200501120718906

​ 可以看到with rollup選項實際上做的是一個超分組的操作,即對分組的數據再次進行匯總。最后一行的NULL實際上代表為空,效果是這樣滴:

image-20200501121451477

​ 那問題就來了,如果本來的分組值里面有個NULL怎么辦呢?

嗎,

​ 注意,同所有數據分析的工具一樣,如果不加with rollup選項,NA值會附加在末尾,而一旦加了with rollup選項,空值會自動調整到第一行。

​ 除了能用GROUP BY分組數據外,MySQL還允許過濾分組,規定包括哪些分組,排除哪些分組。我們已經看到了WHERE子句的作用。

​ 但是,在這個需求下WHERE不能完成任務,因為WHERE過濾指定的是行而不是分組。事實上,WHERE沒有分組的概念。那么,不使用WHERE使用什么呢?MySQL為此目的提供了另外的子句,那就是HAVING子句。HAVING非常類似於WHERE。事實上,目前為止所學過的所有類型的WHERE子句都可以用HAVING來替代。唯一的差別是WHERE過濾行,而HAVING過濾分組。

HAVING支持所有WHERE操作符 之前提到過的所有可以在where子句中使用過的條件都可以在HAVING子句中使用。

HAVING和WHERE的差別 這里有另一種理解方法,WHERE在數據分組前進行過濾,HAVING在數據分組后進行過濾。這是一個重要的區別,WHERE排除的行不包括在分組中。這可能會改變計算值,從而影響HAVING子句中基於這些值過濾掉的分組。

分組與排序

ORDER BY與GROUP BY

image-20200502100652478

​ 我們經常發現用GROUP BY分組的數據確實是以分組順序輸出的。但情況並不總是這樣,它並不是SQL規范所要求的。此外,用戶也可能會要求以不同於分組的順序排序。僅因為你以某種方式分組數據(獲得特定的分組聚集值),並不表示你需要以相同的方式排序輸出。應該提供明確的ORDER BY子句,即使其效果等同於GROUP BY子句也是如此。

不要忘記ORDER BY 一般在使用GROUP BY子句時,應該也給出ORDER BY子句。這是保證數據正確排序的唯一方法。千萬不要僅依賴GROUP BY排序數據。

SELECT子句順序

SELECT var1, var2...
FROM <table>
WHERE <conditions>
GROUP BY var
HAVING <conditions>
ORDER BY var [DESC]
LIMIT [start,] num

說明:

子句 說明 是否必須使用
SELECT 要返回的列或表達式
FROM 從中檢索數據的表 僅從表中選擇數據時使用
WHERE 行級過濾
GROUP BY 分組說明 僅在按組計算聚集時使用
HAVING 組級過濾
ORDER BY 輸出排序順序
LIMIT 要檢索的行數

使用子查詢

​ 子查詢,即嵌套在其他查詢中的查詢。

例如:現在需要列出訂購物品TNT2的所有客戶:

image-20200502105436243

格式化SQL 包含子查詢的SELECT語句難以閱讀和調試,特別是它們較為復雜時更是如此。如上所示把子查詢分解為多行並且適當地進行縮進,能極大地簡化子查詢的使用。

​ 尤其要注意這個格式化的過程,因為外部查詢實際上需要的是內部查詢返回一個,分隔的tuple。

​ 可見,在WHERE子句中使用子查詢能夠編寫出功能很強並且很靈活的SQL語句。對於能嵌套的子查詢的數目沒有限制,不過在實際使用時由於性能的限制,不能嵌套太多的子查詢。

​ 雖然子查詢一般與IN操作符結合使用,但也可以用於測試等於(=)、不等於(<>)等。

子查詢和性能 這里給出的代碼有效並獲得所需的結果。但是,使用子查詢並不總是執行這種類型的數據檢索的最有效的方法。

作為計算字段使用子查詢

​ 計算字段,即跟着select語句的一些字段,是使用子查詢的另外一種方式。

​ 例:假如需要顯示customers表中每個客戶的訂單總數,為此,我們必須執行兩個步驟:

  • 從customers表中檢索客戶列表。
  • 對於檢索出的每個客戶,統計其在orders表中的訂單數目。

​ 有:

SELECT cust_name,
	   cust_state,
	   (SELECT COUNT(*)
        FROM orders
        WHERE orders.cust_id = customers.cust_id) AS orders
 FROM customers
 ORDER BY cust_name;

​ 注意這里被嵌套的查詢where子句需要使用完全限定的列名。

相關子查詢(correlated subquery) 涉及外部查詢的子查詢。

此外,如上給出的sql語句並不是最高效的辦法。后續會進行改進。

逐漸增加子查詢來建立查詢 用子查詢測試和調試查詢很有技巧性,特別是在這些語句的復雜性不斷增加的情況下更是如此。用子查詢建立(和測試)查詢的最可靠的方法是逐漸進行,這與MySQL處理它們的方法非常相同。首先,建立和測試最內層的查詢。然后,用硬編碼數據建立和測試外層查詢,並且僅在確認它正常后才嵌入子查詢。這時,再次測試它。對於要增加的每個查詢,重復這些步驟。這樣做僅給構造查詢增加了一點點時間,但節省了以后(找出查詢為什么不正常)的大量時間,並且極大地提高了查詢一開始就正常工作的可能性。

​ 如上提供了SQL的調試辦法。

聯結表

​ 將不同表中的數據合並就是聯結。

維護引用完整性 重要的是,要理解聯結不是物理實體。換句話說,它在實際的數據庫表中不存在。聯結由MySQL根據需要建立,它存在於查詢的執行當中。在使用關系表時,僅在關系列中插入合法的數據非常重要。回到這里的例子,如果在products表中插入擁有非法供應商ID(即沒有在vendors表中出現)的供應商生產的產品,則這些產品是不可訪問的,因為它們沒有關聯到某個供應商。為防止這種情況發生,可指示MySQL只允許在products表的供應商ID列中出現合法值(即出現在vendors表中的供應商)。這就是維護引用完整性,它是通過在表的定義中指定主鍵和外鍵來實現的。

內連接(自然連接)的兩種使用方法:

​ 如果我們要查詢供應商所有的商品及其價格,我們可以:

SELECT vend_name, prod_name, prod_price
FROM vendors, products
WHERE vendors.vend_id = products.vend_id
ORDER BY vend_name, prod_name;

​ 還可以:

SELECT vend_name, prod_name, prod_price 
FROM vendors INNER JOIN products
ON vendors.vend_id = products.vend_id
ORDER BY vend_name, prod_name;

​ 其中前者是通常的where子句寫法,后者明確的表示這是個內連接。通過ON子句傳遞條件。

特別要注意是:where子句和on子句傳遞的條件必須不能忘記,否則直接返回一個笛卡爾積。

不要忘了WHERE子句 應該保證所有聯結都有WHERE子句,否則MySQL將返回比想要的數據多得多的數據。同理,應該保證WHERE子句的正確性。不正確的過濾條件將導致MySQL返回
不正確的數據。

叉聯結 有時我們會聽到返回稱為叉聯結(cross join)的笛卡兒積(cartesian product)的聯結類型。

反過來看一下子查詢中查詢訂購產品TNT2的客戶那個例子:

# 子查詢
SELECT cust_name, cust_contact
FROM customers
WHERE cust_id IN (SELECT cust_id
				  FROM orders
				  WHERE order_num IN (SELECT order_num
				  					  FROM orderitems
				  					  WHERE prod_id = 'TNT2'))
				  					  
# 內連接
SELECT cust_name, cust_contact
FROM customers, orders, orderitems
WHERE customers, cust_id = orders.cust_id
	AND orderitems.order_num = orders.order_num
	AND prod_id = 'TNT2';

多做實驗 正如所見,為執行任一給定的SQL操作,一般存在不止一種方法。很少有絕對正確或絕對錯誤的方法。性能可能會受操作類型、表中數據量、是否存在索引或鍵以及其他一些條件的影響。因此,有必要對不同的選擇機制進行實驗,以找出最適合具體情況的方法。

創建高級聯結

​ MySQL不僅支持給計算字段取別名,同時也支持給表取別名。這樣做主要有以下兩個理由:

  • 縮短SQL語句;
  • 允許單條SELECT語句中多次使用相同的表。

​ 很容易想到的一點是:表別名只在查詢執行中使用,與列別名不一樣,表別名不返回到客戶機。

不同類型的連接:

​ 之前談到過內連接,這里介紹其他的三種連接: 自連接,自然連接,外連接。其中外連接包括左外連接,右外連接,但是MySQL並沒有提供全連接的語法支持。不過是可以通過某些方法模擬出來全連接的。

自連接:

自連接通過給同一張表取別名的方式,多次連接同一張表。並且進行條件運算。如:

image-20200502165436784

注意: MySQL中進行連接時,如果要選擇的計算字段語義不明,必須給定表名限定的字段名。如上所示。同時,既可以使用from <INNER/LEFT/RIGHT> JOIN on...也可以使用from ... where ...

用自聯結而不用子查詢 自聯結通常作為外部語句用來替代從相同表中檢索數據時使用的子查詢語句。雖然最終的結果是相同的,但有時候處理聯結遠比處理子查詢快得多。應該試一下兩種方法,以確定哪一種的性能更好。

自然連接:

無論何時對表進行聯結,應該至少有一個列出現在不止一個表中(被聯結的列)。標准的聯結(笛卡爾積)返回所有數據,甚至相同的列多次出現。自然聯結排除多次出現,使每個列只返回一次。

怎樣完成這項工作呢?答案是,系統不完成這項工作,由你自己完成它。自然聯結是這樣一種聯結,其中你只能選擇那些唯一的列。這一般是通過對表使用通配符(SELECT *),對所有其他表的列使用明確的子集來完成的。下面舉一個例子:

SELECT c.*, o.order_num, o.order_date,
						oi.prod_id, oi.quantity, oi.item_price
FROM customers AS c, orders AS o, orderitems AS oi
WHERE c.cust_id = o.cust_id
	AND oi.order_num = o.order_num
	AND prod_id = 'FB';
(其實我有點沒看太懂)

外部連接:

SELECT customers.cust_id, orders.order_num
FROM customers LEFT JOIN orders
	ON customers.cust_id = orders.cust_id;

MySQL左外連接,右外連接的語法支持分別是 LEFT JOINRIGHT JOIN

沒有=操作符 MySQL不支持簡化字符=和=*的使用,這兩種操作符在其他DBMS中是很流行的。

外部聯結的類型 存在兩種基本的外部聯結形式:左外部聯結和右外部聯結。它們之間的唯一差別是所關聯的表的順序不同。換句話說,左外部聯結可通過顛倒FROM或WHERE子句中表的順序轉換為右外部聯結。因此,兩種類型的外部聯結可互換使用,而究竟使用哪一種純粹是根據方便而定

此外,聚集函數也可以在各種連接中使用。

使用連接和鏈接條件:

  • 注意所使用的聯結類型。一般我們使用內部聯結,但使用外部聯結也是有效的。
  • 保證使用正確的聯結條件,否則將返回不正確的數據。
  • 應該總是提供聯結條件,否則會得出笛卡兒積。
  • 在一個聯結中可以包含多個表,甚至對於每個聯結可以采用不同的聯結類型。雖然這樣做是合法的,一般也很有用,但應該在一起測試它們前,分別測試每個聯結。這將使故障排除更為簡單。

組合查詢

組合查詢/復合查詢:或者說,不同查詢結果的並(UNION)。

兩種情況:

  • 在單個查詢中從不同的表返回類似結構的數據;
  • 對單個表執行多個查詢,按單個查詢返回數據。

組合查詢和多個WHERE條件 多數情況下,組合相同表的兩個查詢完成的工作與具有多WHERE子句條件的單條查詢完成的工作相同。換句話說,任何具有多個WHERE子句的SELECT語句都可以作為一個組合查詢給出,在以下段落中可以看到這一點。這兩種技術在不同的查詢中性能也不同。因此,應該試一下這兩種技術,以確定對特定的查詢哪一種性能更好。

​ 可用UNION操作符來組合數條SQL查詢。利用UNION,可給出多條SELECT語句,將它們的結果組合成單個結果集。例如:篩選價格小於等於5的所有物品的一個列表,而且還想包括供應商1001和1002生產的所有物品(不考慮價格)。雖然可以很簡單的用where子句完成,但這里也可以選用UNION操作符:

image-20200502204529237

​ 使用UNION操作符的一些規則:

  • UNION必須由兩條或兩條以上的SELECT語句組成,語句之間用關鍵字UNION分隔(因此,如果組合4條SELECT語句,將要使用3個UNION關鍵字)。
  • UNION中的每個查詢必須包含相同的列、表達式或聚集函數不過各個列不需要以相同的次序列出)。
  • 列數據類型必須兼容:類型不必完全相同,但必須是DBMS可以隱含地轉換的類型(例如,不同的數值類型或不同的日期類型)。

此外,UNION的默認行為會去重,如果想要覆蓋掉這種默認行為,可以:

SELECT vend_id, prod_id, prod_price
FROM products
WHERE prod_price <= 5
UNION ALL
SELECT vend_id, prod_id, prod_price
FROM products
WHERE vend_id IN (1001, 1002)

使用UNION ALL,MySQL不取消重復的行。

UNION與WHERE UNION幾乎總是完成與多個WHERE條件相同的工作。UNION ALL為UNION的一種形式,它完成WHERE子句完成不了的工作。如果確實需要每個條件的匹配行全部出現(包括重復行),則必須使用UNION ALL而不是WHERE。

最后,如果想要對UNION的結果排序,只能在最后一行加一行ORDER BY對整個表進行排序。不能以一種方式對一部分排序,以另一種方式對另一部分排序。並且,前面提到過的,可以對不同的表應用組合查詢,而不僅限於例子中的單表。

之前在創建高級連接的時候提到過,mysql並沒有語法層面的對full join的支持,學完UNION后,我們看看怎樣實現一個FULL JOIN:

可能第一次會是這樣:

SELECT * FROM t1
LEFT JOIN t2 ON t1.id = t2.id
UNION
SELECT * FROM t1
RIGHT JOIN t2 ON t1.id = t2.id

然后這就沒問題了嗎?不對,一個比較通用的FULL JOIN是不會自動去重的,所以第二次你可能會寫出這樣的code:

(The query above works for special cases where a FULL OUTER JOIN operation would not produce any duplicate rows. The query above depends on the UNION set operator to remove duplicate rows introduced by the query pattern. We can avoid introducing duplicate rows by using an anti-join pattern for the second query, and then use a UNION ALL set operator to combine the two sets. In the more general case, where a FULL OUTER JOIN would return duplicate rows, we can do this:)

SELECT * FROM t1
LEFT JOIN t2 ON t1.id = t2.id
UNION ALL
SELECT * FROM t1
RIGHT JOIN t2 ON t1.id = t2.id
WHERE t1.id IS NULL

但是這樣就完了嗎?不對,可以看到最后一行做了判斷t1.id IS NULL,對問題來了,怎么保證t1.id是允許為空的,上面那個查詢一定會是語法正確的嗎?

參考:

Stack Overflow How to do a FULL OUTER JOIN in MySQL?

Stack Overflow Why does MySQL report a syntax error on FULL OUTER JOIN?

MySQL Reference Manual

全文本搜索

MySQL是一個多引擎架構的關系數據庫。但是並非所有的引擎都支持全文本搜索。

並非所有引擎都支持全文本搜索 MySQL支持幾種基本的數據庫引擎。並非所有的引擎都支持全文本搜索。兩個最常使用的引擎為MyISAM和InnoDB,前者支持全文本搜索,而后者不支持。這就是為什么例子中絕大多數表格創建時使用的都是InnoDB , 而有一個樣例表(productnotes表)卻使用MyISAM的原因。如果你的應用中需要全文本搜索功能,應該記住這一點。

注意,在新版MySQL5.6.24中也允許在InnoDB上建全文本索引了。

那為什么有了通配操作符和正則表達式之后還需要全文本搜索呢?這是因為雖然這些機制非常有用,但有幾個重要的限制:

  • 性能——通配符和正則表達式匹配通常要求MySQL嘗試匹配表中所有行(而且這些搜索極少使用表索引)。因此,由於被搜索行數不斷增加,這些搜索可能非常耗時。
  • 明確控制——使用通配符和正則表達式匹配,很難(而且並不總是能)明確地控制匹配什么和不匹配什么。例如,指定一個詞必須匹配,一個詞必須不匹配,而一個詞僅在第一個詞確實匹配的情況下才可以匹配或者才可以不匹配。(我表示懷疑,正則表達式是萬能的)
  • 智能化的結果——雖然基於通配符和正則表達式的搜索提供了非常靈活的搜索,但它們都不能提供一種智能化的選擇結果的方法。例如,一個特殊詞的搜索將會返回包含該詞的所有行,而不區分包含單個匹配的行和包含多個匹配的行(按照可能是更好的匹配來排列它們)。類似,一個特殊詞的搜索將不會找出不包含該詞但包含其他相關詞的行。

接下來就是重頭戲了:所有這些限制以及更多的限制都可以用全文本搜索來解決。在使用全文本搜索時,MySQL不需要分別查看每個行,不需要分別分析和處理每個詞。MySQL創建指定列中各詞的一個索引,搜索可以針對這些詞進行。這樣,MySQL可以快速有效地決定哪些詞匹配(哪些行包含它們),哪些詞不匹配,它們匹配的頻率,等等。

​ 為了進行全文本搜索,必須索引被搜索的列,而且要隨着數據的改變不斷地重新索引。在對表列進行適當設計后,MySQL會自動進行所有的索引和重新索引。在索引之后,SELECT可與Match()和Against()一起使用以實際執行搜索。

​ 一般在創建表時啟用全文本搜索。CREATE TABLE語句接受FULLTEXT子句,給出被索引列的一個逗號分隔的列表。

如:

CREATE TABLE productnotes
(
  note_id    int           NOT NULL AUTO_INCREMENT,
  prod_id    char(10)      NOT NULL,
  note_date datetime       NOT NULL,
  note_text  text          NULL ,
  PRIMARY KEY(note_id),
  FULLTEXT(note_text)
) ENGINE=MyISAM;

​ MySQL 根據子句FULLTEXT(note_text)對該列進行索引。如果需要,也可以索引多個列。

​ 在定義之后,MySQL將會自動維護該索引。在增刪改的時候,索引會自動更新。可以在創建表時指定FULLTEXT,也可以稍后指定。

顯然,這解決了開頭提出的第一個問題--性能。

不要在導入數據時使用FULLTEXT 更新索引要花時間,雖然不是很多,但畢竟要花時間。如果正在導入數據到一個新表,此時不應該啟用FULLTEXT索引。應該首先導入所有數據,然后再修改表,定義FULLTEXT。這樣有助於更快地導入數據(而且使索引數據的總時間小於在導入每行時分別進行索引所需的總時間)。

​ 如果可以,盡量先導入數據,然后立即建立索引。

全文本索引的三種用途:

基礎功能:

​ 利用Match()和Against()兩個函數執行全文本搜索,其中Match指定被搜索的列,Against()指定要使用的搜索表達式。注意這里是可以使用對多列使用全文本搜索的。比如對一篇文章進行全文本搜索的時候,我們不光關心它的內容,對標題也會進行全文本搜索。

使用完整的Match() 說明 傳遞給Match() 的值必須與FULLTEXT()定義中的相同。如果指定多個列,則必須列出它們(而且次序正確)。(好像在現在版本里面只需要傳遞個Match()的值與定義相同即可,順序倒沒有那么重要了。)

搜索不區分大小寫 除非使用BINARY方式,否則全文本搜索不區分大小寫(沒有找到如何區分大小寫的辦法)。后續,找到了,建表的時候將字符編碼方式從utf8改成utf8bin即可。其余的字符編碼方式同理。

那怎么實現開頭提出的第三個問題呢?

我們看全文索引怎么對搜索的結果進行排序的。

實例:

SELECT note_text,
	   Match(note_text) Against('rabbit') AS sequence
FROM productnotes;

結果:

image-20200502232252206

分析:

這里,在SELECT而不是WHERE子句中使用Match()和Against()。這使所有行都被返回(因為沒有WHERE子句)。Match()和Against()用來建立一個計算列(別名為rank),此列包含全文本搜索計算出的等級值。等級由MySQL根據行中詞的數目、唯一詞的數目、整個索引中詞的總數以及包含該詞的行的數目計算出來。正如所見,不包含詞rabbit的行等級為0(因此不被前一例子中的WHERE子句選擇)。確實包含詞rabbit的兩個行每行都有一個等級值,文本中詞靠前的行的等級值比詞靠后的行的等級值高。

這個例子說明了全文搜索怎么排除行(排除那些相關等級為0的行),怎么排序結果(按等級降序排序)。

排序多個搜索項 如果指定多個搜索項,則包含多數匹配詞的那些行將具有比包含較少詞(或僅有一個匹配)的那些行高的等級值。

使用查詢擴展:

查詢擴展用來設法放寬所返回的全文本搜索結果的范圍。

B19254F4FC3B0C54D51F585848C5BC30

IMG_0344(https://gallery-1259614029.cos.ap-chengdu.myqcloud.com/img/20200502233247.PNG)

C1BCF00D3D9EC511821CDECB21A38E85

敲字員消極怠工。。。

布爾文本搜索:

​ MySQL全文搜索支持兩種模式:默認的稱為自然語言模式(IN NATURAL LAGUAGE MODE),再者就是布爾文本搜索(IN BOOLEAN MODE)。

​ 布爾文本搜索提供以下的內容細節:

  • 要匹配的詞;
  • 要排斥的詞(如果某行包含這個詞,則不返回該行,即使它包含其他指定的詞也是如此);
  • 排列提示(指定某些詞比其他詞更重要,更重要的詞等級更高);
  • 表達式分組;
  • 另外一些內容。

即使沒有FULLTEXT索引也可以使用 布爾方式不同於迄今為止使用的全文本搜索語法的地方在於, 即使沒有定義FULLTEXT索引,也可以使用它。但這是一種非常緩慢的操作(其性能將隨着數據量的增加而降低)。(雖然說是這么說,但是還沒找到怎么使用的方法)

全文本搜索布爾操作符:

操作符 描述
+ 包括,這個詞必須存在。
- 排除,這個詞不能存在。
> 包括並增加排名值。
< 包括並降低排名值。
() 將單詞分組成子表達式(允許將其包括,排除,排序等作為一個組)。
~ 否定一個詞的排名值。
* 通配符,在結尾的單詞,感覺有點像正則里的位置匹配$符號
“” 定義一個短語(與單個單詞列表相反,整個短語匹配包含或排除)。
表格參考:[易百教程](https://www.yiibai.com/mysql/boolean-text-searches.html)

舉例:

使用要注意的一些地方:

  • 在索引全文本數據時,短詞被忽略且從索引中排除。短詞定義為那些具有3個或3個以下字符的詞(如果需要,這個數目可以更改)。
  • MySQL帶有一個內建的非用詞(stopword)列表,這些詞在索引全文本數據時總是被忽略。如果需要,可以覆蓋這個列表(請參閱MySQL文檔以了解如何完成此工作)。
  • 許多詞出現的頻率很高,搜索它們沒有用處(返回太多的結果)。因此,MySQL規定了一條50%規則,如果一個詞出現在50%以上的行中,則將它作為一個非用詞忽略。50%規則不用於IN BOOLEAN MODE。
  • 如果表中的行數少於3行,則全文本搜索不返回結果(因為每個詞或者不出現,或者至少出現在50%的行中)。
  • 忽略詞中的單引號。例如,don't索引為dont。
  • 不具有詞分隔符(包括日語和漢語)的語言不能恰當地返回全文本搜索結果。這個地方又要注意一下,在后續版本中,加入了分詞器,使得中文和日文等也能很好的建立全文索引。

沒有鄰近操作符 鄰近搜索是許多全文本搜索支持的一個特性,它能搜索相鄰的詞(在相同的句子中、相同的段落中或者在特定數目的詞的部分中,等等)。MySQL全文本搜索現在還不支持鄰近操作符,不過未來的版本有支持這種操作符的計划。

我才說的應該就是分詞器了....找了很久也沒看到鄰近搜索的消息。

接下來看一些其他最近版本的全文本搜索:

首先看一下一些與全文本搜索有關的變量:

MySQL對全文索引的支持:

  • 在 MYSQL 中全文索引是類型為 FULLTEXT 的索引.
  • 全文索引只能用在 InnoDBMyISAM 表上, 且只能在 CHAR, VARCHAR, TEXT 類型的列上創建
  • MYSQL 提供了內置的基於 ngram 的解析器, 用於支持中韓日文; 還有一個可安裝的 MeCAb 解析器插件來支持日文.
  • FULLTEXT 索引可以在創建表時通過 CREATE TABLE 中定義, 或者在之后 ALTER TABLECREATE INDEX.
  • 對於大數據集, 應該將數據插入沒有 FULLTEXT 索引的表中, 再創建全文索引, 這比將數據插入已創建全文索引的表中更快.

實現細節以及中文搜索:

​ 默認情況下, 搜索是以 不區分大小寫 的方式執行的. 要區分大小寫, 必須對索引列使用區分大小寫或二進制的排序規則. 比如, 使用 utf8mb4 字符集的列可以使用 utf8mb4_0900_as_csutf8mb4_bin 排序規則.

​ 包含在雙引號中的短語, 將按字面值進行匹配. 全文索引會將短語分解為單詞, 並在 FULLTEXT 索引中搜索單詞. 非單詞字符不要求完全匹配. 搜索僅要求匹配項包含與短語完全相同的單詞, 並且順序相同. 例如, 短語 "test phrase" 與文本 "test, phrase" 就匹配.

​ MYSQL FULLTEXT 實現會將任何 字符序列(字母, 數字, 下划線) 視作一個單詞. 這個字符序列中可以包含單引號(), 但在一行中不能多於一個. 所以 aaa'bbb是一個單詞, 但aaa''bbb` 就不是一個單詞. 同時 FULLTEXT 會將單詞開頭或結尾的單引號刪除.

​ 內置的解析器通過查找某些定界符(delimiter characters)來確定單詞的開頭或結果. 常見的定界符有空格, 逗號, 句號. 對於單詞之間沒有定界符分隔的語言, 例如中文, 內置的解析器就無法確定單詞的開始或結束位置.

對於這種情況, 為了將這些語言中的單詞添加到全文索引中, 有兩種方式處理.

  1. 對文本進行預處理, 使得單詞之間存在任意的定界符. 通常是分詞.
  2. 創建 FULLTEXT 時使用 ngram 解析器(中日韓文適用)或 MeCab 解析器(日文適用).

在全文索引中, 某些單詞會被忽略掉:

  • 任何太短的單詞. 全文索引的默認最小單詞長度在 InnoDB 是三個字符, 在 MyISAM 中是四個字符. 這個特性對 ngram 解析器不適用, ngram 解析器中單詞長度由 ngram_token_size 選項確定.
  • 在 stopwords 中的單詞將被忽略.(最開始的截圖中有介紹)

​ 集合和查詢中的每個正確的單詞都被根據其在集合或查詢中的重要性進行加權. 類似於 TF-IDF. 在多數文檔中出現的單詞權重比較低. 在所有文檔中較少出現的單詞權重較高.

布爾搜索:

布爾型的全文搜索支持以下運算符:

  • + 前導或尾隨的加號表示單詞必須出現在返回的每一行中. InnoDB 僅支持前導的加號.
  • - 前導或尾隨的減號表示單詞不能再返回的任何行中出現. InnoDB 僅支持前導的減號. - 僅能排除其他條件搜索到的行. 僅有 - 會返回空的結果.
  • (沒有操作符) 默認情況, 該單詞為可選, 但包含該單詞的行評分較高.
  • @distance 僅用於 InnoDB 表. 測試兩個或多個單詞出現的距離是否在 distance 的值之內. distance 的單位是單詞的個數. 使用雙引號指定要比較的單詞. MATCH(col1) AGAINST('"word1 word2 word3" @8' IN BOOLEAN MODE) word1, word2, word3 之間的距離(單詞數)在 8 之內.
  • >``< 用於修改單詞對所在行的相關性的貢獻度.
  • () 括號用於將單詞分組為子表達式. 括號可以嵌套.
  • ~ 前導的 ~ 表示否定運算符, 使得單詞對行的相關性的貢獻度為負數. 這對於標記噪音字符很有用. 包含這類單詞的行的相關性低於其他行, 但不完全排除.
  • * 星號用作截斷(通配符)運算符. 當一個單詞使用截斷符指定時, 即使它太短或者在 stopwords 列表中, 也不會被忽略.
  • " 包含在引號中的短語, 將按字面值進行匹配.

下面是幾個例子:

  • 'apple banana' 返回至少包含其中一個單詞的行.
  • '+apple ~macintosh' 返回包含單詞 apple 的行, 如果行中有 macintosh, 它的評級會更低.
  • `'+apple +(>turnover 返回行中同時有 apple 和 turnover, 或者同時有 apple 和 strudel 的行. apple 和 turnover 的行, 比 apple 和 strudel 的行優先級高.
  • apple* 返回單詞以 apple 開頭的行, 例如 “apple”, “apples”, “applesauce”, “applet”.
  • '"some words"' 返回行中明確包含短語 "some words" 的行, 可以是 “some words of wisdom” 但不能是 “some noise words”.

InnoDB 上的全文搜索是建立在 Sphinx 全文搜索引擎上的, 算法是基於 BM25 和 TF-IDF 排名算法.

相關性排名的計算方式

# 計算 IDF 逆文檔頻率
${IDF} = log10( ${total_records} / ${matching_records} )
# 文檔多次包含一個單詞時
${TF} * ${IDF}

# 單個單詞的相關性, 不知道為什么要多乘次 ${IDF}
${rank} = ${TF} * ${IDF} * ${IDF}

# 多個單詞的相關性
${rank} = ${TF} * ${IDF} * ${IDF} + ${TF} * ${IDF} * ${IDF}

ngram 解析器

​ 一個 ngram 是 n 個字符的連續序列. ngram 解析器將文本序列標記為連續的 n 字符序列. 對於文本序列 "abcd" 可以使用 ngram 解析器標記為不同的 n 版本:

n=1: 'a', 'b', 'c', 'd'
n=2: 'ab', 'bc', 'cd'
n=3: 'abc', 'bcd'
n=4: 'abcd'

​ ngram 全文解析器是內置的服務器插件, 啟動服務器時會自動加載該插件.

配置 token size

​ ngram 解析器默認的 ngram token size 是 2(bigram).

​ 使用 ngram_token_size 配置選項設置 token size, 可配置的最小值是 1, 最大值是 10. 通常將 ngram_token_size 配置為你想要搜索的最大 token 的長度.

可以在啟動的時候設置:

mysqld --ngram_token_size=2

或者在配置文件中設置:

[mysqld]
ngram_token_size=2

查看當前配置:

show variables like '%ngram%';

使用 ngram 解析器創建 FULLTEXT 索引

CREATE TABLE articles2 (
  id INT UNSIGNED AUTO_INCREMENT NOT NULL PRIMARY KEY,
  title VARCHAR(200),
  body TEXT,
  FULLTEXT (title,body) WITH PARSER ngram
) ENGINE=InnoDB CHARACTER SET utf8mb4;

插入些測試數據:

SET NAMES utf8mb4;
INSERT INTO articles2 (title,body) VALUES
    ('數據庫管理','在本教程中我將向你展示如何管理數據庫'),
    ('數據庫應用開發','學習開發數據庫應用程序');

在表 INFORMATION_SCHEMA.INNODB_FT_INDEX_CACHE 中查看 tokenized data:

SET GLOBAL innodb_ft_aux_table="test/articles2";
SELECT * FROM INFORMATION_SCHEMA.INNODB_FT_INDEX_CACHE ORDER BY doc_id, position;

搜索查詢:

select * from articles2 where match(title,body) against ("開發");

修改已存在的表:

CREATE TABLE articles (
  id INT UNSIGNED AUTO_INCREMENT NOT NULL PRIMARY KEY,
  title VARCHAR(200),
  body TEXT
) ENGINE=InnoDB CHARACTER SET utf8;

ALTER TABLE articles ADD FULLTEXT INDEX ft_index (title,body) WITH PARSER ngram;

# Or:

CREATE FULLTEXT INDEX ft_index ON articles (title,body) WITH PARSER ngram;

其他細節

​ ngram 解析器會在解析時消除空格.

​ 對於 stopwords, ngram 解析器會排除包含 stopwords 的 token. 比如 stopwords 包含逗號, "a,b" 會在 n=2 時解析為 "a," 和 ",b", 這兩個 token 都會被排除. stopwords 中長度大於 ngram_token_size 的單詞, 將會被忽略.

​ 對於自然語言搜索, 搜索字符串將會被轉換為 ngram token 的並集, 比如 "abc" 會被轉換為 "ab" 和 "bc".

​ 對於布爾搜索, 搜索字符串將會被轉換為 ngram phrase search. 比如 "abc" 會被轉換為 ""ab bc"". 給定兩個文檔, 一個包含 'ab', 另一個包含 'abc', 搜索短語 ""ab bc"" 只會匹配包含 'abc' 的文檔.

通配符搜索可能返回意外的結果. 以下行為適用:

  • 如果一個通配符搜索的前綴項小於 ngram_token_size, 查詢會返回所有 ngram token 以前綴開始的行. 比如 n=2 時, 搜索 "a*" 會返回所有 ngram token 中以 "a" 開頭的行.
  • 如果一個通配符搜索的前綴項大於 ngram_token_size, 前綴項會被轉換為 ngram phrase, 並且忽略通配符. 比如 n=2 時, "abc*" 通配符搜索會被轉換為 "ab bc".

​ phrase search 會被轉換為 ngram phrase search. 比如搜索短語 "abc" 會被轉換為 "ab bc", 返回包含 "abc" 或 "ab bc" 的文檔. 搜索短語 "abc def" 會被轉換為 "ab bc de ef", 返回包含 "abc def" 或 "ab bc de ef" 的文檔. 文檔包含 "abcdef" 的卻不會返回.

最后這個真是迷惑行為大賞, 還是看個例子好了.

INSERT INTO articles2 (title,body) VALUES ('abcdef','在本教程中我將向你展示如何管理數據庫');

普通搜索是有數據的:

select * from articles2 where match(title,body) against ('abc def' IN BOOLEAN MODE);

短語搜索沒有數據, 注意用 雙引號 引起來的表示短語:

select * from articles2 where match(title,body) against ('"abc def"' IN BOOLEAN MODE);

然后切換到自然語言搜索, 兩種方式都是有結果的:

select * from articles2 where match(title,body) against ('abc def');
select * from articles2 where match(title,body) against ('"abc def"');

參考:

官方文檔

知乎

插入數據

插入數據通常有以下幾種形式:

  • 插入完整的行;
  • 插入行的一部分;
  • 插入多行;
  • 插入某些查詢的結果

插入及系統安全 可針對每個表或每個用戶,利用MySQL的安全機制禁止使用INSERT語句

插入完整的行:

INSERT INTO <table> VALUES(NULL/num/string/...)

比如:

分析:

此例子插入一個新客戶到customers表。存儲到每個表列中的數據在VALUES子句中給出,對每個列必須提供一個值。如果某個列沒有值(如上面的cust_contact和cust_email列),應該使用NULL
值(假定表允許對該列指定空值)。各個列必須以它們在表定義中出現的次序填充。第一列cust_id也為NULL。這是因為每次插入一個新行時,該列由MySQL自動增量。你不想給出一個值(這是MySQL的工作),又不能省略此列(如前所述,必須給出每個列),所以指定一個NULL值(它被
MySQL忽略,MySQL在這里插入下一個可用的cust_id值)。

​ 可以看到,上面的SQL語句極其的不健壯,如果列中的次序發生改變,難免會出現安全問題,所以可以考慮用如下的方式插入記錄。

總是使用列的列表 一般不要使用沒有明確給出列的列表的INSERT語句。使用列的列表能使SQL代碼繼續發揮作用,即使表結構發生了變化。

仔細地給出值 不管使用哪種INSERT語法,都必須給出VALUES的正確數目。如果不提供列名,則必須給每個表列提供一個值。如果提供列名,則必須對每個列出的列給出一個值。如果不這樣,將產生一條錯誤消息,相應的行插入不成功。

省略列 如果表的定義允許,則可以在INSERT操作中省略某些列。省略的列必須滿足以下某個條件。
 該列定義為允許NULL值(無值或空值)。
 在表定義中給出默認值。這表示如果不給出值,將使用默認值。
如果對表中不允許NULL值且沒有默認值的列不給出值,則
MySQL將產生一條錯誤消息,並且相應的行插入不成功。

提高整體性能 數據庫經常被多個客戶訪問,對處理什么請求以及用什么次序處理進行管理是MySQL的任務。INSERT操作可能很耗時(特別是有很多索引需要更新時),而且它可能降低等待處理的SELECT語句的性能。如果數據檢索是最重要的(通常是這樣),則你可以通過在INSERT和INTO之間添加關鍵字LOW_PRIORITY,指示MySQL降低INSERT語句的優先級,如下所示:順便說一下,這也適用於馬上要介紹的UPDATE和DELETE語句。

插入多個行:

​ 顯然,第一種方法是:使用多條插入語句,甚至一次提交他們,每條語句間以一個分號結束:

或者,在列名和次序相同的情況下,組合各語句:

注意:VALUES之間以逗號分隔。

提高INSERT的性能 此技術可以提高數據庫處理的性能,因為MySQL用單條INSERT語句處理多個插入比使用多條INSERT語句快。

插入檢索出的數據:

注意:這里導入了cust_id,所以插入的時候需要保證cust_id的值不會重復。當然,也可以根本不關心這一列。

INSERT SELECT中的列名 為簡單起見,這個例子在INSERT和SELECT語句中使用了相同的列名。但是,不一定要求列名匹配。事實上,MySQL甚至不關心SELECT返回的列名。它使用的是列的位置,因此SELECT中的第一列(不管其列名)將用來填充表列中指定的第一個列,第二列將用來填充表列中指定的第二個列,如此等等。這對於從使用不同列名的表中導入數據是非
常有用的。

更新和刪除數據

更新和刪除數據都有兩種方式:

  • 操作表中特定的行
  • 操作表中所有的行

不要省略WHERE子句 因為UPDATE和DELETE都涉及到更改存儲的數據,並且MySQL中是不存在undo操作的。因此一定要顯式的給出WHERE子句,除非的確打算對整個表進行操作。

同樣,兩者因為都是對數據更改的不可逆操作,因此可以通過給不同的用戶賦予不同的權限,保證操作的安全性。

UPDATE語句的組成:

  • 要更新的表
  • 列名和它們的新值
  • 確定要更新的行和過濾條件

例如:現在需要更新10005客戶的郵件信息和名字:

UPDATE customers
SET cust_name = 'The Fudds',
	cust_email = 'elmer@fudd.com'
WHERE cust_id = 10005;

可以看到,需要更新多個記錄時只需要將key-value對用逗號分開。

此外,UPDATE子句中給定的條件當然可以指定為單個值,多個值,范圍甚至是子查詢等。

IGNORE關鍵字 如果用UPDATE語句更新多行,並且在更新這些行中的一行或多行時出一個現錯誤,則整個UPDATE操作被取消(錯誤發生前更新的所有行被恢復到它們原來的值)。為了 即使是發生錯誤,也繼續進行更新,可使用IGNORE關鍵字,如下所示:
UPDATE IGNORE customers…

而刪除指定行某幾列的值的時候,只需要將指定列設置為NULL即可。

DELETE語句的組成:

  • 要刪除的數據來源表
  • 確定要刪除的行的過濾條件

例如:現在需要從customers表中刪除cust_id為10006的行:

DELETE FROM customers
WHERE cust_id = 10006;

注意DELETE語句不需要列名或通配符。DELETE刪除整行而不是刪除列。

刪除表的內容而不是表 DELETE語句從表中刪除行,甚至是刪除表中所有行。但是,DELETE不刪除表本身。

更快的刪除 如果想從表中刪除所有行,不要使用DELETE。可使用TRUNCATE TABLE語句,它完成相同的工作,但速度更快(TRUNCATE實際是刪除原來的表並重新創建一個表,而不
是逐行刪除表中的數據)。

注意:如果UPDATE和DELETE語句不帶WHERE子句,則都是對全表進行操作,這是很危險的。

一些推薦的原則:

  • 除非確實打算更新和刪除每一行,否則絕對不要使用不帶WHERE子句的UPDATE或DELETE語句。
  • 保證每個表都有主鍵,盡可能像WHERE子句那樣使用它(可以指定各主鍵、多個值或值的范圍)。
  • 在對UPDATE或DELETE語句使用WHERE子句前,應該先用SELECT進行測試,保證它過濾的是正確的記錄,以防編寫的WHERE子句不正確。
  • 使用強制實施引用完整性的數據庫,這樣MySQL將不允許刪除具有與其他表相關聯的數據的行。

對於最后一條是怎么理解的呢?不妨我們刪除一行試一哈:

沒錯,根本刪除不了。

小心使用 MySQL沒有撤銷(undo)按鈕。應該非常小心地使用UPDATE和DELETE,否則你會發現自己更新或刪除了錯誤的數據。

結語:

2020.05.01-2020.05.03,花了三天終於看完了MySQL的增刪改查和寫完了前20章總結的筆記,挑戰極限!是一本很好的MySQL入門書籍,后續應該會看《高性能MySQL》吧。不打算繼續寫讀書筆記了,效率實在比較低。希望能晚上看完最后10章的內容。Code is Power!

參考書目:

《MySQL必知必會》


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM