数据库笔记: SQL
1 數據庫語言
DBMS提供操作命令和語言,使用戶能夠對數據庫進行各式各樣的操作,例如查詢、增、刪、改數據,定義、修改數據模式等
——— >這就構成了用戶和數據庫的接口。
DBMS所提供的語言一般局限于對數據庫的操作,有別于計算完備的程序設計語言,稱為數據庫語言(database language)。
1.1 數據庫語言分類
數據庫語言一般分為這幾種
1.1.1 形式化查詢語言(Query Language )
本章的重點SQL就是這一類語言,它有嚴格的語法和文法
1.1.2?表格查詢語言(Tabular Query Language )
用填表的方式描述查詢需求
我們給一個關系的名字(student),DBMS自動把屬性填到表里面去。然后用戶往表里面填需求。
在上圖中,比如用戶填的P,表示打印(顯示)操作;填的IS,表示找尋所有學院是IS學院的學生
1.1.3? 圖像查詢語言(Graphic Query Language)
每一個方框代表一種數據結構
找出數據庫中教師的名字和他們的年齡(青色表中段)。其中教師的職位是教授(左綠色表)、年齡大于45歲(青色表下段)、學院是計算機學院(右綠色表)
1.2?對于關系數據模型的查詢語言
關系數據模型的查詢語言是基于關系代數或關系演算的理論基礎,進行開發的。
SQL基于關系演算,是一種非過程化的查詢語言(回憶一下關系演算:用布爾公式表述結果應該滿足的條件)【數據庫筆記——數據模型_劉文巾的博客-CSDN博客】
***需要注意一點,查詢語言不是編程語言。因為它不是圖靈完備的,沒有邏輯編程能力。
QL(query language)只支持數據的查詢,不能編程(如果要編程,就需要和別的語言結合、嵌入。目前常用方法是將數據庫語言嵌入到一種高級程序設計語言中【如C,后文會講】。這種高級程序設計語言【C語言】稱為數據庫語言的宿主語言。)
1.3 SQL的四個子語言
數據定義語言 Data Definition Language (DDL)
????????用來定義、刪除、修改數據模式(表、視圖。。。)
查詢語言 Query Language
????????重中之重,就是select語句及其各種子句
數據操縱語言Data Manipulation Language (DML)
????????插入、刪除、更新數據庫中的數據
數據控制語言Data Control Language (DCL)
????????控制、授權用戶對數據的訪問權限
2 SQL
2.1 SQL中的基本概念
基表:一個基表就是真實存在磁盤上的一個關系數據結構
視圖:又叫作虛表,根據基表通過映射和計算得到的虛表。不是真實存儲在磁盤上的數據,邏輯意義上的表
主鍵primary key? 外鍵 foreign key: 同數據模型
2.2 SQL中的保留字
NULL——空值,由于引入了NULL,所以SQL是三值邏輯而不是二值邏輯(真、假、不知道)
UNIQUE——創建表的時候,說明一個屬性值是否允許重復值
DEFAULT——為數據庫某一張表的某一個屬性值指定缺省值
CHECK——定義一張表的時候,我們會定義一些完整性約束。之后向數據庫插入數據的時候,系統會check這個要插入的數據是否符合約束條件
2.3 后文大部分SQL案例使用的數據表格
依舊是水手信息S1 S2、船只信息B1 、船只預定信息R1
2.4 SQL基本架構
target——目標列表
distinct——可缺省,加了之后,要求系統對查詢結果消除重復元素,如果不加這個關鍵字的話,系統不會主動消除重復元素【這個類似于上一章的投影,做投影的時候不會主動刪除重復元素,用戶要求(比如加distinct關鍵字)后,才會刪除】
from——查詢所涉及的表
qualification——查詢結果需要滿足的布爾表達式
聯想到關系演算(數據庫筆記——數據模型_劉文巾的博客-CSDN博客):
target-list就是關系演算定義中 '|'之前的部分.
relation-list就是這個元組應該要屬于的某個關系
qualifications就是關系演算定義中 '|'后面的部分
2.5 系統執行一條SQL語句的概念性方法
這是一種naive的實現方法,實際的數據庫會根據所存儲和所操作的數據,以及不同的應用而改變一些流程
1 ) 計算所有在relation-list中,也就是本次查詢涉及到的表的笛卡爾乘積
2 ) 刪去所有不滿足qualifications的元組
3 ) 刪去所有不在target-list中的屬性
4 )? 如果有distinct關鍵字,那么就刪除所有重復的元組
3 簡單的SQL例子
3.1 兩張表聯立
比如我們要找定了103號船的水手的名字
這時候需要水手信息的表格S和預定船信息的表格R聯立。
讓兩個表聯系起來的條件是S.sid=R.sid
這里的S和R是數據庫的別名。
SELECT S.sname FROM Sailors S, Reserves R WHERE S.sid=R.sid AND R.bid=103按照前面naive的思路。我們進行這個SQL查詢的時候,會對R和S先進性笛卡爾乘積。得到一張大表,然后找滿足條件的元組。截取需要的屬性。
3.1.1 別名
在不引起混淆的情況下屬性對應的表格的名字可以不出現
!!!但是出于規范的考慮,還是建議寫上!!!
SELECT S.sname FROM Sailors S, Reserves R WHERE S.sid=R.sid AND bid=103 SELECT sname FROM Sailors, Reserves WHERE Sailors.sid=Reserves.sid AND bid=103以上兩個在語法和語義上也是可以的。但是為了規范起見,還是建議使用3.1中的SQL編程習慣。
3.2 distinct的使用
我們現在需要完成一個這樣的查詢:
我們現在要找一個至少預定了一艘船的水手。
SQL實現如下:
SELECT S.sid FROM Sailors S, Reserves R WHERE S.sid=R.sid如果我們只是要水手的id的話,那么完全可以不用引入S這個表格,這里加上S表格,是和后面的SQL和查詢要求相對應的。
此時,我們在SELECT后面加或者不加DISTINCT關鍵字,在語義上不會有什么異同(因為預定了水手的數量不會因為加了distinct語句而發生什么改變)。
如果我們是要水手的名字呢?直接S.sid改成S.sname就可以了。
此時我們加或者不加DISTINCT關鍵字,有什么區別嗎?有的,如果水手出現重名的話,加了DISTINCT關鍵字之后可能水手的數量就變少了(幾個重名的水手算成了一個人)
3.3 新屬性的命名
SELECT S.age, age1=S.age-5, 2*S.age AS age2 FROM Sailors S WHERE S.sname LIKE ‘B_%B’第一行的后面兩個表達式(“as”和“=”)表示我們查詢的結果所需要填入的屬性名(給計算得到的新屬性賦予名字的兩種方法)
SQL用LIKE關鍵詞進行模糊匹配,_代表匹配一個字符,%代表匹配0個或者更多字符(這里的意思是首位和末尾都是大寫B,且整個字符串至少長度為三個字符的水手名字)
3.4 連接詞的使用
如果我們要找一個水手,預定了一艘紅船或者一艘綠船:
SELECT S.sid FROM Sailors S, Boats B, Reserves R WHERE S.sid=R.sid AND R.bid=B.bid AND (B.color=‘red’ OR B.color=‘green’)如果我們的查詢只是需要水手的id的話,在這里我們只需要Boats和Reserves兩張表就可以了,這里我們加Sailors這張表,是為了和后面的對照SQL相對應。
當然,上面這個查詢我們也可以用Union集合并的方式寫這個查詢
SELECT S.sid FROM Sailors S, Boats B, Reserves R WHERE S.sid=R.sid AND R.bid=B.bid AND B.color=‘red’ UNION SELECT S.sid FROM Sailors S, Boats B, Reserves R WHERE S.sid=R.sid AND R.bid=B.bid AND B.color=‘green’但如果我們要找的是,一個定了一條紅船以及一條綠船的水手呢?
此時我們不能簡單地把上面的"OR"直接換成"AND"。因為一條船只可能為一個顏色,不可能即使紅色又是綠色。
我們可以用自聯結的方式表達這個查詢,Boats和Reserves出現兩次,類似于一個自聯結(Boats和對應的Reserves是靠bid聯系起來的,Reserves之間靠sid相關聯)
SELECT S.sid FROM Sailors S, Boats B1, Reserves R1, Boats B2, Reserves R2 WHERE S.sid=R1.sid AND R1.bid=B1.bid AND S.sid=R2.sid AND R2.bid=B2.bidAND B1.color=‘red’ AND B2.color=‘green’幾張表之間的關聯如下
B1——(bid)——R1——(sid)——R2——(bid)——B2
? ? ? ? ? ? ? ? ? ? ? ? ? ?|-----(sid)----->S
當然,我們也可以用INTERSECT集合交來表示這個查詢(這個就是簡單地把前面的UNION換成INTERSECT就可以了)
SELECT S.sid FROM Sailors S, Boats B, Reserves R WHERE S.sid=R.sid AND R.bid=B.bid AND B.color=‘red’ INTERSECT SELECT S.sid FROM Sailors S, Boats B, Reserves R WHERE S.sid=R.sid AND R.bid=B.bid AND B.color=‘green’3.5 嵌套查詢
3.5.1 互相無關聯的嵌套查詢
在3.1 節中,我們用兩個表之間的相關聯來實現了“找預定了103號船的水手的名字”這一個查詢。
當時我們使用的SQL是這樣的:兩張表格用sid相連接
SELECT S.sname FROM Sailors S, Reserves R WHERE S.sid=R.sid AND R.bid=103我們現在也可以用嵌套來完成這個查詢
SELECT S.sname FROM Sailors S WHERE S.sid IN (SELECT R.sidFROM Reserves RWHERE R.bid=103)嵌套子查詢中的SELECT語句在外層查詢處理之前求解。
我們先在Reserves表內找到預定了103號船的所有水手的id(內層查詢)。
然后在S表查詢這些id對應的水手的名字。
當查詢涉及多個關系時,用嵌套查詢逐次求解層次分明,具有結構程序設計特點。
同時,嵌套查詢的執行效率也比聯接查詢的效率高。(因為連接查詢需要笛卡爾乘積這一步操作)
如果我們要找的是沒有預定103號船的水手,那么我們用NOT IN代替IN即可:
SELECT S.sname FROM Sailors S WHERE S.sid NOT IN (SELECT R.sidFROM Reserves RWHERE R.bid=103)3.5.2 互相關聯的嵌套
下面這個例子是互相關聯的嵌套
SELECT S.sname FROM Sailors S WHERE EXISTS (SELECT *FROM Reserves RWHERE R.bid=103 AND S.sid=R.sid)此時,外循環S的每一個sid,都需要帶入內循環去進行一次查詢,如果滿足內循環的條件,那么這個sid就是一個我們要查詢的內容。
這條嵌套查詢語句和上面無關聯嵌套查詢語句在語義上是等價的。但是上面那個無關聯嵌套查詢語句,內層循環只需要執行一次。而這里外層查詢的S有幾個sid,內層循環就需要進行幾次。效率是很低的。
所以同樣的SQL邏輯,不同的設計方法,效率會大大不同。
3.5.3 嵌套的一個比較繞的例子
怎么表達一個只預定了一次103號船的水手呢?(預定一次的意思是只預定了一天的船)
第一種思路是使用NOT IN 關鍵字
SELECT S.name FROM Sailors S Reserve R WHERE R.sid=S.sid AND R.bid=103 AND S.sid NOT IN ( SELECT R1.sidFROM Reserve R1 WHERE R1.bid=103 AND R1.day <>R.day)外層循環時先找哪些定了103號船的水手
內層循環是什么意思呢?
字面理解是這樣的:從另一張Reserve表中找水手的id,這一條元組中的這個水手也定了103號船。同時預定這條船的日期和外層循環預定了這條船的日期不一樣。
翻譯一下,內層循環就是找至少定了兩次103號船的水手的id集合。
那么外層循環where里面說的是sid NOT IN 內存循環,不在內存循環查詢到的集合中。
也就是說。滿足條件的水手不在“至少定了兩次103號船的水手”集合中,同時這個水手又預定了103號船。
兩個取一個交際,就是只預定了一次103號船的水手的id
第二種思路是使用EXISTS 和NOT EXISTS關鍵字
SELECT S.name FROM Sailors S WHERE EXISTS (SELECT *FROM Resreves R1WHERE R1.bid=103 AND S.sid=R2.sid )ANDNOT EXISTS (SELECT *FROM Resreves R2WHERE R2.bid=103 AND S.sid=R2.sid AND R1.day <> R2.day )思路和第一種思路是一樣的 “預定了103號船”+不在”至少預定了兩次103號船“。這兩個條件相疊加。得到我們要的查詢結果。
3.5.4 關系嵌套+自聯結
比如我們要找只被一個水手預定過的船:
SELECT bid FROM Reserves R1 WHERE bid NOT IN (SELECT bidFROM Reserves R2WHERE R2.sid <> R1.sid)對R1里面的每條記錄,我們把它的sid帶進去
子查詢的意思是 R2中所有 不被這個R1.sid而被其他sid預定的船的編號
不在子查詢中的船,就是只被這個R1.sid預定的船
3.5.5 用嵌套的方法解決 3.4 “同時定紅船以及綠船”的問題
我們再回來看找一個同時選了紅船和綠船的水手這個查詢
之前我們是用自聯結、INTERSECT來表示的
我們現在用嵌套的方式來表達:
SELECT S.sid FROM Sailors S, Boats B, Reserves R WHERE S.sid=R.sid AND R.bid=B.bid AND B.color=‘red’AND S.sid IN (SELECT S2.sidFROM Sailors S2, Boats B2, Reserves R2WHERE S2.sid=R2.sid AND R2.bid=B2.bidAND B2.color=‘green’)外循環的意思是找到所有預約了紅船的水手的id,同時這個id是滿足內循環條件的元組里面的id。
內循環的意思是找到所有預約了綠船的水手的id
3.5.4?第3.4節 “同時定紅船以及綠船”的問題 進一步的思考
在3.4和3.5.5中,我們查找的是滿足條件的水手的id。如果我們需要查的是水手的姓名呢?
首先,自聯結的方法,直接把sid換成sname就可以了,別的都不動
SELECT S.sname FROM Sailors S, Boats B1, Reserves R1, Boats B2, Reserves R2 WHERE S.sid=R1.sid AND R1.bid=B1.bid AND S.sid=R2.sid AND R2.bid=B2.bidAND B1.color=‘red’ AND B2.color=‘green’然后嵌套的方法,也是直接把sid換成sname就可以了
SELECT S.sname FROM Sailors S, Boats B, Reserves R WHERE S.sid=R.sid AND R.bid=B.bid AND B.color=‘red’AND S.sid IN (SELECT S2.sidFROM Sailors S2, Boats B2, Reserves R2WHERE S2.sid=R2.sid AND R2.bid=B2.bidAND B2.color=‘green’)但是對于INTERSECT的實現方法,可以直接將sid換成sname嘛?
這種方法是有一定的風險的。和3.2節加不加DISTINCT關鍵字一樣。如果水手用重名的話,就有可能出現問題(比如一個叫Bob的只訂了紅船,另一個叫Bob的只訂了綠船。但是兩個查詢分別返回了Bob,INTERSECT之后Bob是算在最終的結果里面的)
如果一定要用INTERSECT呢?我想到的辦法就是再加一層INTERSECT:
SELECT SX.sname FROM Sailors SX WHERE SX.sid IN(SELECT S.sidFROM Sailors S, Boats B, Reserves RWHERE S.sid=R.sid AND R.bid=B.bidAND B.color=‘red’INTERSECTSELECT S.sidFROM Sailors S, Boats B, Reserves RWHERE S.sid=R.sid AND R.bid=B.bidAND B.color=‘green’)內層循環就是原來3.4節中的INTERSECT部分
3.5.5 FROM語句的嵌套
這里會用到一丟丟第四節的東西。
我們現在需要找找平均年齡最小的rating。
首先 ,聚集函數不能嵌套,所以以下寫法是錯誤的:
SELECT S.rating FROM Sailors S WHERE S.age = (SELECT MIN (AVG (S2.age))FROM Sailors S2)那么我們應該怎么辦呢?這時候我們可以對FROM語句使用嵌套:
這里用到了第四節GROUPBY的內容
SELECT Temp.rating FROM (SELECT S.rating, AVG (S.age) AS avgageFROM Sailors SGROUP BY S.rating) AS Temp WHERE Temp.avgage = (SELECT MIN (Temp.avgage)FROM Temp)Temp是一個新的表,表的內容是每一個rating和rating對應的平均年齡的一一對應
WHERE中就是找等于最小平均年齡的那一組的rating
3.6 SQL 連接詞
除了 NOT IN, NOT EXISTS, NOT UNIQUE之外,還可以有 op ANY(比某些op) op ALL (比所有op) (op是<,>,=,≤,≥,≠中的一個)
比如我們要找比某些叫Horatio等級高的水手:
SELECT * FROM Sailors S WHERE S.rating > ANY (SELECT S2.ratingFROM Sailors S2WHERE S2.sname=‘Horatio’)子查詢的意思是所有叫Horatio的水手的等級。
如果要找的是比所有叫Horatio等級高的水手,那么就把ANY替換成ALL就可以了,即:
SELECT * FROM Sailors S WHERE S.rating > ALL (SELECT S2.ratingFROM Sailors S2WHERE S2.sname=‘Horatio’)3.7 SQL除法
3.7.1 關系代數除法回憶
回顧一下關系代數中的除法(數據庫筆記——數據模型_劉文巾的博客-CSDN博客)
和關系代數里面用基礎運算符表達除法一樣,我們也可以用”否定之否定“來表達除法
回顧一下關系代數中對于除法的表述(數據庫筆記——數據模型_劉文巾的博客-CSDN博客)
3.7.2 SQL除法舉例
找到預定了所有船的水手
SELECT S.sname FROM Sailors S WHERE NOT EXISTS ((SELECT B.bidFROM Boats B)EXCEPT(SELECT R.bidFROM Reserves RWHERE R.sid=S.sid))子查詢里面 第一個子句:找到Boat里面所有的船的編號
子查詢里面第二個字句? 找到所有當前外查詢遍歷到的sid 預定的船的編號
兩個子句一減,就是 所有當前外查詢遍歷到的sid 沒有預定的船的編號
然后外查詢的條件是NOT EXIST,即沒有 沒有預定的船,也就是所有的船都被預定了。
如果我們不用EXCEPT呢?
SELECT S.sname FROM Sailors S WHERE NOT EXISTS (SELECT B.bidFROM Boats B WHERE NOT EXISTS (SELECT R.bidFROM Reserves RWHERE R.bid=B.bidAND R.sid=S.sid))這個比較復雜,我們一層一層看:
最外層:我們從Sailors數據庫中尋找水手的名字,這些水手不滿足一些條件
中間層:水手不滿足什么條件呢? 從Boats數據庫中找船的id,這些船不滿足一些條件
最里層:這些船不滿足什么條件呢?條件是: 在預約數據集Reserves里面,這個水手定了這艘船
然后我們從里向外分析:
船不滿足”水手定了這艘船“——水手沒有定這艘船
水手不滿足”水手沒有定這艘船“——水手定了所有船
3.8 SQL 排序
ORDER BY 關鍵字用于對結果集按照一個列或者多個列進行排序。
ORDER BY 關鍵字默認按照升序對記錄進行排序。如果需要按照降序對記錄進行排序,可以使用 DESC 關鍵字。
以上面這個表為例
SELECT * FROM Websites ORDER BY alexa?對結果按照alexa排序,結果為:
3.8.2 倒排序
倒排序就是在上面的基礎上,加一個DESC即可
SELECT * FROM Websites ORDER BY alexa DESC?結果為:
3.8.3 多列排序
SELECT * FROM Websites ORDER BY country,alexa?
?上述SQL結果為:
DESC或者ASC只對它緊跟著的第一個列名有效,其他的不受影響
3.9?其他SQL命令
3.9.1? LIMIT
選取頭幾條記錄
SELECT * FROM Students LIMIT 2上面一條語句是從Students這個表中選取頭兩條記錄
LIMIT y 分句表示——?讀取 y 條數據
LIMIT x, y 分句表示——跳過 x 條數據,讀取 y 條數據
LIMIT y offset x 分句表示——?跳過 x 條數據,讀取 y 條數據
LIMIT n?等價于?limit 0,n
3.9.2 TOP PERCENT
選取頭百分之多少的記錄
SELECT TOP 50 PERCENT * FROM Students?上面一條語句是從Students這個表中選取前百分之五十的記錄
3.9.3 MINUS
使用方式和前面的UNION,INTERSECTION一樣,只不過這邊是集合差的意思
3.9.4 IFNULL
IFNULL() 函數用于判斷第一個表達式是否為 NULL,如果為 NULL 則返回第二個參數的值,如果不為 NULL 則返回第一個參數的值。
IFNULL(expression, alt_value)| expression | 必須,要測試是否為NULL的值 |
| alt_value | 必須,expression 表達式為 NULL 時返回的值 |
3.10 SQL連接
本小節需要用到的表:
? ??
3.10.1 (INNER) JOIN
內連接,或等值連接
獲取兩個表中字段匹配關系的記錄
SELECT a.role_id, a.occupation, a.camp, b.mount_name FROM roles a INNER JOIN mount_info b ON a.role_id = b.role_id;對于inner join 這兩個語句是一樣的
SELECT a.role_id, a.occupation, a.camp, b.mount_name FROM roles a, mount_info b WHERE a.role_id = b.role_id;查詢結果為:
3.10.2 LEFT JOIN
?? ??
LEFT JOIN 會讀取左側數據表的全部數據,即使右側表中無對應數據。
SELECT a.role_id, a.occupation, a.camp, b.mount_name FROM roles a LEFT JOIN mount_info b ON a.role_id = b.role_id;?查詢結果為:
3.10.3?RIGHT JOIN
?? ??
RIGHT JOIN 會讀取右側數據表的全部數據,即便左側表無對應數據。
SELECT a.role_id, a.occupation, a.camp, b.mount_name FROM roles a RIGHT JOIN mount_info b ON a.role_id = b.role_id;查詢結果為:
3.10.4 連接語句 ON 和WHERE的區別
參考SQL中的ON和WHERE的區別_liitdar的博客-CSDN博客
以 LEFT JOIN 為例:在使用 LEFT JOIN 時,ON 和 WHERE 過濾條件的區別如下:
——ON 條件是在生成臨時表時使用的條件,它不管 ON 中的條件是否為真,都會返回左邊表中的記錄;
——WHERE 條件是在臨時表已經生成后,對臨時表進行的過濾條件。因為此時已經沒有 LEFT JOIN 的含義(必須返回左側表的記錄)了,所以如果 WHERE 條件不為真的記錄就會被過濾掉。
?
?? ??
以下面的查詢為例
SELECT * FROM roles LEFT JOIN mount_info ON (roles.role_id = mount_info.role_id) WHERE mount_info.mount_name="sheep";查詢結果為
分析一下上述SQL語句的執行過程:
1. 首先,根據ON過濾條件“roles.role_id = mount_info.role_id”(相當于去掉后面的WHERE過濾條件),生成中間表,如下:
2. 然后,針對上面生成的中間表,再根據WHERE過濾條件“mount_info.mount_name="sheep"”,產生最終查詢結果,如下:
?3.10.5 JOIN總結
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ????????????????? ?
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??
3.11 IF
IF(sex = 'f', 'm', 'f')這條語句表示:如果sex是'f'的話,那么返回'm',否則返回'f'
4 聚集計算
聚集計算有以上幾個,分別對應了元組的個數、某一個屬性(不同)值數量、某一個屬性(不同)值的和、某一個屬性(不同)值的平均數、某一個屬性的最大值、某一個屬性的最小值。
注:以count為例,count(*)會去計算有NULL的行。count+字段只考慮 非NULL的部分
4.1 聚集計算舉例說明
4.1.1 水手個數
SELECT COUNT (*) FROM Sailors S4.1.2??叫bob的水手有幾個不同的等級
SELECT COUNT (DISTINCT S.rating) FROM Sailors S WHERE S.sname=‘Bob’4.1.3 級別為10的水手的平均年齡
SELECT AVG (S.age) FROM Sailors S WHERE S.rating=104.1.4 級別為10的水手的不同年齡的平均值
SELECT AVG (DISTINCT S.age) FROM Sailors S WHERE S.rating=104.1.5 級別最高的那些水手的名字
SELECT S.sname FROM Sailors S WHERE S.rating= (SELECT MAX(S2.rating)FROM Sailors S2)內循環是求水手的最高等級
4.2 GROUPING
4.2.1 grouping語句的引出
我們如果需要對數據庫每一個分組計算出一個值,那么我們就需要用到grouping.
GROUP BY子句按列值分組。列值相同的分為一組。當其后有多個列名時,則先按第一列名分組,再按第二列名在組中分組,直到GROUP子句指名的列都具有相同值的基本組。
比如,我們要找每個等級最年輕的水手
首先,我們不知道水手有幾個等級、這幾個等級的值都是多少
其次,就算我們知道了水手的等級,我們也不太好用我們已經介紹了的這些sql語句實現
比如我們知道了rating是從1到10遞增的,我們可以這么寫嗎?
答案是不行的,因為我們在1.2節說過“QL(query language)只支持數據的查詢,不能編程”。SQL語言無法實現for操作。
4.2.2?group by 和having語句格式
其中,規定target-list 中的屬性必須是grouping-list中的子集。
sql要求 在selct字句和having出現的屬性必須是分組屬性集grouping-list中的子集
where語句篩選元組,having語句篩選組
4.2.3 GROUPING語句的邏輯流程
1)先對 from語句里面的relation-list做笛卡爾乘積
2)?where 子句對笛卡爾乘積得到的大表做篩選
3) group by 分組,將group by語句屬性值相等的元組放到同一分組
4) 按照having語句的條件對組做篩選
5)合格的組,按照select做相應的計算,每一個組得到一個結果
4.2.4 grouping 語句舉例
“Find age of the youngest sailor with age ≥ 18, for each rating with at least 2 such sailors”
SELECT S.rating, MIN (S.age) AS minage FROM Sailors S WHERE S.age >= 18 GROUP BY S.rating HAVING COUNT (*) > 1邏輯步驟:
1) 先篩選年齡大于18的水手(where條件)
2)然后對rating排序,然后相同rating一組
3)最后就是having的條件 count(*)>1
Find age of the youngest sailor with age > 18, for each rating with at least 2 sailors (of any age)
剛才的要求是:
“Find age of the youngest sailor with age ≥ 18, for each rating with at least 2 such sailors”
和前一個的區別在于,前一個是18+的人數大于等于2作為組別的篩選條件。現在是組別所有人的人數大于等于2。
這里我們不能像之前那樣直接 having 語句里面count(*) 。因為where已經把小宇等于18的人刪掉了。
這時候我們需要having的嵌套語句:
SELECT S.rating, MIN (S.age) FROM Sailors S WHERE S.age > 18 GROUP BY S.rating HAVING 1 < (SELECT COUNT (*)FROM Sailors S2WHERE S2.rating = S.rating)4.2.4 “是grouping-list的子集”約束條件舉例
? ? ? ? 關系型數據庫不能從語義上檢查判斷selct和having 每一個屬性在每一個分組中值是否單一,所以才規定了“在selct字句和having出現的屬性必須是分組屬性集grouping-list中的子集”這個約束條件。
對每艘紅船,找他的預定次數
For each red boat, find the number of reservations for this boat
SELECT B.bid, COUNT (*) AS scount FROM Boats B, Reserves R WHERE R.bid=B.bid AND B.color=‘red’ GROUP BY B.bid上面這個肯定是對的。但如果B.color='red'不在where語句,二是在Having里面,那么可不可以呢?就是以下這樣的:
SELECT B.bid, COUNT (*) AS scount FROM Boats B, Reserves R WHERE R.bid=B.bid GROUP BY B.bid HAVING B.color=‘red’首先,邏輯上是沒有問題的,但是SQL會報錯
為什么呢?就是前面說的”“在selct字句和having出現的屬性必須是分組屬性集grouping-list中的子集"..HAVING語句中的屬性color不在分組屬性集bid中,所以SQL會自動報錯。哪怕語義邏輯上是正確的。
如果我們一定不能在WHERE中使用color='red'呢?
第一種方法是HAVING中使用嵌套,然后HAVING語句中的屬性值也只是bid
SELECT B.bid, COUNT(*) AS scount FROM Boats B, Reserves R WHERE R.bid=B.bid GROUP BY B.bid HAVING B.bid in SELECT B2.bid FROM BOATS B2WHERE B2.color='red'第二種方法是在GROUP BY中使用color='red'
SELECT B.bid, COUNT(*) AS scount FROM Boats B, Reserves R WHERE R.bid=B.bid GROUP BY B.bid AND B.color='red'5 CAST語句舉例
類似于強制類型轉換
5.1 CAST語句的幾個作用
5.1.1 匹配函數的參數
比如截取字符串,設置首末位置的下標為整數
5.1.2 轉換精度
Decimal (5,0) 表示長度為5位 小數點后0位
5.1.3 對空值賦以類型
假設我們有這樣兩個表:
我們想到一張students和soldier的外并,但我們只能用并操作來完成。
那我們就需要考慮并兼容的問題,并兼容的話就需要解決NULL空值的情況
CREATE VIEW prospects (name, school, service) ASSELECT name, school, CAST(NULL AS Varchar(20))FROM Students UNIONSELECT name, CAST(NULL AS Varchar(20)), serviceFROM Soldiers ;VIEW,視圖,也就是虛表。我們根據查詢結果臨時算出來的。
prospects 括號里面的是視圖的模式。
students和solders各補了一列,以滿足并兼容。
CAST 語句的意思是,把NULL轉換成最長20的字符串。
6?CASE語句
很多數據庫為了節省空間,對屬性進行了編碼。但是用戶使用的時候,看編碼肯定不方便。于是我們需要把編碼再還原成語義信息。這時候就可以用到CASE語句。
我們現在有這樣一個數據庫:
其中status是officer現在的狀態。但是數據庫中為了節省空間,并沒有存儲狀態實際的字符串,而是用數字表示的。
但是用戶看的時候,officer的狀態是數字肯定是不方便的。于是我們需要再用戶使用端再把數字轉換回字符串:
SELECT name, CASE statusWHEN 1 THEN ‘Active Duty’WHEN 2 THEN ‘Reserve’WHEN 3 THEN ‘Special Assignment’WHEN 4 THEN ‘Retired’ELSE ‘Unknown’END AS status FROM Officers ;我們提取name 和status兩個屬性。
其中status屬性我們進行轉換,如果是1,那么在我們新的表中,status為‘active duty’;如果是2,那么在我們新的表中,status為‘Reserve’。。。以此類推。那么此時存在Officers里面的status屬性就不是數字,是字符串了。
我們比較一下以下兩條SQL語句:
SELECT type, CASEWHEN sum(hours_used)>0 THEN sum(accidents)/sum(hours_used)ELSE NULLEND AS accident_rate FROM Machines GROUP BY type;SELECT type, sum(accidents)/sum(hours_used) FROM Machines GROUP BY type HAVING sum(hours_used)>0;表達的語義都是每一組的平均出故障時間比例。
但是不同的是,如果我們的數據里面有沒有使用過的機器(也就是他們sum(hours_used)為0的機器),在第一個SQL查詢結果里面也會出現,值是NULL。
但在第二個SQL查詢里面,就會被HAVING語句篩掉。結果只出現sum(hours_used)>0的部分)
7 子查詢
之前的無關聯嵌套和有關聯嵌套都是子查詢,我們這里系統的整理一下
子查詢一共有三種:
7.1?標量子查詢
凡是可以出現一個值的地方,都能是標量子查詢
比如我們要查詢平均獎金大于平均薪水的部門:
SELECT d.deptname, d.location FROM dept AS d WHERE (SELECT avg(bonus)FORM empWHERE deptno=d.deptno)> (SELECT avg(salary)FORM empWHERE deptno=d.deptno)當然這里也可以grouping來表示
SELECT d.deptname, d.location FROM dept AS d GROUP BY d.deptno HAVING (SELECT avg(bonus)FROM emp WHERE deptnp =d.deptno)>(SELECT avg(salary)FROM emp WHERE deptnp =d.deptno)上面的寫法是正確的,下面是錯誤的
SELECT d.deptname, d.location FROM dept AS d GROUP BY d.deptno HAVING avg(bonus) > avg(salary)錯誤的原因和4.2.4中是一樣的。就是前面說的”“在selct字句和having出現的屬性必須是分組屬性集grouping-list中的子集"..HAVING語句中的兩個屬性不在分組屬性集bid中,所以SQL會自動報錯。哪怕語義邏輯上是正確的。
7.2?表格表達式
表格表達式實際上得到的都是視圖(虛表)
eg?每一年入職的員工平均拿到的錢
SELECT startyear, avg(pay) FROM (SELECT name, salay+bonus AS pay, year(startdate) AS startyearFROM emp) AS emp2 GROUP BY startyear;7.3?公共表表達式
在特別復雜的查詢里面,可能某一種查詢不止出現一次。如果我們每次用到這個表的時候,在SQL里面都寫一次這個表的表達式,也不是不可以,但是得重復計算,效率偏低。
為了提高效率,我們定義一個公共表表達式,在select之前定義、計算一次,得到一張視圖。然后在之后的查詢中直接使用即可。
比如我們要去找拿錢最多的部門:
這時候我們要對表格進行兩次查詢:第一次查詢拿錢最多的這個值,第二次查詢哪個部門拿的錢數的等于這個值
WITH payroll (deptno, totalpay) AS(SELECT deptno, sum(salary)+sum(bonus)FROM empGROUP BY deptno) SELECT deptno FROM payroll WHERE totalpay = (SELECT max(totalpay)FROM payroll);公共表表達式用WITH進行定義
這個公共表表達式的名字叫payroll,定義是AS之后的部分
比如我們要找,平均薪資一組比另一組多一倍以上的部門對
WITH deptavg (deptno, avgsal) AS(SELECT deptno, avg(salary)FROM empGROUP BY deptno) SELECT d1.deptno, d1.avgsal, d2.deptno, d2.avgsal FROM deptavg AS d1, deptavg AS d2 WHERE d1.avgsal>2*d2.avgsal;7.3.1?綜合使用公共表達式和cast語句實現外連接
我們現在有這么兩張表。我們希望能同時找到 這學期所有開的課和對應的老師、這學期不開的課,這學期沒有課的老師這三種信息,也就是兩個表的外連接
WITHinnerjoin(name, rank, subject, enrollment) AS(SELECT t.name, t.rank, c.subject, c.enrollmentFROM teachers AS t, courses AS cWHERE t.name=c.teacher AND c.quarter=‘Fall 96’) ,teacher-only(name, rank) AS(SELECT name, rankFROM teachersEXCEPT ALLSELECT name, rankFROM innerjoin) ,course-only(subject, enrollment) AS(SELECT subject, enrollmentFROM coursesEXCEPT ALLSELECT subject, enrollmentFROM innerjoin)SELECT name, rank, subject, enrollment FROM innerjoinUNION ALLSELECT name, rank,CAST (NULL AS Varchar(20)) AS subject,CAST (NULL AS Integer) AS enrollment FROM teacher-onlyUNION ALLSELECT CAST (NULL AS Varchar(20)) AS name,CAST (NULL AS Varchar(20)) AS rank,subject, enrollment FROM course-only ;interjoin 表示這學期有老師上課的情況
teacher-only 所有老師減去interjoin里面的老師,就是這學期沒有開課的老師?
這里使用except all會比except效率高(如果我們的查詢結果沒有重復,或者重復沒有影響,我們可以使用except all;因為except 會先做一次排序,效率不如except all)
course-only 所有課程減去interjoin里面的課程,就是這學期不開的課
外連接回顧:(數據庫筆記——數據模型_劉文巾的博客-CSDN博客)
8 遞歸查詢
自己的查詢里面使用了自己的定義
8.1 有結束條件的遞歸查詢
比如我們有這樣一個表格,manager表示自己的直接上司。
我們希望找到hoover所管轄的員工中薪資大于100000的人
我們不能直接 where manager='HOOVER’ 來判斷這個人是不是hoover的下屬。因為這樣只能判斷hoover的直接管轄的員工,但是直接管轄的員工還有他們管轄的員工,那些人也是算hoover的下屬的。。。。
這時候我們就需要使用遞歸實現了
WITH agents (name, salary) AS((SELECT name, salary --- initial queryFROM FedEmpWHERE manager=?Hoover?)UNION ALL(SELECT f.name, f.salary --- recursive queryFROM agents AS a, FedEmp AS fWHERE f.manager = a.name))SELECT name --- final query FROM agents WHERE salary>100000 ;agents表格最終達到的效果是hoover所有下屬以及對應的薪水。
initial query指的是hoover的直接下屬。
recursive query的話,就是找現在agents中的員工各自的直接下屬。然后把這些直接下屬放到agents中,再找他們的直接下屬。。。以此循環。直到最底層員工,然后跳出循環。
得到表格后,我們再query就很方便了。
8.2 沒有結束條件的遞歸查詢
我們現在看這樣一個問題
我們有這么幾個機場,幾個機場之間有航班相連。我們現在要找從SFO到JFK 開銷最小的航班坐法
數據可視化之后的結果
SQL中是沒有辦法表達中轉的?
我們需要建立一個臨時表。這個臨時表記錄了從SFO中轉一次、兩次、三次。。??赡艿竭_的目的地。(如下表,記錄了我們做一次、兩次、三次航班后的目的地、路徑、開銷)
WITH trips (destination, route, nsegs, totalcost) AS((SELECT destination, CAST(destination AS varchar(20)), 1, costFROM flights --- initial queryWHERE origin=‘SFO’)UNION ALL(SELECT f.destination, --- recursive queryCAST(t.route||’,’||f.destination AS varchar(20)), t.nsegs+1, t.totalcost+f.costFROM trips t, flights fWHERE t.destination=f.originAND f.destination<>’SFO’ --- stopping rule 1AND f.origin<>’JFK’ --- stopping rule 2AND t.nsegs<=3)) --- stopping rule 3SELECT route, totalcost --- final query FROM trips WHERE destination=‘JFK’ AND totalcost= --- lowest cost rule(SELECT min(totalcost)FROM tripsWHERE destination=‘JFK’) ;||表示字符串連接
我們重點看一下遞歸查找中的結束條件。
首先'<>'表示不等于 也就終點不能是SFO;
然后起點不能是JFK,
以及最多可以坐三次航班(這可以看作“兜底”。哪怕前面思考的不周到,我們也最多遞歸三次)
最終的查詢結果為:
9 數據操縱語言
主要是三個操作
9.1 insert
將一條元組插入數據庫中
VALUE后面是要插入的元組值,其次序 和域應與STUDENT的模式定義一致。
INSERT INTO EMPLOYEES VALUES ('Smith', 'John', '1980-06-10', 'Los Angles', 16, 45000);?把VALUES 后面括號里面的元組插入到INTO后面的數據庫中
9.2 delete
把滿足條件(條件為WHERE后面的語句)的元組刪除
DELETE FROM Person WHERE LastName = 'Rasmussen' ;如果沒有WHERE子句,則刪除指定表中的所有元組,使該表為一空表。(刪除整個表要用 DROP TABLE語句)
9.3 update
把滿足條件(條件為WHERE后面的語句)的元組中的某一些屬性更新(更新方式為SET后面的語句)
UPDATE Person SET Address = 'Zhongshan 23', City = 'Nanjing' WHERE LastName = 'Wilson';9.4 創建新表
比如我們要生成一個學生成績臨時表GRADE,表中包括SNAME,CNO,GRADE三個屬性。
首先定義一個臨時表GRADE:
CREATE TABLE GRADE(SNAME VARCHAR(8) NOT NULL,CNO CHAR(6) NOT NULL,GRADE DEC(4,1) DEFAULT NULL);其次插入有關的數據:
INSERT INTO GRADE SELECT SNAME, CNO, GRADE FROM STUDENT,SC WHERE STUDENT.SNO=SC.SNO10 視圖總結
10.1 普通視圖
是一個虛表,用create view 創建的虛表。
它區別于基表,后者是以某種形式存在磁盤里面的。
普通視圖是在基表的基礎上利用查詢得到的,數據庫中只會記錄他們的定義。
普通視圖可以保證數據的邏輯獨立性(按照功能創建普通視圖)。
普通視圖也能保證數據的安全性(比如用戶可以看到的數據是總數居的一部分,那么給用戶的數據可以是所有屬性的一部分、所有元組的一部分、甚至是加工后的結果。此時可以保證數據的安全性
視圖對應的內容總是實時、最新的內容,并不是視圖定義時對應內容。這是由于基表隨著更新操作其內容在不斷變化,所以視圖對應的內容也在不斷變化。
10.1.1 普通視圖的更新問題。
早期的SQL中是不允許視圖來更新屬性的,當時的視圖是只讀。
但現在的SQL中,只要視圖的屬性可以唯一對應基表中的屬性,那么視圖也是可以更新的。相當于更新的基表中的屬性.
比如上面這樣兩個普通視圖,第一個就可以更新視圖里面的屬性值,而第二個就不行。
10.1.2 普通視圖的撤銷
使用DROP VIEW 來撤銷
DROP VIEW YoungSailor10.2 臨時視圖和遞歸查詢
臨時視圖的定義方式with和普通視圖的create view很像,他們的實現也是類似的。
唯一不同的是,臨時視圖的定義也是臨時的,數據庫并不會存儲他。當查詢語句完成,臨時試圖就被遺棄。
11?嵌入式SQL
我們之前進行的,都是一條查詢語句。而SQL不是編程語言,本身不具備程序設計能力。
那么如果我們要基于數據庫進行應用程序開發,我們就需要SQL和某些編程語言相結合。
在這里,我們主要介紹在C語言中使用嵌入式SQL。
11.1 嵌入式SQL的特點
1)所有嵌入在c里面的SQL命令,都以“EXEC SQL"這個開始,以分號結束。編譯可以用這個來識別這段語句是SQL語言還是C語言
2)使用宿主變量(host variables)在C和數據庫之間傳遞數據和消息。宿主變量需要以”EXEC SQL“為開頭進行定義。
3)在SQL語句中,如果是C語言中的變量,我們需要加上冒號以示區分SQL(數據庫)中的變量屬性
4)在宿主語言(也就是這里的C語言)中,宿主變量正常使用就ok了(和普通變量一樣使用)
5)不能將宿主變量成定義數組或者結構
6)SQL中有一個通訊區SQLCA,可以利用這個數組變量實現C和SQL語言之間信息的交換
7)SQLCA.SQLCODE判斷返回的狀態(查詢結果正常與否)
8)使用短整型 indicator(說明符)表示C里面沒有空值(0還是1)
11.2?宿主變量的定義
EXEC SQL BEGIN DECLARE SECTION; char SNO[7]; char GIVENSNO[7]; char CNO[6]; char GIVENCNO[6]; float GRADE; short GRADEI; /*indicator of GRADE*/ EXEC SQL END DECLARE SECTION;這里面的GRADEI就是前面11.1第8條說的短整型說明符
11.3 連接數據庫
首先要用connect和數據庫建立連接
連接數據庫需要uid用戶標識符和pwd用戶輸入的密碼
連接的時候,用這個用戶名和密碼來訪問數據庫
EXEC SQL CONNECT :uid IDENTIFIED BY :pwd;11.4 將C語言中的值插入到數據庫
EXEC SQL INSERT INTO SC(SNO,CNO,GRADE)VALUES(:SNO, :CNO, :GRADE);將C語言程序中的SNO,CNO,GRADE宿主變量(加冒號的部分)插入到SC數據庫中
11.5?查詢語句
EXEC SQL SELECT GRADEINTO :GRADE :GRADEIFROM SCWHERE SNO=:GIVENSNO AND CNO=:GIVENCNO;查詢的返回結果放到c變量里面去(INTO里面的部分)
11.6 游標
在11.5中,{SNO,CNO}是SC的主鍵。所以查詢的結果只有一條語句。如果查詢的結果是一組元組呢?
這時候我們需要使用游標cursor來處理一般的查詢語句返回的元組集合
定義一個游標,類似于C中執行的SQL語句一樣來定義,for后面的就是查詢語句。
我們把游標看成一個文件,那么對游標的操作就和對文件的是一樣的了(會有一個讀寫指針),需要open,close。
讀寫指針一開始指向第一個元組
fetch語句逐條元組地取每一個值,按照順序賦給宿主變量。每一個宿主變量得到了一個值。
我們用一個循環再去fetch,直到集合中所有的元組的信息都被提取出來了。
那么循環什么時候結束呢?
SQLCA.SQLCODE的值是100的時候,表明查詢結果的結果集處理完了。
下面是一個完整的嵌入式游標操作:(聲明+打開+一條一條讀取+關閉)
EXEC SQL DECLARE C1 CURSOR FORSELECT SNO, GRADEFROM SCWHERE CNO = :GIVENCNO;EXEC SQL OPEN C1;if (SQLCA.SQLCODE<0) exit(1); /* There is error in query*/while (1) { EXEC SQL FETCH C1 INTO :SNO, :GRADE :GRADEIif (SQLCA.SQLCODE==100) break; /* treat data fetched from cursor, omitted*/ ∶ }EXEC SQL CLOSE C1;12 動態嵌入式sql
在之前11小節的嵌入式SQL中,SQL語句都是在編譯之前就已經寫好了。
但是在一些應用中。SQL語句并不能提前寫好(根據用戶的輸入的信息構建sql;換句話說,程序執行之前需要執行什么SQL用戶是不知道的)。他需要在程序運行過程中動態地建立。
12.1?非查詢動態SQL
EXEC SQL BEGIN DECLARE SECTION; char sqlstring[200]; EXEC SQL END DECLARE SECTION;char cond[150]; strcpy( sqlstring, ”DELETE FROM STUDENT WHERE ”);printf(“ Enter search condition :”); scanf(“%s”, cond);strcat( sqlstring, cond);EXEC SQL EXECUTE IMMEDIATE :sqlstring;上面的嵌入式SQL作用是將滿足條件的學生刪除,其中條件是使用者輸入進去的
sqlstring是拼接之后的SQL語句
cond是用戶需要輸入的條件。條件是程序運行的時候,由用戶來決定。
IMMEDIATE表示數據庫系統動態地立即執行sqlstring里面的語句
12.2 有動態參數的嵌入式SQL
先用占位符(place holder)在事先寫好的SQL語句中占一個位置,然后以后填進去
EXEC SQL BEGIN DECLARE SECTION; char sqlstring[200]; int birth_year; EXEC SQL END DECLARE SECTION;strcpy( sqlstring, ”DELETE FROM STUDENT WHERE YEAR(BDATE) <= :y; ”);printf(“ Enter birth year for delete :”); scanf(“%d”, &birth_year);EXEC SQL PREPARE purge FROM :sqlstring;EXEC SQL EXECUTE purge USING :birth_year;這里的:y就是占位符。
PREPARE語句先準備一下要執行的sql語句,此時還是占位符:y。
真正執行的時候EXECUTE,用宿主變量的值替換占位符位置的值。
13 嵌入式SQL存儲過程
允許用戶把公用的SQL語句段定義成一個過程,系統事先經過編譯優化后存儲在數據庫系統里面。將來用戶要用的時候直接調用它。(有點類似于程序語言中的函數)。
這樣可以改善性能,方便開發
對于頻繁同時使用這幾個SQL語句段的用戶來說,下次要用的時候,直接調用存儲過程,不用重新寫語句了。
如果需要修改需求,只要改定義SQL語句段成為過程的那一個地方就可以了,要不然SQL語句段在程序中使用過的地方都需要改。
不用對存儲的語句再編譯(SQL語句段都寫在C程序里面的話,每次進行都要進行預編譯優化,如果存成一個過程的話就只需要在存入DBMS的第一次過程中優化)。
EXEC SQLCREATE PROCEDURE drop_student(IN student_no CHAR(7),OUT message CHAR(30))BEGIN ATOMICDELETE FROM STUDENTWHERE SNO=student_no;DELETE FROM SCWHERE SNO=student_no;SET message=student_no || ’droped’;END; EXEC SQL ∶ CALL drop_student(…); /* call this stored procedure later*/ ∶放在一起連續做的事情構建成一個存儲過程,定義成一個模塊
在上面例子中,drop_student就是這個存儲過程。之后在C語言程序中直接調用這個,就代表了它定義里面的幾條SQL語句了。
存儲過程和函數類似,可以有輸入輸出
in—— 學生的學號
out——返回值,告訴用戶查詢過程是成功還是失敗
ATOMIC表示里面操作為原子操作——要么全部成功,要么一個不做
總結
以上是生活随笔為你收集整理的数据库笔记: SQL的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 文巾解题 LCP 07. 传递信息
- 下一篇: linux~mysql安装、卸载及使用命