两种在数据库中执行程序代码的方法:一种是通过扩展SQL语言来支持程序的操作;另一种是在数据库中执行程序语言中定义的函数。
5.1 使用程序设计语言访问数据库
数据库程序员必须能够使用通用程序设计语言的原因:
- SQL没有提供通用程序设计语言那样的表达能力
- 非声明性的动作(例如打印一份报告、和用户交互,或者把一次查询的结果送到一个图形用户界面)都不能用SQL实现
通用编程语言中访问SQL有两种方法:
- 动态SQL,在运行时以字符串形式构建SQL查询,提交查询,然后把结果存储程序变量中
- 嵌入式SQL,嵌入式SQL语句必须在编译时全部确定,并交给预处理器。预处理程序提交SQL语句到数据库系统进行预编译和优化,然后它把应用程序中的SQL语句替换成响应的代码和函数,最后调用程序语言的编译器进行编译。
5.1.1 JDBC
Java数据库连接(Java DataBase Connectivity)
JDBC标准定义了Java程序连接数据库服务器的应用程序接口
5.1.1.1 连接到数据库
DriverManager
GetConnection
5.1.1.2 向数据库系统中传递SQL语句
Statment
executeQuery 查询语句
executeUpdate 非查询语句/更新、插入、删除、创建
5.1.1.3 获取查询结果
ResultSet
next方法
程序结束时语句和连接都将被关闭
5.1.1.4 预备语句
通过“?”来代表以后再给出的实际值,而创建一个预备语句。数据库系统在准备了查询语句的时候对他进行编译。在每次执行该语句时应用新值进行查询。
Connection类的prepareStatement方法来提交SQL语句进行编译。
预备语句的优势:
- 在同一查询编译一次然后设置不同的参数值执行多次的情况下,预备语句使得执行更加高效。
- 用户输入的某些特殊字符,例如一个单引号,除非采取额外工作对用户输入进行检查,否则生成的SQL语句会出现语法错误。setString方法自动完成检查,并插入需要的转移字符,以确保语法正确性。同时也可以防止**SQL注入(SQL injection)**窃取数据或损坏数据库。
举例:
"select * from instructor where name =' " + name + "'"
如果name输入
X' or 'Y' = 'Y
产生的语句变成
"select * from instructor where name = ' " + "X' or 'Y' = 'Y" + ","
即
select * from instructor where name = 'X' or 'Y' = 'Y'
在生成的查询中,where子句总是真,所以查询结果返回整个instructor关系。使用预备语句可以防止这类问题,因为输入的字符串将被插入转移字符。
"select * from instructor where name = 'X\ ' or \ 'Y\ ' = \'Y"
这是无害的查询语句,返回结果为空集
5.1.1.5 可调用语句
JDBC提供了CallableStatement接口来允许调用SQL的存储过程和函数
CallableStatement cStmt1 = conn.prepareCall("!? = call some_function(?)|");
CallableStatement cStmt2 = conn.prepareCall("| call some_procedure(?,?)");
函数返回值和过程的对外参数的数据类型必须先用方法registerOutParameter()注册
5.1.1.6 元数据特性
查询结果封装在一个ResultSet对象中。接口ResultSet有一个getMetaData()方法,发挥一个包含结果集元数据的ResultSetMetaData对象。ResultSetMetaData进一步又包含查找元数据信息的方法,列入结果的雷叔,某个特定列的名称,或者谋改革特定列的数据类型。
ResultSetMetaData rsmd = rs.getMetaData();
for (init i = 0; i <= rsmd.getColumnCount(); i++) {
System.out.println(rsmd.getColumnName());
System.out.println(rsmd.getColumnTypeName());
}
循环从1开始是为了和JDBC的管理一致
DatabaseMetaData接口提供了查询数据库元数据的机制,接口Connection包含一个getMetaData的方法用于返回一个DatabaseMetaData对象。接口DatabaseMetaData进一步又包含大量的方法可以用于获取程序锁连接的数据库和数据库系统的元数据。
- getColumns
- getTables
- getCrossReference
5.1.1.7 其他特性
可更新的结果集,一个对结果集中的元组的更新将引起对数据库关系中相应元组的更新
conn.setAutoCommit()允许打开或关闭每个SQL语句被作为一个自动提交的独立的事务来对待。
ResultSet接口提供方法getBlob()和getClob()获取Blob和Clob对象的定位器,然后用getBytes和getSubString类似的方法类读取对象中的数据。setBlob(int parameterIndex, InputStream inputStream)把一个类似为二进制大对象(blob)的数据库列与一个输入流关联起来。当预备语句被执行时,数据从输入流被读取,然后被写入数据库的二进制大队下乡中。使用setClob可以设置字符大对象列
JDBC还提供了行集(row set),允许把结果集打包起来发送给其他应用程序。行集可以向后又可以向前扫描,并且可被修改。
5.1.2 ODBC
开发数据库互联(Open DataBase Connectivity, ODBC)
5.1.3 嵌入式SQL
SQL标准定义了嵌入SQL到许多不同的语言中,例如C、C++、Gobol、Pascal、Java和Fortran。SQL查询嵌入的语言被称为宿主语言,宿主语言中使用的SQL结构被称为嵌入式sQL。
一个使用嵌入式SQl的程序在编译前必须由一个特殊的预处理器进行处理。嵌入的SQL请求被宿主语言的声明以及允许运行时刻执行数据库访问的过程调用所代替,然后,所产生的程序由宿主语言编译器编译。这是嵌入式SQL与JDBC或ODBC的主要区别。
在JDBC中,SQL语句是在运行时被解释的(即使是利用预备语句特性对其进行准备也是如此)。当使用嵌入式SQL时,一些SQl相关的错误可以在编译过程中被发现。
为使预处理器识别嵌入式SQL请求,使用EXEC SQL语句
EXEC SQL <嵌入式SQL语句>
5.2 函数和过程
开发者可以编写自己的函数和过程,把他们存储在数据库里面并在SQL语句中调用。
函数和过程允许“业务逻辑”作为存储过程记录在数据库中,并在数据库内执行。
存储过程的几个有点:
- 允许多个应用访问这些过程
- 允许当业务规则发生变化时进行单个点的改变,而不必改变应用系统的其他部分
- 应用代码可以调用存储过程,而不是直接更新数据库关系
大多数据数据库都实现了他们自己的非标准版本的语法。
5.2.1 声明和调用SQL函数和过程
create function dept_count(dept_name varchar(20))
returns integer
begin
declare d_count interger;
select count(*) into de_count
from instructor
where instructor dept_name = dept_name
return d_count;
end
select dept_name, budget
from department
where dept_count(dept_name) > 12
SQL标准支持返回关系作为结果的函数;这种函数称为表函数(table function)
create function instructor_of(dept_name varchar(20))
returns table (
ID varchar(5),
name varchar(20),
dept_name varchar(20),
salary numeric (8,2))
return table
(select ID, name, dept_name, salary
from instructor
where instructor.dept_name = instructor_of.dept_name);
通常以表为值的函数可以被看做待参数的视图(parameterized view),它通过允许参数把视图的概念更加一般化
select *
from table(instructor_of('Finance'));
过程的写法
create procedure dept_count_proc(in 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
过程调用使用call语句
declare d_count integer;
call dept_count_prco('Physics', d_count);
SQL允许多个过程同名,主要同名过程的参数个数不同
SQL允许多个函数同名,只要同名的函数参数个数不同,如果参数个数相同则至少一个参数类型不同
5.2.2 支持过程和函数的语言构造
SQL标准中处理这些构造的部分称为持久存储模块(Persistent Storage Module,PSM)
变量通过declare生命,可以是任意合法的SQL乐行,使用set语句进行赋值
在begin和end之间会包含复杂的SQL语句,可以在符合语句中声明局部变量。
begin atomic……end可以确保其中包含的所有语句作为单一的事务来执行
while
while 布尔表达式 do
语句序列
end while
repeat
语句序列
until 布尔表达式
end repeat
for
declare n integer default();
for r as
select budget from department
where dept_name = 'Music'
do
set n = n - r.budget
end for
程序每次获取查询结果的一行,并存入for循环变量r中。语句leave可用来退出循环,而iterate表示跳过剩余语句从循环的开始进入下一个元组。
if-then-else
if 布尔表达式
then 语句或符合语句
elseif 布尔表达式
then 语句或复合语句
else 语句或复合语句
end if
case语句
异常
declare out_of_classroom_seats condition
declare exit handleer for out_of_classroot_seats
begin
sequence of statements
end
在begin和end之间的语句可以执行signal out_of_classroot_seats来引发一个异常。
5.2.3 外部语言过程
尽管对SQL的过程化扩展非常有用,然后这些并不被跨数据库的标准的方法所支持
用程序设计语言定义并在数据库系统之外编译的函数可以由数据库系统代码来加载和执行。不过这么做存在危险,程序中的错误可能破坏数据库内部结构,并且绕过数据库系统的访问控制功能。
在C这种语言中创建一个沙盒是不可能的,因为C语言允许通过指针不加限制地访问内存
5.3 触发器
**触发器(trigger)**是一条语句,当对数据库做修改时,它自动被系统执行。要设置触发器机制,必须满足两个要求:
- 指明什么条件下执行触发器。分解为一个引起触发器被检测的事件和一个触发器执行必须满足的条件。
- 指明触发器执行时的动作。
5.3.1 对触发器的需求
用来当满足特定条件时对用户发劲爆或自动开始执行某项任务。
触发器通常不能执行数据库意外的更新,因此在库存补充的例子中,不能用一个触发器去直接在外部世界下订单,而是在存放订单的关系中添加一个关系记录。另外创建一个持久运行的系统进程来周期性扫描该关系并订购产品。
5.3.2 SQL中的触发器
大不分数据库实现的是非标准版本的语法。
5.3.3 何时不用触发器
使用级联特性来实现外码约束的on delete cascade而不是用触发器
使用物化视图而不是触发器
使用数据库内置的复制工具来备份数据库而不是触发器
当备份节点加载备份数据时要关闭触发器
触发器要限制触发器链的长度,避免无限的触发链
触发器是很有用的工具,但是如果有其他候选方法最好别用触发器。很多触发器的应用都可以用适当的存储过程来替换。
5.4 递归查询
传递闭包(transitive closure)。
一辆自行车可能有子部件如车轮和踏板,他们又有子部件如轮胎、轮圈、辐条。可以对这种层次结构使用传递闭包来找出,例如自行车中的所有部件
5.4.1 用迭代来计算传递闭包
SQL允许使用命令create temporary table来创建临时表,这些表仅在执行查询的事务内部才可用,并随事务的完成而被删除。
过程中用到了三个临时表:
- c_prereq:存储要返回的元组集合
- new_c_prereq:存储在前一次迭代中找到的课程
- temp:当对课程集合进行操作时用作临时存储。
该过程在repeat循环之前把课程cid的所有直接先修课程插入new_c_prereq中。repeat循环首先把new_c_prereq中所有课程加入c_prereq中。然后,它为new_c_prereq中所有课程计算先修课程,从结果冲筛选掉已经计算出来cid的先修课,吧剩下的存放在临时表temp中。最后把new_c_prereq中的内容替换成temp中的内容。当找不到新的(间接)先修课程是,repeat循环终止。
5.4.2 SQL中的递归
用迭代传递闭包是挺不方便的,还有另一种方法,即使用递归视图定义。
with recursive子句来支持有限形式的递归
任何地柜视图都必须被定义为两个子查询的并:一个非地柜的基查询(base query)和一个使用递归视图的递归查询(recursive query)
理解如下:首先计算基查询并把所有结果元组添加到视图关系rec_prereq(初始为空)中。然后用当前视图关系的内容计算递归查询,并把所有结果元组佳慧到视图关系中。持续重复上述步骤直至没有新的元组添加到视图关系中为止。得到的视图关系实例就称为递归视图定义的一个不动点(fixed point)
递归查询不能用于任何下列构造,因为他们会导致查询非单调:
- 递归视图上的聚集
- 在使用递归视图的子查询上的not exists语句
- 右端使用递归视图的集合差(except)运算
5.5 高级聚集特性
5.5.1 排名
给出每个学生的名词
select ID, rank() over( order by(GPA) desc ) as s_rank
from student_grades
order by s_rank;
rank函数对所有在order by属性上相等的元组赋予相同的名次,如果有两个第一,那么下一个则是第三。另有一个dense_rank函数,它不在登记排序中产生隔断。
也可以使用基本的SQl聚集函数来表达上述查询
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;
rank子句的系统实现能够对关系进行排序并在相当短的时间内计算排名。
按照系而不是在整个学校范围内排名
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;
5.5.2 分窗
窗口查询用来对于一定范围内的元组计算聚集函数
趋势分析是分窗的应用案例
select year, avg(num_credits)
over (order by year rows 3 preceding)
as avg_total_credits
from tot_credits;
这个查询计算指定顺序下的前三个元组的均值
5.6 OLAP
连接分析处理(OLAP)系统是一个交互式系统,它允许分析人员查看多维数据的不同种类的汇总数据。联机表示分析人员提出汇总请求之后几秒钟之内在线得到相应,而无需等待很长时间。
5.6.1 联机分析处理
统计分析通常要对多个属性进行分组。给出一个用户数据分析的关系。
我的理解:用来分组的属性一般就可以是维度属性(dimension attribute)。用来聚集操作的属性作为度量属性(measure attribute’)
能够模式化为维度属性和度量属性的数据统称为多维数据(multidimensional data)
一个交叉表(cross-tabulation)也可称为转轴表(pivot-table),是从一个关系导出来的,由关系R的一个属性的值构成其行表头,关系R的另一个属性的值构成其列表头。
将二维的交叉表推广到n维,可视作n维立方体,称为数据立方体(data cube)
改变交叉表中的维的操作即做转轴(pivoting)
OLAP系统允许在某一个固定的clothes_size值(比如large而不是所有size的综合)来查看一个在item_name和color上的交叉表。这样的操作称为切片(slicing),因为它可以看做是查看一个数据立方体的某一篇。该操作有时也叫做切块(dicing)
OLAP系统允许用户按照期望的粒度级别观察数据。从较细粒度数据转到较粗粒度(通过聚集)的操作叫做上卷(rollup)。相反的操作,将较粗粒度的数据转化为较细粒度数据,称为下钻(drill down)。显然较细粒度数据不可能由较粗粒度数据产生,它们必须由原始数据产生,或者由更细粒度的汇总数据产生。
一个属性的不同细节层次可以组成成一个层次结构(hierarchy)
5.6.2 交叉表和关系表
对于没有汇总值的交叉表,可以把它直接表示为具有固定列数的关系形式。对于有汇总行/列的交叉表,可以通过引入一个特殊值all以表示子汇总值。值all可以被看做是代表一个属性的所有值的集合。在color、clothes_size上进行group by可用于得到在item_name上取all值的元组
最早的OLAP系统使用内存中的多维数组存储数据立方体,称为**多维OLAP(multidimentional OLAP, MOLAP)系统。后来,OLAP工具集成到关系系统中,数据存储到关系数据库里,这样的系统称为关系OLAP(relational OLAP, ROLAP)系统。符合系统将一些汇总数据存在内存中,基表数据和另一些汇总数据存在关系数据库中,称为混合OLAP(hybrid OLAP,HOLAP)**系统
在一个关系上计算整个数据立方体的一种朴素方法是使用任何一种标准算法来计算聚集操作,一次计算一个分组。朴素算法需要对关系进行大量的扫描操作。
数据立方体通常大于形成数据立方体的原始关系,在许多情况下存储整个数据立方体是不可行的。
作为对预先计算并存储所有可能的分组的替代方案,预先计算并存储某些分组,按需计算其他分组是有意义的。
5.6.3 SQL中的OLAP
原始关系表
通过SQL server和Oracle的pivot子句
select *
from sales
pivot (
sum(quantity)
for color in ('dark', 'pastel', 'white')
)
order by item_name;
创建的交叉表
pivot子句中的for子句用来指定属性color的哪些值应该在转轴的结果中作为属性名出现。属性color本身从结果中被去除。另外新产生的属性的值被设定来自于属性quantity。
如果想用分组的方式生成整个数据立方体,需要写一系列独立查询,如下
(item_name, color, clothes_size), (item_name, color), (item_name, clothes_size),(color, clothes_size),(item_name),(color),()
可以使用cube子句来实现
select item_name, color, clothes_size, sum(quantity)
from sales
group by cube(item_name, color, clothes_size);
5.7
- SQL查询可以从宿主语言通过前如何动态SQL激发。ODBC和JDBC标准给C、Java等语言的应用程序定义计入SQL数据库的应用程序接口。
- 函数和过程可以用SQL提供的过程扩展来定义,它允许迭代和条件(if-then-else)语句
- 触发器定义了当讴歌事件发生而且满足响应条件时自动执行的动作。触发器有很多用处,例如实现业务规则、审计日志,甚至执行数据库系统外的操作
- 一些查询,如传递闭包或者可以用迭代表示或者可以用递归SQL查询表示。递归可以用递归视图,或者用递归的with子句定义
- SQ支持一些高级的聚集特性,包括排名和分窗查询
- 联机分析处理(OLAP)
- OLAP工具工作在以维属性和度量属性为特性的多维数据上
- 数据立方体由不同方式汇总的多维数据构成,预先计算数据立方有助于提高汇总数据查询速度
- 交叉表的显示允许用户一次查看多维数据额两个维及汇总数据
- 下钻、上卷、切片和切块是用户使用oLAp工具时执行一些操作。
- 从SQL:1999开始,SQL提供了一些列用于数据分析的操作符,其中包括cube和rollup操作,有些系统还支持pivot子句