目錄
第五章 高級SQL
5.1 使用程序設計語言訪問數據庫
爲什麼需要使得再編程語言中能夠訪問SQL?
- SQL的表達能力弱於通用程序語言
- SQL一個應用系統不僅需要查詢和更新數據庫,還需要進行很多其他的非聲明性操作。
在編程語言中訪問SQL的兩種方式:
- 動態SQL:通用程序設計語言可以通過函數(非過程式語言)或者方法(對於面向對象的語言)來連接數據庫並與之交互。
- JDBC和ODBC是兩個通用的連接到SQL數據庫並執行查詢和更新的標準。
- 嵌入式SQL:嵌入式SQL必須在編譯時全部確定,並交給預處理器。預處理程序提交SQL到數據庫系統進行預編譯和優化,然後它把應用程序中的SQL語句替換成相應的代碼和函數,最後調用程序語言的編譯器進行編譯。
5.1.1 JDBC
JDBC標準:定義了Java程序連接數據庫服務器的應用程序接口(Application program interface,API)。
應用程序連接數據庫的過程:
- 連接到數據庫: getConnection
- 向數據庫發送SQL語句用於執行: createStatement
- 獲取查詢結果: executeQuery/executeUpdate...
- 關閉連接: close
5.1.1.1 連接到數據庫
// 必須在數據庫鏈接之前完成 驅動程序加載
Class.forName("oracle.jdbc.driver.OracleDriver");
// 創建了一個數據庫鏈接的句柄
Connection conn = DriverManager.getConnection(url, username, password);
/*
URL - 指明服務器所在的主機名稱(db.yale.edu);
與數據庫通信所用的協議(jdbc:oralce:thin:);
數據庫系統用來通信的端口號1521;
服務器端使用的特定數據庫(univdb)
username - 指定數據庫用戶標識
password - 指定密碼
*/
5.1.1.2 向數據庫系統傳遞SQL語句
// 通過Statement實例向數據庫發送SQL語句用於執行
Statement stmt = conn.createStatement();
stmt.executeQuery()/executeUpdate().....
5.1.1.3 獲取查詢結果
ResultSet可以接收stmt.executeQuery() 的返回結果。
使用迭代器訪問元組。可以利用屬性名或者屬性位置提取元組的屬性。
注意:執行完之後一定要關閉statement句柄和數據庫連接。
5.1.1.4 預備語句
// 使用PreparedStatement提交SQL用於編譯。
// 注意:其優勢在於防止SQL注入。對設置的參數值中的特殊字符會進行轉義。
PreparedStatement pStmt = conn.prepareStatement("insert into instructor values(?,?,?,?)");
pStmt.setString(1,"8877");
pStmt.setString(2,"Perry");
pStmt.setString(3,"Finance");
pStmt.setInt(4,125000);
pStmt.executeUpdate();
pStmt.setString(1,"8878");
pStmt.executeUpdate();
5.1.1.5 可調用語句
// CallableStatement可以調用SQL的存儲過程和函數
CallableStatement cStmt1 = conn.prepareCall("{?=call some_function(?)}");
CallableStatement cStmt2 = conn.prepareCall("{?=call some_procedure(?,?)}");
5.1.1.6 元數據特性
// ResultSet有一個getMetaData()方法,它可以返回包含結果集元數據的ResultSetMetaData對象。
// ResultSetMetaData進一步包含查找元數據信息的方法,如:結果的列數、特定列的名稱、或者特定列的數據類型。
ResultSetMetaData rsmd = rs.getMetaData();
for(int i = 0;i<rsmd.getColumnCount();i++){
System.out.println(rsmd.getColumnName(i));
System.out.println(rsmd.getColumnTypeName(i));
}
// DatabaseMetaData提供了查找數據庫元數據的機制
// conn.getMetaData()返回DatabaseMetaData對象
// DatabaseMetaData又進一步包括大量方法可以查詢所連接的數據庫和數據庫系統的元數據
DatabaseMetaData dbmd = conn.getMetaData();
ResultSet rs = dbmd.getColumns(null, "univdb","department","%");
//getColumns的參數:類別,模式名,表名,列名
//返回值:每列返回一行,包含一系列屬性,如:COLUMN_NAME,TYPE_NAME
while(rs.next()){
System.out.println(rs.getString("COLUMN_NAME"), rs.getString("TYPE_NAME"));
}
5.1.1.7 其他特性
- 可更新的結果集(updatable result set): 可以從一個在數據庫關係上執行選擇和/或投影操作的查詢中創建一個可更新的結果集。對結果集中的元組的更新將引起數據庫關係中相應元組的更新。
- setAutoCommit(false/true): 打開或者關閉自動提交(即:每個SQL語句作爲一個自動提交的獨立事務對待)。
- 處理大對象的接口:ResultSet提供getBlob(), getClob()返回Blob或者Clob對象。這些對象不存儲整個大對象,而是存儲這些大對象的定位器,即:指向數據庫中實際大對象的邏輯指針。
- 反向存儲大對象的接口:PreparedStatement.setBlob(int parameterIndex, InputStream inputStream)把一個類型爲blob的數據庫列與一個輸入流關聯起來。當預備語句執行時,數據從輸入流被讀取,然後寫入數據庫的二進制大對象中。setClob方法類似。
- 提供行集(row set)特性:允許把結果集打包起來發送給其他應用程序。行集可以向後也可以向前掃描,並且可以被修改。
5.1.2 ODBC
開放數據庫互連(Open DataBase connectivity,ODBC)標準定義了一個API,應用程序用它來連接數據庫,並與之進行交互。
- 創建SQL環境變量,和數據庫連接句柄:SQLAllocEnv(),SQLAllocConnect()
- 建立數據庫連接:SQLConnect()
- 把SQL語句發送到數據庫執行:SQLAllocStmt(),SQLExecDirect()
- 處理結果集合:SQLFetch()遍歷結果集;SQLBindCol()提取屬性值
- 釋放句柄,關閉連接,釋放環境句柄:SQLFreeStmt(), SQLDisconnect(), SQLFreeConnect(), SQLFreeEnv()
其他特性:
- 開啓和關閉自動提交:SQLSetConnectOption(conn, SQL_AUTOCOMMIT, 0)
- 提交事務/回滾事務:SQLTransact(conn, SQL_COMMIT), SQLTransact(conn, SQL_ROLLBACK)
- ODBC定義了符合性級別(conformance level):用於指定標準定義的功能的自己。
- 一個ODBC的實現可以提供核心級特性,也可以以提供更多的高級特性(level1和level2)
- level1需要支持取得目錄的有關信息,如:什麼關係存在,它們屬性的類型等
- level2需要更多的特性,例如發送和提取參數值數組以及檢索有關目錄更詳細信息的能力。
- 一個ODBC的實現可以提供核心級特性,也可以以提供更多的高級特性(level1和level2)
5.1.3 嵌入式SQL
SQL標準定義了可以將SQL嵌入到不同的語言中,如:C++,C,Java等。SQL查詢所嵌入的語言成爲宿主語言,宿主語言中使用的SQL結構被稱爲嵌入式SQL。
嵌入式SQL和動態SQL的區別:
- 一個使用嵌入式SQL的程序在編譯前必須先經過一個特殊的預處理器進行處理。嵌入式的SQL請求被宿主語言的聲明以及允許運行時刻執行數據庫訪問的過程調用所代替。然後,所產生的程序由宿主語言編譯器進行編譯。當使用嵌入式SQL時,一些SQL相關的錯誤可以再編譯時就被發現。
- 而JDBC或者ODBC中,SQL語句是在運行時被解釋的。
// 使預處理器識別嵌入式SQL請求
EXEC SQL <嵌入式SQL語句>;
// 表明預處理器應該在此處插入特殊變量以用於程序和數據庫系統間的通信
EXEC SQL INCLUDE SQLCA;
// 在執行SQL之前,需要連接數據庫
EXEC SQL connect to <server> user <user-name> using <password>;
// 在嵌入的SQL中可以使用宿主語言的變量,但是需要加上“:”,以區別SQL變量
// 這類變量必須在DECLARE SECTIION塊中進行聲明,不過變量聲明的語法取決於宿主語言
EXEC SQL BEGIN DECLARE SECTIION;
int credit_amount;
EXEC SQL END DECLARE SECTIION;
// 聲明遊標(declare cursor),此時並不計算查詢結果,程序必須使用open和fetch語句得到元組
EXEC SQL
declare c cursor for
select ID, name
from student
where tot_cred>:credit_amount;
// 當open子句被執行時,宿主變量credit_amount的值就會被應用到查詢中,
// 數據庫系統會將該語句的執行結果存儲在一個臨時關係中。
EXEC SQL open c;
// 如果查詢及誒過存在錯誤,則數據庫系統會在SQL通信區域(SQLCA——)的變量中存儲一個錯誤診斷信息。
// 利用一系列的fetch子句把結果賦值給宿主語言的變量
// 單條fetch子句每次只能獲得一個元組
// 可以使用while循環,遍歷全部元組
EXEC SQL fetch c into :si,:sn;
// 必須使用close,告訴數據庫系統刪除臨時關係c
EXEC SQL close c;
// insert/update/delete的嵌入式SQL不返回結果
EXEC SQL <update/insert/delete子句>;
// 也可以通過遊標來更新數據庫關係
EXEC SQL
declare c cursor for
select *
from instructor
where dept_name = 'Music'
for update;
// 利用fetch操作進行迭代,每取到一個元組,都執行如下代碼
EXEC SQL
update instructor
set salary = salary+100
where current of c;
// 提交事務
EXEC SQL COMMIT;
//回滾事務
EXEC SQL ROLLBACK;
SQLJ:Java的嵌入式SQL。SQLJ與其他嵌入式SQL語法不通,使用了#sql代替EXEC SQL標識,並且不使用遊標,而是使用Java的迭代器接口來獲得查詢結果。
5.2 函數與過程
5.2.1 聲明和調用SQL函數和過程
// 定義一個普通函數
create function dept_count(dept_name varchar(20))
return integer
begin
declare d_count inetger;
select count(*) into d_count
from instructor
where instructor.dept_name = dept_name
return d_count;
end
// 定義一個表函數:返回值是表的函數
create function instructor_of(dept_name varchar(20))
return table(
ID varchar(5),
name varchar(20),
dept_name varchar(20),
salary numberic(8,2))
return table(
select ID, name, dept_name, salary
from instructor
where instructor.dept_name = instructor_of.dept_name);
// 定義一個過程
// SQL允許多個過程同名,只要同名過程的參數個數不同。名稱和參數個數用於表示一個過程。
// SQL也允許多個韓樹同名,只要這些函數的參數個數不同,或者參數類型不同。
create procedure dept_count_proc(int dept_name varchar(20), out d_count integer)
begin
select count(*) into d_count
from instructor
where instructor.dept_name = dept_count_proc.dept_name
end
// 調用過程
declare d_count integer
call dept_count_proc('Physics',d_count);
5.2.2 支持過程和函數的語言構造
持久存儲模塊(Persistent Storage Module, PSM):
// declare用來聲明;set用來賦值
// 複合語句
begin...end
//確保其中包含的所有語句作爲單一的事務執行
begin atomic ... end
// while, repeat,for語句
while <布爾表達式> do
語句序列;
end while
repeat
語句序列;
until <布爾表達式>
end repeat
declare n integer default 0;
for r as
select budget from department;
do
set n = n - r.budget
end for
// if else
if <布爾表達式>
then 語句/符合語句
elseif <布爾表達式>
then 語句/符合語句
else
語句/符合語句
end if
// case語句
// 異常處理:聲明異常條件和句柄(handler)來處理異常
declare out_of_classroom_seats condition
declare exit handler for out_of_classroom_sears
begin
sequence of statements
end
// begin...end中間可以用signal out_of_classroom_seats觸發異常
5.2.3 外部語言過程
SQL允許使用程序設計語言定義函數和過程,如:C++,java。這種方式定義的函數和過程會比SQL中定義的函數高效,並執行一些SQL中無法實現的計算。
外部過程和函數可以這樣指定:
create procedure dept_count_proc(in dept_name varchar(20), out count integer)
language C
external name '/usr/avi/bin/dept_count_proc'
create function dept_count_proc(dept_name varchar(20))
return integer
language C
external name '/usr/avi/bin/dept_count'
外部語言需要處理參數(in和out參數),已經返回值中的控制,還需要傳遞操作失敗/成功的狀態,以方便對異常進行處理。可用如下參數表示:
- 一個指明失敗/成功狀態的sqlstate值
- 一個存儲函數返回值的參數
- 一些指明每個參數/函數結果的值是否爲空的指示器變量
具體如何處理,取決於不同數據庫系統的具體實現。
如果函數不想關注這些問題,可以聲明的時候添加:
// 指明外部過程或函數只使用說明的變量,而不處理空值和異常
parameter style general
- 用程序設計語言定義並在數據庫系統之外編譯的函數可以由數據庫系統代碼來加載和執行。不過這麼做會引入一些危險:程序中的錯誤可能破壞數據庫內部的結構,並繞過數據庫系統的訪問-控制功能。若系統關心執行的效率勝過安全性,則可以採用這種方式執行過程。
- 若系統關心安全性勝過效率,則可以將這些代碼作爲一個單獨進程的一部分來執行,通過進程間通信,傳入參數的值,取回結果。但是代價很高。
- 如果用Java/C#這種”安全“的語言書寫,則可能會存在第三種可能:在數據庫進程本身的沙盒(sandbox)中執行代碼。沙盒允許java/C#訪問它的內存區域,但阻止代碼直接在查詢執行過程的內存中做任何讀操作或者更新操作,或者訪問文件系統中的文件。(C語言無法創建沙盒,因爲可以通過指針不加限制的訪問內存)
Oracle支持外部語言例程在查詢執行過程中的沙盒運行,允許java作爲數據庫過程中的一部分運行。
5.3 觸發器
觸發器(trigger)是一條語句,當數據庫修改時,它自動被系統執行。要設置觸發器必須滿足以下兩個條件:
- 指明什麼條件下執行觸發器:引起觸發器檢測的事件+觸發器執行的條件
- 指明觸發器執行的動作
5.3.1 對觸發器的需求
- 可以用來實現未被SQL約束機制指定的某些完整性約束
- 可以用來設置滿足特定條件時對用戶發出警報或自動開始執行某項任務
- 例如:某種商品庫存低於最小值時,自動發出一個訂單
注意:觸發器不能執行數據庫以外的更新,上述例子中是在訂購表中添加一條記錄,而不是直接去外界下單。
5.3.2 SQL中的觸發器
本節介紹SQL標準定義的語法,但是實際的數據庫系統在實現觸發器時不完全遵照該標準。
觸發器可以在事件(insert, delete, update)之前或者之後觸發。
// referencing new row as nrow - 創建一個過渡變量(transition variable)
// for each row - 顯式的在每一個指定的行進行迭代
// when 語句 - 指定一個條件,系統僅對滿足條件的元組執行觸發器的其餘部分
// begin...end - 將多行SQL作爲一個複合語句進行執行
// 案例:檢查插入時的完整性
create trigger timeslot_check1 after insert on section
referencing new row as nrow
for each row
when (nrow.time_slot_id not in (
select time_slot_id
from time_slot /*time_slot中不存在該time_slot_id*/
))
begin
rollback
end;
// 案例:檢查刪除時的完整性
// 被刪除的元組的time_slot_id要麼還在time_slot中,要麼不在section中,否則將違背參照完整性
create trigger timeslot_check2 after delete on time_slot
referencing old row as orow
for each row
when (orow.time_slot_id not in (
select time_slot_id
from time_slot /*time_slot剛剛被刪除的time_slot_id*/
)
and orow.time_slot_id in (
select time_slot_id
from section) /*在section中仍然含有該time_slot_id的引用*/
)
begin
rollback
end;
// 案例:檢查更新
// after update of takes on grade -- 可以指定只在更新特定屬性時被觸發 更新takes中的grade屬性時纔出發
// 當takes中元組的grade屬性被更新時,需要用觸發器維護student元組的tot_cred屬性,使其保持實時更新
// 當grade從空或者‘F’被更新爲代表課程已經完成的具體分數時,觸發器才被觸發
create trigger credits_earned after update of takes on (grade)
referencing new row as nrow
referencing old row as orow
for each row
when nrow.grade<>'F' and nrow.grade is not null
and (orow.grade='F' or orow.grade is null)
begin atomic
update student
set tot_cred = tot_cred + (select credits from course where course.course_id = nrow.course_id)
where student.id = nrow.id;
end;
// 案例:使用set來更改插入值
create trigger setnull before update of takes
referencing new row as nrow
for each row
when (nrow.grade='')
begin atomic
set nrow.grade = null;
end;
// 其他:
// for each statement - 我們還可以對引起插入、刪除或者更新的SQL語句執行操作,而不是對被影響的行操作
// 過渡表(transition table) - referencing old table as / referencing new table as
// 過渡表只能用於after觸發器,不能用於before觸發器
// 設置觸發器無效 - 取決於數據系統的實現
alter trigger trigger_name disable;
// 刪除觸發器
drop trigger trigger_name;
5.3.3 何時不用觸發器
觸發器有很多合適的用途,但是也有很多不適合的情況。觸發器很好用,但是如果有其他解決方法時儘量避免觸發器。
- 用觸發器替代on delete cascade ---- 缺點:會導致大量的工作量
- 用觸發器維護物化視圖 ---- 缺點:但是現在許多數據庫能夠自動維護物化視圖
- 通過觸發器複製或備份數據庫: 當每個關係插入、更新或者刪除上時設置觸發器,將變更記錄存儲在change或delta的關係上,再通過一個單獨的進程將這些改變的數據複製到數據庫的副本中。 ---- 缺點:現在的數據庫提供內置的數據庫複製工具,不需要手工操作
- 觸發器的非故意執行 - 例如:
- 當數據從一個備份的拷貝中加載,或者一個站點上的數據庫更新複製到備份站點時,觸發器非故意執行。在該種情況下,觸發器已經執行過了,不需要再次執行。因此,應該在加載數據時觸發器應當被設置爲無效。等到備份結束後,再設置爲有效
- 作爲取代的方法,
- 有些數據庫系統支持觸發器定義爲 not for replication, 保證不會在備份站點執行。
- 有些數據庫提供系統變量指明該數據庫是一個副本,不需要觸發觸發器。
- 還應該避免觸發器的級聯觸發,避免無限循環觸發鏈。
5.4 遞歸查詢
舉例: CS301是CS347的先修課程,並且CS201是CS301的先修課,CS101是CS201的先修課程,那麼CS101,CS201和CS301都是CS347的先修課程。
關係的prereq的傳遞閉包(transitive closure):是一個包含所有(cid,pre)對的關係,pre是cid的一個直接 或者間接的先修課程。
有很多要求計算與此類似的層次(hierarchy)的傳遞閉包的應用,如:自行車中的所有部件(自行車-輪子+車身,輪子-輪骨+輪胎....,如此類推)
5.4.1 用迭代來計算傳遞閉包
// create temporary table table_name 創建臨時表,這些表僅在執行查詢的事務內部纔可用,事務結束後會被刪除
// repeat循環
// 計算給定課程cid的全部先修課程,包括直接和間接
// 注意:關係prerequisite(course_id, prereq_id)已經存在,並指明哪一門課是另一門課的直接先修課程
create funciton findAllPrereqs(cid varchar(8))
return table(course_id varchar(98))
begin
create temporary table c_prereq(course_id varchar(8)) -- 用來存儲要返回的課程集合
create temporary table new_c_prereq(course_id varchar(8)) -- 用來存上一次迭代發現的全部先修課程
create temporary table temp(course_id varchar(8)) -- 存儲中間結果
// 查找當前cid的全部直接先修課程,並插入new_c_prereq
insert into new_c_prereq (select prereq_id from prereq where course_id = cid);
// 循環
repeat
// 將new_c_prereq插入c_prereq
insert into c_prereq (select course_id from new_c_prereq);
// 查找new_c_prereq表中所有課程的直接先修課程, 並去除那些已經在在c_prereq存在的先修課程
// 將結果插入到temp
insert into temp (
(select prereq_id from new_c_prereq, prereq where new_c_prereq.course_id = prereq.course_id)
except
(select course_id from c_prereq) --except可以避免環的出現 a->b,b->c,c->a
);
// 更新new_c_prereq
delete from new_c_prereq;
insert into new_c_prereq (select * from temp);
// 更新temp
delete from temp;
until not exists(select * from new_c_prereq); -- 直到找不到新的先修課程,則循環終止
end repeat;
return table c_prereq;
end
5.4.2 SQL中的遞歸
用遞歸視圖定義傳遞閉包.
SQL標準用with recursive子句來支持有限形式的遞歸,在遞歸中一個視圖(或臨時視圖)用自身表達自身
create recursive view - 可以創建永久的遞歸視圖
任何遞歸視圖都必須定義爲兩個子查詢的並:
- 一個非遞歸的基查詢(base query)
- 一個使用遞歸視圖的遞歸查詢(recursive query)
可以理解爲:
- 首先計算基查詢並把所有結果元組添加到視圖關係(初始爲空)中;
- 然後用當前視圖關係中的元組計算遞歸查詢,並把所有結果元組加到視圖關係中。
- 重複上述步驟,知道沒有新的元組添加到視圖關係爲止。
得到的視圖關係實例就稱爲遞歸視圖定義中的不動點(fixed point)。
/*
CS247課程的先修課程:
- CS247的先修課程
-CS247的(直接和間接)先修課程的先修課程 ----這是個遞歸過程
*/
with recursive rec_preqreq(course_id, prereq_id) as (
select course_id, prereq_id
from prereq
union
select rec_prereq.course_id, prereq.prereq_id
from rec_prereq, prereq
where rec_prereq.prereq_id = prereq.course_id
)
select * from rec_preqreq;
遞歸視圖中的遞歸查詢是有一些限制的:該遞歸查詢必須是單調的(monotonic),即:如果視圖關係實例V1是實例V2的超集,那麼它在V1上的結果必須是它在V2上結果的超集。
遞歸查詢不能用於下列場景,因爲它們會導致非單調
- 遞歸視圖上的聚集
- 在遞歸視圖的子查詢上的not exists語句
- 右端使用遞歸視圖的集合差except運算
- 如:遞歸查詢是r-v,其中v是遞歸視圖,那麼我們在v中添加一個元組,會導致查詢結果變小,破壞單調性。
==》 只要遞歸查詢是單調的,遞歸視圖的含義就可以用迭代過程定義;如果遞歸查詢時非單調的,那麼就很難確定視圖的含義
5.5 高級聚集特性
5.5.1 排名
計算排名,分位數等查詢可以用SQL完成,但是會比較複雜。通常編程人員會藉助SQL+程序設計語言共同實現。
本節,只介紹如何用SQL實現。
/*
rank()函數:對order by屬性上所有具有相同值的元組賦予相同的排名:
例如:A和B都具有最高的GAP,則A和B的名詞都是1,C具有次高的GPA,C的名次爲3
dense_rank()函數:與rank()函數類似,不過它不在等級排序中產生隔斷。
例如:A和B都具有最高的GAP,則A和B的名詞都是1,C具有次高的GPA,C的名次爲2
*/
// student_grades(ID, GPA) - 給出每個學生的平均績點,
// 計算GPA排名 - 但是不排序
select ID rank(), over (order by GPA desc) as s_rank
from student_grades;
// 計算GPA排名 - 排序
select ID, rank() over (order by GPA desc) as s_rank
from student_grades
order by s_rank;
// 等價於下面的實現 - 但是下面實現的缺點是計算代價隨着關係的大小線性增長
select ID, (1 + (select count(*) from student_grades B where B.GPA > A.GPA)) as s_rank
from student_grades A
order by s_rank;
// 分區 partition by
// dept_grades(ID, dept_name, GPA)
// 計算每個學生在各自系的排名
select ID, dept_name, rank() over (partition by dept_name order by GPA desc) as dept_rank
from dept_grades
order by dept_name, dept_rank;
//注意:
一個select語句中可以使用多個rank函數
當rank(可能帶有分區)與group by同時出現時,group by先執行,分區和排名在group by的結果上執行。
// limit子句 - limit 10,獲取前10個 // 注意:limit子句不支持分區
select ID, GPA from student_grades order by GPA limit 10;
// 一些其他函數
percent_rank: 以分數的方式給出元組的名詞。(r-1)/(n-1) - 其中r是元組的名詞,n爲分區中包含的元組的個數。如果分區中只有一個元組,則定義爲null。
cume_dist: 累積分佈的簡寫, p/n。其中p是分區中排序值小於或等於該元祖排序值的元組數,n爲分區中包含的元組的個數。
row_number: 對行進行排序,並且按照行在排序中所處的位置給每行一個唯一行號,具有相同排序的不同行將按照非確定的方式得到不同的行號
ntile(n): 按照給定的順去取的每個分區中的元組,並把他們分成n個具有相同元祖數目的桶 -=>可用於構造基於百分比的直方圖
select ID, ntile(4) over (order by GPA desc) as quartile from student_grades;
nulls first/nulls last: 指定空值的位置
select ID, rank() over(order by GPA desc nulls last) as s_rankfrom student_grades;
5.5.2 分窗
窗口查詢用來對一定範圍內的元組計算聚集函數。如:趨勢分析,股票市場的趨勢分析等。
// preceding, following, unbounded preceding, unbounded following
// range between ... and ...
// tot_credits(year, num_credits) - 給出每年學生選課的總學分
// 計算每年中[當前年-2,當前年]窗口的平均總學分
select year, avg(num_credits) over (order by year rows 3 preceding) as avg_total_credits
from tot_credits;
// 計算每年中 當前年之間的所有年 的窗口的平均總學分
select year, avg(num_credits) over (order by year rows unbounded preceding) as avg_total_credits
from tot_credits;
select year, avg(num_credits) over (order by year rows between 3 preceding and 2 following) as avg_total_credits
from tot_credits;
// [當前年-4,當前年],包括邊界
select year, avg(num_credits) over (order by year rows between year-4 and year) as avg_total_credits
from tot_credits;
// 分區+分窗
// tot_credits_dept(dept_name, year, num_credits)
select dept_name, year,
avg(num_credits) over (partition by dept_name order by year rows between 3 preceding and current row) as avg_total_credits
from tot_credits_dept;
5.6 OLAP
聯機分析處理系統(OLAP)是一個交互式系統,它允許分析人員查看多維數據的不同種類的彙總數據。
聯機 - 指的是針對數據分析人員提出的新的彙總數據的請求,能夠幾秒鐘之內在線得到響應,而無需等待很長時間。
5.6.1 聯機處理分析
sales(item_name, color, clothes_size, quantity)
- item_name: skirt, dress, shirt, pants
- color: white, dark, pastel
- clothes size: small, medium, large
- quantity: 整數值,表示商品的數量
統計分析需要對多個屬性進行分組。
度量屬性(measure attribute): 這些屬性可以用於度量某個值,並在其上做聚集操作。例如:quantity屬性就可以看成度量屬性。
維屬性(dimension attribute):維屬性定義了度量屬性以及度量屬性的彙總在這些維屬性上進行觀察的各個維度。例如 :item_name,color,clothes size就是維屬性。
交叉表(cross-tablulation/cross-tab): 一個交車表是從一個關係(如R)中導出的,由關係的一個屬性(A)的值構成其行表頭,另一個屬性(B)的值構成其列表頭。
- 交叉表還可已包含彙總行和彙總列
轉軸表(pivot-table):交叉表也叫轉軸表。
數據立方體(data cube):將二維的交叉表推廣到n維,可視作一個數據立方體。如圖5-18所示,該立方體的大小爲(3+1)*(4+1)*(3+1) = 80,爲每個屬性可能的屬性取值個數加一的乘積 (加一是因爲增加了all)。
- 每個交叉表是一個多維數據立方體的二維視圖。數據分析人員可以交互的選擇交叉表的屬性進行查看,改變交叉表中的維的操作叫做轉軸(pivoting)。
- OLAP系統 允許對一個固定的clothes_size值來查看一個 在item_name和color上的交叉表。這樣的操作叫做切片(slicing)。該操作有時也可以叫做切塊(dicing)。
- 上卷(rollup)和下鑽(drill down):
- 由較細粒度到較粗粒度的 操作叫做上卷
- 由較粗粒度到較細粒度的操作叫做下鑽。
- 一個屬性的不同的細節層次可以組織爲一個層次結構(hierarchy),如:region-country-state-city。分析人員可以以沿着層次結構進行上卷和下鑽。
all: 表示當前屬性的全部的值。SQL標準使用null值替代all。
5.6.2 交叉表和關係表
交叉表與數據庫的關係表的區別:
- 交叉表的列的數目依賴於書籍的數據。數據值的變化可能導致增加更多的列。
OLAP的實現
- OLAP的類型:
- 多維OLAP(Multi地mentional OLAP, MOLAP)系統:使用內存中的多維數組存儲數據立方體
- 關係OLAP(Relational OLAP,ROLAP)系統:使用關係數據庫存儲彙總和基表數據
- 混合OLAP(Hybrid OLAP,HOLAP)系統:複合系統將一些彙總數據存在內存中,基表數據和另一些彙總數據存儲到關係數據庫中。
許多OLAP系統實現爲空戶-服務繫系統。服務器端包含關係數據庫和MOLAP數據立方體。客戶端系統通過與服務器通信獲得數據的視圖。
5.6.3 SQL中的OLAP
pivot子句; 在group by子句中使用rollup和cube操作;
- 案例一:pivot子句
// sales(item_name, color,clothes_size, quantity)
select *
from sales
pivot (
sum(quantity)
for color in ('dark','pastel','white) -- 指定color屬性中的哪些值可以再轉軸的結果中作爲屬性名出現
)
order by item_name;
輸出結果:案例二:cube操作
select item_name, color, clothes_size, sum(quantity)
from sales
group by cube(item_name, color, clothes_size);
/*
此時,生成的(item_name, color,null,sum(quantity)) 表示在該item_name,color下全部的clothes_size的聚集。用null表示了之前介紹的all。
group by cube(item_name, color, clothes_size)操作:
會在{(item_name,color,clothes_size),(item_name,color),(item_name,clothes_size),(color, clothes_size),(item_name),
(color),(clothes_size),()}這些上做group by操作,產生八個分組。
*/
// 爲了便於理解,還可以用DECODE函數 將null替換成all : 下圖僅對item_name和color作爲維度屬性
// decode (value, match-1, replacement-1, match-2, replacement-2,..., match-n, replacement-n, default-replacement);
// grouping函數,當參數是cube或rollup產生的null值時,它將返回1,否則返回0
select decode(grouping(item_name),1,'all',item_name) as item_name,
decode(grouping(color),1,'all',color) as color,
sum(quantity) as quantity
from sales
group by cube(item_name, color);
第二個sql的輸出結果就是:
案例三:rollup操作
/*
rollup操作與cube操作類似,只是產生的結果會少於cube。rollup中屬性的順序會影響計算結果
group by rollup(item_name,color,clothes_size) :
僅會在{(item_name,color,clothes_size), (item_name, color), (item_name),()}上做group_by運算來產生四個分組。
應用場景:對類似(region,country,state,city)這種層次結構上的數據的下鑽和上卷非常有用。
*/
/*多個rollup和cube可以再一個單獨的group by中使用
下面的SQL可以產生如下分組:
{(item_name, color, clothes_size),(item_name,color),(item_name),(color,clothes_size),(color),()}
這是因爲:
rollup(item_name) ==> {(item_name),()}
rollup(color, clothes_size) ==> {(color, clothes_size),(color),()}
二者取笛卡爾積,就得到了這個結果。
*/
select item_name, color, clothes_size, sum(quantity)
from sales
group by rollup(item_name),rollup(color,clothes_size);
//注意:我們也可以使用having來限制rollup和cube的分組的產生
總結
- SQL查詢可以從宿主語言通過嵌入和動態SQL激發。ODBC和JDBC標準給C、Java等語言的應用程序定義了接入SQL數據庫的應用程序接口。程序員可以通過這些API訪問數據庫
- 函數和過程可以用SQL提供的過程擴展來定義,它允許迭代和條件(if-then-else)語句
- 觸發器定義了當某個事件發生而且滿足相應條件時自動執行的動作。觸發器有很多用處,例如:實現業務規則,審計日誌,甚至執行數據庫系統外的操作。大多數數據庫都支持觸發器。
- 一些查詢,如:傳遞閉包,既可以用迭代表示,也可以通過遞歸SQL查詢表示。遞歸可以用遞歸視圖,或者用遞歸的with子句定義
- SQL支持高級的聚集特性,如:排名和分窗查詢
- OLAP工具幫助分析人員從不同的維度查看彙總數據:
- OLAP工具工作在以維屬性和度量屬性爲特性的多維數據之上
- 數據立方體由以不同方式彙總的多維數據構成。預先計算數據立方體有助於提高彙總數據的查詢速度
- 交叉表的顯示允許用戶一次查看多維數據的兩個維及其彙總數據
- 上卷、下鑽、切片和切塊是用戶使用OLAP工具時常用的一些操作
- 從SQL-1999標準開始,SQL提供了cube和rollup操作符,有些系統還支持了pivot子句。