本篇文章是筆者學習《數據庫系統概念》總結來的,主要介紹SQL的基本結構和概念。
SQL語言有以下幾個部分:
- 數據定義語言(Data-Definition Language,DDL):SQL DDL提供定義關係模式、刪除關係以及修改關係模式的命令。
- 數據操縱語言(Data-Manipulation Language,DML):SQL DML提供從數據庫中查詢信息,以及在數據庫中插入元組、刪除元組、修改元組的能力。
- 完整性(integrity):SQL DDL包括定義完整性約束的命令,保存在數據庫中得數據必須滿足所定義的完整性約束。破壞完整性約束的更新是不允許的。
- 視圖定義(view definition):SQL DDL包括定義視圖的命令。
- 事務控制(transaction control):SQL包括定義事務的開始和結束的命令。
- 授權(authorization):SQL DDL包括定義對關係和視圖的訪問權限的命令。
SQL數據定義
數據庫中的關係集合必須由數據定義語言(DDL)指定給系統,SQL的DDL不僅能夠定義一組關係,還能定義每個關係的信息。
Mysql支持多種類型,大致可以分爲三類:數值、字符串和日期/時間類型。此處我們只介紹數值和字符串類型,日期/時間類型後面再做介紹。
類型 | 長度(字節) | 範圍(有符號) | 範圍(無符號) | 說明 |
---|---|---|---|---|
TINYINT | 1 | (-128,127) | (0,255) | 小整數值 |
SMALLINT | 2 | (-32768,32767) | (0,65535) | 大整數值 |
MADIUNINT | 3 | (-8388608,8388607) | (0,16777215) | 大整數值 |
INT | 4 | (-2147483648,2147483647) | (0,4294967295) | 大整數值 |
BIGINT | 8 | (-9,223,372,036,854,775,808,9 223 372 036 854 775 807) | (0,18 446 744 073 709 551 615) | 極大整數值 |
FLOAT | 4 | (-3.402 823 466 E+38,-1.175 494 351 E-38),0,(1.175 494 351 E-38,3.402 823 466 351 E+38) | 0,(1.175 494 351 E-38,3.402 823 466 E+38) | 單精度 浮點數值 |
DOUBLE | 8 | (-1.797 693 134 862 315 7 E+308,-2.225 073 858 507 201 4 E-308),0,(2.225 073 858 507 201 4 E-308,1.797 693 134 862 315 7 E+308) | 0,(2.225 073 858 507 201 4 E-308,1.797 693 134 862 315 7 E+308) | 雙精度 浮點數值 |
DECIMAL | DECIMAL(M,D),佔用M+2個字節 | 取決於M和D | 取決於M和D | M爲小數點左邊和右邊可存儲的十進制數字的最大個數,D爲小數點右邊可存儲的十進制數字的最大個數 |
類型 | 長度(字節) | 說明 |
---|---|---|
CAHR | (0,255) | 定長字符串,例如屬性A類型爲char(10),存入字符串“AAA”,該字符串後會追加7個空格達到10個串長度 |
VARCHAR | (0,65535) | 變長字符串,例如屬性V類型爲varchar(10),存入字符串“BBB”,不會增加空格 |
TINYBLOB | (0,255) | 二進制字符串 |
TINYTEXT | (0,255) | 短文本字符串 |
BLOB | (0,65535) | 二進制的長文本數據 |
TEXT | (0,65535) | 長文本數據 |
MEDIUMBLOB | (0,16777215) | 二進制中等長度文本數據 |
MEDIUMTEXT | (0,16777215) | 中等長度文本數據 |
LONGBLOB | (0,4294967295) | 二進制極大文本數據 |
LONGTEXT | (0,4294967295) | 極大文本數據 |
基本模式定義
使用create table命令定義SQL關係,create table命令的通用形式是:
CREATE TABLE r
(A1 D1,
A2 D2,
...,
<完整性約束>);
其中r是關係名,每個A是關係r模式中的屬性名,D是屬性A的域,也就是說D指定了屬性A的類型以及可選的約束,用於限制所允許的A取值的集合。create table命令用分號結束,事實上所有的SQL命令都用分號結束。另外,SQL語言是不區分大小寫的,但按照規範所有關係名或者屬性名都是小寫,關鍵字以及內置函數全部大寫。
創建名爲city的關係命令如下:
CREATE TABLE `city` (
`city_id` int(10),
`city_name` varchar(50),
`province_id` varchar(20),
`first_letter` varchar(20),
`is_hot` int(10),
`state` int(10),
PRIMARY KEY (`city_id`)
);
上面創建的關係具有6個屬性,city_id是一個10位的整數,city_name是最大長度爲50的字符串,指明瞭city_id屬性是city關係的主碼。
SQL支持許多不同的完整性約束,此處我們只討論以下幾個:
- primary key(A1,A2,...,An):表示屬性A1,A2,...,An構成關係的主碼。主碼屬性必須非空且唯一,也就是說沒有一個元組在主碼屬性上取空值,關係中也沒有兩個元組在所有主碼屬性上取值相同。雖然主碼的聲明是可選的,但爲每個關係指定一個主碼通常會更好。
- foreign key(A1,A2,...,An)references s:表示關係中任意元組在屬性(A1,A2,...,An)上的取值必須對應於關係s中某元組在主碼屬性上的取值。
- not null:一個屬性上的not null約束表明在該屬性上不允許空值,即此約束把空值排除在該屬性域之外。
SQL禁止破壞完整性約束的任何數據庫更新。例如,如果關係中一條新插入或新修改的元組在任意一個主碼屬性上有空值,或者元組在主碼屬性上的取值與關係中的另一個元組相同,SQL將標記一個錯誤並阻止更新。類似的,對於foreign key(A1,A2,...,An)references s,如果插入一個在屬性(A1,A2,...,An)上的取值沒有出現在關係s中,就會破壞外碼約束,SQL會阻止這種插入。
一個新創建的關係最初是空的,我們可以用insert命令將數據加載到關係中。例如向上面新創建的city關係插入一個元組,city_id爲000000,city_name爲紐約,province_id爲110000,first_letter爲ny,is_hot爲0,state爲1,則這條insert命令可以這樣寫:
INSERT INTO city VALUES(000000,'NewYork',110000,'ny',0,1);
值被給出的順序應該遵循對應屬性在關係模式中列出的順序。
我們可以使用delete命令從關係中刪除元組,例如下面的SQL將會刪除city關係中的所有元組。
DELETE FROM city;
如果要從SQL數據庫中刪除一個關係,我們使用drop table命令,drop tablea命令從數據庫中刪除關於被刪除關係的所有信息。
DROP TABLE city;
命令drop table是比delete from更強的語句,後者刪除了city關係中的所有元組但保留了city關係,前者不僅刪除了city關係中的所有元組還刪除了city關係。
我們使用alter table命令爲已有關係增加屬性,關係中的所有元組在新屬性上的取值將被設爲null,alter table命令的格式爲:
ALTER TABLE r ADD A D;
其中r是現有關係的名字,A是待添加屬性的名字,D是待添加屬性的域。例如爲關係city添加屬性grate,域爲int(1),命令如下:
ALTER TABLE city ADD grade int(1);
我們也可以使用以下命令從關係中刪除屬性:
ALTER TABLE r DROP A;
其中r是現有關係的名字,A是關係的一個屬性的名字。例如刪除關係city中名爲grade的屬性的命令如下:
ALTER TABLE city DROP grade;
SQL查詢的基本結構
SQL查詢的基本結構由三個子句構成:select、from和where。查詢的輸入是在from子句中列出的關係,在這些關係上進行where和select子句中指定的運算,然後產生一個關係作爲結果。
先介紹我們稍後會使用的三個關係模式,分別爲region,city,province。
region(region_id,region_name,city_id)
city(city_id,city_name,province_id,first_letter,is_hot,state)
province(province_id,province_name)
單關係查詢
我們用city關係的一個簡單查詢:“找出所有城市的名字”。city關係放在from子句中,城市的名字即city_name屬性,因此把它放在select子句中,最終命令如下:
SELECT city_name FROM city;
其結果是由屬性名爲city_name的單個屬性構成的關係。查詢結果如圖(下左一):
另一個查詢:“找出所有城市所屬的省份”,此查詢的命令如下:
SELECT province_id FROM city;
查詢結果如圖(上左二),因爲一個省份有多個城市,所以在city關係中,province_id可以出現多次。
在關係模型的形式化數學定義中,關係是一個集合,因此重複的元組不應該出現在關係中。在實踐中,去除重複是相當費時的,所以SQL允許在關係以及SQL表達式結果中出現重複。因此,在上述SQL查詢中,每個province_id在city關係中的元組中沒出現一次,都會在查詢結果中列出一次。我們如果想強行刪除重複,可以在select命令後加入關鍵詞distinct,例如上面的查詢中去除重複的province_id的語句爲:
SELECT DISTINCT province_id FROM city;
查詢結果如圖(上左三),可以看到與圖(上左二)有差別,province_id只出現一次。
select子句還可帶有+、-、*、/運算符的算術表達式,運算對象可以是常數或元組的屬性,例如以下查詢
SELECT city_id,city_name,province_id * 2 FROM city;
查詢結果如圖(下左一)將province_id的值*2,但關係city的province_id屬性並沒有任何改變。
where子句允許我們只選出那些在from子句的結果掛你想中滿足特定謂詞的元組。例如查詢:“找出所有在city關係中province_de爲130000且ciry_id大於130500的城市名稱”,該查詢的SQL如下:
SELECT city_id,city_name,province_id FROM city WHERE province_id = 130000 AND city_id > 130500;
查詢結果如圖(上左二)。SQL允許在where子句中使用邏輯連詞and、or和not,邏輯連詞的運算對象可以是包含比較運算符<、<=、>、>=、=和<>的表達式。SQL允許我們使用比較運算符來比較字符串、算術表達式以及特殊類型,例如日期類型。
多關係查詢
目前爲止我們的查詢示例都是基於單個關係的,通過查詢需要從多個關係中獲取信息,下面來學習如何書寫這樣的查詢。
我們現在想要這樣的查詢:“找出所有城市的名稱,以及他們所屬的省份id和省份名稱”。在關係city中我們可以很簡單的查找到城市名稱以及他們所屬的省份,但省份名稱在province表中。因此city關係中的每個元組必須與province關係中的元組匹配,後者在province_id上的取值相配與city在province_id上的取值。最終的SQL語句如下:
SELECT city_name,city.province_id,province.province_name
FROM city,province
WHERE city.province_id = province.province_id;
查詢結果如圖(下左一),由於province_id即出現在city關係中又出現在province關係中,因此在SQL中用關係名作爲前綴來說明我們使用的是哪個屬性。
多個關係的SQL查詢包括三種類型的子句:
- select子句:用於列出查詢結果中所需要的屬性;
- from子句:列出查詢求值中需要訪問的關係列表;
- where子句:作用在from子句中關係的屬性上的謂詞。
如果省略where子句,則謂詞爲true,from語句定義了一個在該子句中所列關係上的笛卡爾積。例如上面的查詢去掉where子句的查詢結果如圖(上左二),截圖中的數據較少,但還是可以看的出來笛卡爾積把city關係中的每個元組與province關係中的每個元組組合了。很明顯北京市只屬於北京市,與其他省份無關,這樣的數據是沒有意義的。因此需要where子句中的謂詞來限制笛卡爾積所建立的組合,只留下對所需答案有意義的組合。對於上面的查詢,city關係中的province_id與province關係中的province_id相匹配時,他們的組合纔是我們需要的答案。
對於多關係查詢,一個SQL查詢的含義可以理解如下:
- 在from子句中列出的關係產生笛卡爾積;
- 在步驟1的結果上應用where子句中指定的謂詞;
- 對於步驟2產生的每個元組,輸出select子句中指定的屬性或表達式的結果。
但上述步驟的順序只是有助於我們明白一個SQL查詢的結果應該是怎樣的,在SQL的實際實現中不會執行這種形式的查詢,它會儘可能只產生滿足where子句謂詞的笛卡爾積來優化執行。
自然連接
在上面的查詢示例中,需要從city關係和province關係中組合信息,匹配條件是city關係中的province_id屬性等於province關係中的province_id屬性。事實上from子句中的匹配條件在最通常的情況下需要在所有匹配名稱的屬性上相等。爲了簡化這種通用情況下SQL編程者的工作,SQL提供一種被稱作自然連接的運算。
自然連接(natural join)運算作用於兩個關係,併產生一個關係作爲結果。與笛卡爾積不同的是,自然連接只考慮那些在兩個關係模式中都出現的屬性上取值相同的元組對。例如city關係和province關係,自然連接只考慮在它們相同的屬性province_id上的取值相同的元組。
在多關係查詢中的示例查詢:“找出所有城市的名稱,以及他們所屬的省份id和省份名稱”,用自然連接運算實現更簡單:
SELECT city_name,city.province_id,province.province_name
FROM city NATURAL JOIN province;
查詢結果與多關係查詢中的查詢結果一致。
在from子句中,可以用自然連接將多個關係結合在一起,例如:
SELECT A1,A2,...,An
FROM r1 NATURAL JOIN r2 NATURAL JOIN ... NATURAL JOIN rn
WHERE P;
爲了讓讀者注意到自然連接的問題,在region關係中添加一個名稱爲province_name的屬性,關係中所有元組的該屬性值爲null。現在我們想要這樣一個查詢:“找出所有的區名稱,以及他們所屬的城市名稱和省份名稱”,使用自然連接的語句如下:
SELECT region_name,city_id,city_name,province.province_name
FROM region NATURAL JOIN city NATURAL JOIN province;
查詢結果如圖(下左一),生成的關係中沒有一個元組,與我們預期的似乎有些不符。原因在於自然連接會考慮兩個關係模式中都出現的屬性在取值上相同的元組對,region關係和city關係自然連接後的屬性包括region(region_id,region_name,city_id,province_name,city_name,province_id,first_letter,is_hot,state),而province關係包含的屬性包括(province_id,province_name),作爲這兩者自然連接的結果,需要來自這兩個輸入的元組既要在province_id上取值相同,又要在province_name上取值相同。
我們想要的是region關係和city關係自然連接,產生的關係的province_id與province關係的province_id相同即可,province_name屬性取值不需相同。下面的SQL即可滿足此查詢要求。
SELECT region_name,city_name,province.province_name
FROM region NATURAL JOIN city,province
WHERE city.province_id = province.province_id;
查詢結果如圖(下左二)。
事實上,爲了發揚自然連接的優點,同時避免不必要的相等屬性帶來的危險,SQL提供了一種自然連接的構造形式,允許用戶來指定那些列需要相等,用這種方式完成上面的查詢的SQL如下:
SELECT region_name,city_id,city_name,province.province_name
FROM (region NATURAL JOIN city) JOIN province USING(province_id);
查詢結果與圖(上左二)相同。join...using運算需要給定一個屬性名列表,其兩個輸入中都必須具有指定名稱的屬性。這樣的連接構造允許region關係中的province_name與province關係中的province_name是不同的。
附加的基本運算
更名運算
在上面的多關係查詢中,查詢結果的屬性名來自於from子句中關係的屬性名,但有些情況我們不能用這種方法派生的名字,原因如下:
- from子句的兩個關係可能存在同名屬性,這種情況就會出現重複的屬性名;
- 如果在select子句中使用算術表達式,那麼在結果中該屬性就沒有名稱;
- 有時我們需要重命名在查詢的結果的屬性。
因此,SQL提供了一個重命名結果關係中屬性的方法,即使用as子句:
old_name AS new_name
as子句既可以出現在select子句中,也可以出現在from子句中。例如我們用name來代替city_name,想用c來代替city,用p來代替province,那麼查詢:“找出所有城市的名稱,以及他們所屬的省份id和省份名稱”,可以用如下SQL語句完成:
SELECT city_name AS name,c.province_id,p.province_name
FROM city AS c NATURAL JOIN province AS p;
查詢結果如圖(下左一),查詢結果中的city_name被重命名成了name。像c和p那樣被用來重命名關係的標識符在SQL標準中被稱作相關名稱(correlation name),通常也被稱作表別名(table alias),或者相關變量(correlation variable),或者元組變量(tuple variable)。
字符串運算
SQL使用一對單引號來標示字符串,例如'hello'。如果單引號是字符串的組成部分,那就用兩個單引號字符來表示,如字符串'it's right'可表示爲'it"s right'。
在SQL標準中,字符串上的相等運算是大小寫敏感的,所以表達式“'S'=='s'”的結果是假。但一些數據庫例如MySQL,在匹配字符串時並不區分大小寫。
SQL還允許在字符串上有多種函數,MySQL的字符串函數如下:
- LOWER(column | str):將字符串參數轉換爲小寫後返回或者將某一列屬性轉換爲小寫後返回,例如:
//將字符串轉換爲小寫然後返回 SELECT LOWER('HELLO'); //將某個屬性轉換爲小寫然後返回 SELECT LOWER(first_letter) FROM city;
-
UPPER(column | str):將字符串參數轉換爲大寫後返回或將某一列屬性轉換爲大寫後返回,例如:
//將字符串轉換爲大寫然後返回 SELECT UPPER('HELLO'); //將某個屬性轉換爲大寫然後返回 SELECT UPPER(first_letter) FROM city;
-
CONCAT(column1 | str1,column2 | str2, ...):將多個字符串參數或屬性首尾相連後返回。如果某個參數爲null,則函數返回null;如果參數是數字,自動轉換爲字符串。例如:
SELECT CONCAT('hello',' ',city_name,' ',first_letter) FROM city;
-
CONCAT_WS(separator,str1,str2,...):將多個字符串以給定的分隔符separator首尾相連後返回,如果某個參數爲null,則忽略null;如果參數是數字,自動轉換爲字符串。例如:
SELECT CONCAT_WS(' || ','hello',city_name,city_id) FROM city;
-
SUBSTR(str,pos,len):與SUBSTRING函數相同,從字符串str的pos位置截取len個字符。len可以省略,省略時取到字符串結尾,len爲負數表示從字符串的尾部開始截取。第一個字符的位置爲1,不是0。例如:
SELECT SUBSTRING('hello world',5); 輸出結果:0 world SELECT SUBSTRING('hello world',5,4); 輸出結果:o wo SELECT SUBSTRING('hello world',-4); 輸出結果:orld
-
LENGTH(column | str):返回某個屬性或字符串的長度。例如:
//輸出結果爲所有元組的first_letter的屬性的長度 SELECT LENGTH(first_letter) FROM city; //輸出結果爲字符串長度,結果爲5 SELECT LENGTH('hello'); //輸出結果爲字符串長度,結果爲6。漢字的長度取決於字符集,utf8是6,gbk是4 SELECT LENGTH('你好');
-
CHAR_LENGTH(column | str):返回某個屬性或字符串的字符個數。例如:
//輸出結果爲所有元組的first_letter的屬性的字符 SELECT CHAR_LENGTH(first_letter) FROM city; //輸出結果爲字符串的字符個數 SELECT CHAR_LENGTH('hello'); //輸出結果爲字符串長度,結果爲2。漢字,數字和字母都算一個字符 SELECT CHAR_LENGTH('你好');
-
INSTR(column | str,column | substr):返回substr或某個屬性值在str或某個屬性值中第一次出現的位置。例如:
//返回province_id從第3個字符開始的兩個字符在city_id中第一次出現的位置 SELECT INSTR(city_id,SUBSTR(province_id,3,2)) FROM city;
-
LPAD(str,len,padstr):在str的左邊填充padstr直到長度達到len,返回填充後的字符串。例如:
SELECT LPAD('China',20,'hello'); 輸出結果:hellohellohelloChina SELECT LPAD('China',7,'hello'); 輸出結果:heChina SELECT LPAD('China',3,'hello'); 輸出結果:Chi
-
RPAD(str,len,padstr):在str的右邊填充padstr直到長度達到len,返回填充後的字符串。例如:
SELECT RPAD('China',20,'hello'); 輸出結果:Chinahellohellohello SELECT RPAD('China',7,'hello'); 輸出結果:Chinahe SELECT RPAD('China',3,'hello'); 輸出結果:Chi
-
TRIM([{BOTH | LEADING | TRAILING} [removeStr] FROM] str):從str中去掉兩端、 前綴或後綴的指定字符串。如果沒有指定removeStr,默認去掉空格;如果沒有指定BOTH、LEADING或TRAILING,默認爲BOTH。例如:
//不指定removeStr,默認去掉字符串兩端的空格 SELECT TRIM(' hello '); 輸出結果:hello //去掉指定字符串前綴空格 SELECT TRIM(LEADING FROM ' hello '); 輸出結果:hello(空格空格) //去掉指定字符串後綴空格 SELECT TRIM(TRAILING FROM ' hello '); 輸出結果: hello //去掉指定字符串後綴指定子字符串 SELECT TRIM(TRAILING 'lo' FROM ' hello'); 輸出結果: hel //去掉指定字符串前綴指定字符串 SELECT TRIM(LEADING 'he' FROM 'hello'); 輸出結果:llo
-
REPLACE(str,fromStr,toStr):在字符串str中查找全部的子字符串fromStr並替換爲toStr。例如:
SELECT REPLACE('10101','1','*****'); 輸出結果:*****0*****0*****
-
LTRIM(str),RTRIM(str):去掉字符串左端或右端空格。例如:
//刪除字符串左端空格 SELECT LTRIM(' hello '); 輸出結果:hello(空格空格空格) //刪除字符串右端空格 SELECT RTRIM(' hello '); 輸出結果: hello
-
REPEAT(str,count):將字符串str重複輸出count次。例如:
SELECT REPEAT('hello',4); 輸出結果:hellohellohellohello
-
REVERSE(str):將字符串str反轉後返回。例如
SELECT REVERSE('hello'); 輸出結果:olleh
-
SPACE(count):返回由count個空格組成的字符串。例如:
SELECT SPACE(5); 輸出結果:空格空格空格空格空格
-
LEFT(str,len):返回字符串str最左邊的len個字符。例如:
SELECT LEFT('hello',4); 輸出結果:hell
-
RIGHT(str,len):返回字符串str最右邊的len個字符。例如:
SELECT RIGHT('hello',4); 輸出結果:ello
-
STRCMP(str1,str2):比較字符串str1和str2,如果兩個字符串相同則返回0;如果str1小於str2則返回-1;如果str1大於str2則返回1。例如:
//字符串相同返回0 SELECT STRCMP('abcd','abcd'); 輸出結果:0 //第一個字符串小於第二個字符串返回-1 SELECT STRCMP('abcde','accd'); 輸出結果:-1 //第一個字符串大於第二個字符串返回1 SELECT STRCMP('dbcde','accd'); 輸出結果:1
在字符串上還可以使用LIKE操作粗來實現模式匹配,我們用兩個特殊的字符來描述模式:
- %:匹配任意子串。
- _:匹配任意一個字符。
'hello%':匹配任何以“hello”開頭的字符串
'%hello%':匹配任何包含“hello”的字符串
'___':匹配包含三個字符的字符串
'___%':匹配至少包含三個字符的字符串
例如:“找出城市名稱中包含'京'字的城市id和城市名稱”,SQL如下:
SELECT city_id,city_name FROM city WHERE city_name LIKE '%京%';
類似的,還可以使用not like,例如:“找出城市名稱中不包含'京'字的城市id和城市名稱”,SQL如下:
SELECT city_id,city_name FROM city WHERE city_name NOT LIKE '%京%';
select子句中的屬性說明
星號“*”可以用在select子句中表示“所有的屬性”,如下查詢的select子句表示city關係中的所有屬性都被選中:
SELECT * FROM city;
排列元組的顯示次序
order by子句可以讓查詢結果中元組按排列順序顯示。例如:“按first_letter的字母順序排列city關係數據”,SQL如下:
SELECT * FROM city ORDER BY first_letter;
order by子句默認使用升序,要說明順序,可以用desc表示降序,或用asc表示升序。例如:“按first_letter的字母降序,city_name升序排列city關係數據”,SQL如下:
SELECT * FROM city ORDER BY first_letter DESC,city_name ASC;
where子句謂詞
爲了簡化where子句,SQL提供between比較運算符來說明一個值是小於或等於某個值,同時大於或等於另一個值。例如我們想找出city_id大於等於130100且小於130400的城市信息,以下兩個SQL語句作用是相同的:
SELECT * FROM city WHERE city_id BETWEEN 130100 AND 130400;
SELECT * FROM city WHERE city_id >= 130100 AND city_id <= 130400;
類似的,我們還可以使用not between運算符。例如我們想找出city_id小於130100且大於130400的城市信息,以下兩個SQL語句作用是相同的:
SELECT * FROM city WHERE city_id NOT BETWEEN 130100 AND 130400;
SELECT * FROM city WHERE city_id < 130100 OR city_id > 130400;