高级SQL
约 2370 个字 246 行代码 预计阅读时间 21 分钟
介绍
-
编程语言中访问SQL
本章介绍SQL的高级特性,包括: - 嵌入式SQL - ODBC和JDBC接口 - 函数和过程构造 - 触发器 - 高级聚合特性 - 联机分析处理(OLAP)
编程语言中访问SQL
在实际应用中,SQL查询和更新通常嵌入在通用编程语言(如C、Java、Python等)编写的程序中。SQL不提供完整的图形用户界面工具和复杂业务逻辑的控制结构,因此需要通过编程语言来弥补这些不足。
嵌入式SQL
-
嵌入式SQL基础
- SQL标准定义了SQL在各种编程语言(如C、Java和Cobol)中的嵌入方式
- 嵌入SQL的语言被称为宿主语言
- 使用
EXEC SQL
语句标识嵌入SQL请求 - 主要问题:宿主语言和SQL语句之间的参数和结果交换、集合与变量的处理、获取SQL语句的执行状态等
-
游标
游标允许逐个元组地处理查询结果
游标声明
游标操作 -
OPEN
:执行查询并创建结果集 -FETCH
:获取下一个元组 -CLOSE
:释放资源
游标使用示例(C语言)
void getStudentInfo() {
int credit_amount;
char sId[16];
char sName[16];
EXEC SQL DECLARE c CURSOR FOR
SELECT id, name FROM student WHERE tot_cred > :credit_amount
END_EXEC;
printf("Please input the credit amount: ");
scanf("%d", &credit_amount);
EXEC SQL OPEN c END_EXEC;
while(1) {
EXEC SQL FETCH c INTO :sId, :sName END_EXEC;
if (!strcmp(SQLSTATE, "02000"))
break; // 无更多数据
printf("%s %s\n", sId, sName);
}
EXEC SQL CLOSE c END_EXEC;
}
游标可以用于更新已获取的元组,通过在声明游标时指定FOR UPDATE
选项,并使用WHERE CURRENT OF
子句。
DECLARE c CURSOR FOR
SELECT *
FROM instructor
WHERE dept_name = 'Music'
FOR UPDATE
-- 更新当前游标位置的元组
UPDATE instructor
SET salary = salary + 100
WHERE CURRENT OF c
ODBC和JDBC
-
数据库编程接口
API(应用程序接口)允许程序与数据库服务器交互: - 连接数据库服务器 - 发送SQL命令 - 一个一个地获取结果元组
主要接口: - ODBC(Open Database Connectivity):用于C、C++、C#和Visual Basic - JDBC(Java Database Connectivity):用于Java
-
ODBC架构
ODBC特点: - 定义了应用程序与数据库服务器通信的标准 - 提供API以打开连接、发送查询、更新和获取结果 - 支持各种数据库系统 - 基于驱动程序架构
组件: - ODBC应用程序 - ODBC管理器 - 特定数据库系统的驱动程序
ODBC
ODBC(开放数据库连接)标准允许应用程序通过通用接口与各种数据库系统进行通信。
ODBC程序的基本流程:
- 分配SQL环境和连接句柄
- 连接到数据库
- 执行SQL语句
- 处理结果
- 断开连接并释放资源
ODBC基本连接示例
ODBC预处理语句是一种更安全、更高效的执行SQL的方式:
/* 预处理语句版本 */
SQLAllocStmt(conn, &stmt);
SQLPrepare(stmt, "SELECT id, name, tot_cred FROM student WHERE tot_cred > ?", SQL_NTS);
SQLBindParameter(stmt, 1, SQL_PARAM_INPUT, SQL_C_SLONG, SQL_INTEGER, 0, 0,
&creditAmount, sizeof(int), NULL);
SQLExecute(stmt);
/* 绑定结果列并获取数据 */
SQL注入风险:始终使用预处理语句绑定用户输入,而不是直接拼接SQL字符串。
JDBC
JDBC (Java Database Connectivity) 是Java标准的数据库访问API。
JDBC基本连接示例
public static void JDBCexample(String dbid, String userid, String passwd) {
try {
Class.forName("oracle.jdbc.driver.OracleDriver");
Connection conn = DriverManager.getConnection(
"jdbc:oracle:thin:@db.yale.edu:2000:univdb", userid, passwd);
Statement stmt = conn.createStatement();
/* 执行实际工作 */
stmt.close();
conn.close();
} catch (SQLException sqle) {
System.out.println("SQLException: " + sqle);
}
}
JDBC执行查询和更新:
// 执行更新
stmt.executeUpdate(
"INSERT INTO instructor VALUES('77987', 'Kim', 'Physics', 98000)");
// 执行查询并获取结果
ResultSet rset = stmt.executeQuery(
"SELECT dept_name, AVG(salary) FROM instructor GROUP BY dept_name");
while (rset.next()) {
System.out.println(rset.getString("dept_name") + " " + rset.getFloat(2));
}
JDBC预处理语句:
PreparedStatement pStmt = conn.prepareStatement(
"INSERT INTO instructor VALUES(?,?,?,?)");
pStmt.setString(1, "88877");
pStmt.setString(2, "Perry");
pStmt.setString(3, "Finance");
pStmt.setInt(4, 125000);
pStmt.executeUpdate();
元数据获取:JDBC可以获取数据库和查询结果的元数据信息。
// 获取ResultSet元数据
ResultSetMetaData rsmd = rs.getMetaData();
for(int i = 1; i <= rsmd.getColumnCount(); i++) {
System.out.println(rsmd.getColumnName(i));
System.out.println(rsmd.getColumnTypeName(i));
}
// 获取数据库元数据
DatabaseMetaData dbmd = conn.getMetaData();
ResultSet rs = dbmd.getColumns(null, "univdb", "department", "%");
函数和过程构造
SQL:1999及之后的版本支持过程化SQL,允许定义函数和存储过程,包括条件语句、循环等控制结构。
-
SQL函数
SQL:1999支持使用SQL或外部编程语言编写的函数:
CREATE FUNCTION dept_count (dept_name VARCHAR(20)) RETURNS INTEGER BEGIN DECLARE d_count INTEGER; SELECT COUNT(*) INTO d_count FROM instructor WHERE instructor.dept_name = dept_name; RETURN d_count; END
函数调用示例:
-
表值函数
SQL:2003添加了返回关系作为结果的函数:
CREATE FUNCTION instructors_of(dept_name CHAR(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 = instructors_of.dept_name )
使用方式:
-
存储过程
存储过程是可以存储在数据库中的程序:
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
调用过程:
过程编程构造
SQL标准和各数据库系统提供了丰富的过程化编程构造,包括:
条件语句和循环
复合语句:BEGIN ... END
变量声明:DECLARE n INTEGER DEFAULT 0;
While循环:
Repeat循环:
For循环:
DECLARE n INTEGER DEFAULT 0;
FOR r AS
SELECT budget FROM department
WHERE dept_name = 'Music'
DO
SET n = n + r.budget;
END FOR
条件语句:
外部语言过程和函数
SQL:1999允许使用如C、C++等外部语言编写函数和过程:
CREATE PROCEDURE dept_count_proc(IN dept_name VARCHAR(20), OUT count INTEGER)
LANGUAGE C
EXTERNAL NAME '/usr/avi/bin/dept_count_proc'
优点:对于许多操作更高效,表达能力更强。
缺点:可能存在安全风险,可以通过沙箱技术或单独进程执行来降低风险。
触发器
触发器是在数据库修改时自动执行的语句,它在SQL:1999标准中被引入。
-
触发器基础
设计触发器机制需要指定: - 触发器执行的条件 - 触发器执行时要采取的操作
触发事件可以是: - INSERT - DELETE - UPDATE (可以限制到特定属性)
-
触发器语法
CREATE TRIGGER trigger_name BEFORE|AFTER|INSTEAD OF event ON table_name [REFERENCING OLD ROW AS old NEW ROW AS new] [FOR EACH ROW|STATEMENT] WHEN (condition) BEGIN -- 触发器操作 END;
触发器可以在事件之前或之后执行,并可以引用事件前后的值(OLD ROW和NEW ROW)。
触发器示例:完整性约束检查
触发器示例:维护派生数据
CREATE TRIGGER credits_earned AFTER UPDATE OF grade ON takes
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;
触发器曾经用于维护汇总数据、复制数据库等任务,但现代数据库提供了更好的方式(如物化视图、内置复制支持)。
递归查询
SQL:1999允许递归视图定义,这使得可以编写传递闭包等无法用非递归查询表达的查询。
递归查询示例:先决条件传递闭包
递归查询示例:员工-经理关系
高级聚合特性
SQL提供了丰富的高级聚合功能,包括排名、窗口函数等。
-
排名函数
SQL排名函数与ORDER BY子句一起使用:
排名函数类型: -
RANK()
:留下空缺排名(如有并列第1,则下一个排名为第3) -DENSE_RANK()
:不留空缺(如有并列第1,则下一个排名为第2) -ROW_NUMBER()
:分配唯一排名(对重复值处理非确定性) -PERCENT_RANK()
:百分比排名 -CUME_DIST()
:累积分布(前面值的元组比例) -
窗口函数
窗口函数在一组行上执行计算,用于平滑随机变化:
-- 计算销售移动平均值(当天、前一天和后一天) SELECT date, SUM(value) OVER ( ORDER BY date ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING ) FROM sales;
窗口规范示例: -
ROWS UNBOUNDED PRECEDING
:从分区开始到当前行 -ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
:同上 -RANGE BETWEEN 10 PRECEDING AND CURRENT ROW
:当前值减10到当前值的所有行 -
分区排名和窗口
排名和窗口函数可以在数据分区内应用:
联机分析处理(OLAP)
-
OLAP基础
OLAP(联机分析处理)是数据的交互式分析,允许以在线方式(几乎无延迟)汇总和以不同方式查看数据。
多维数据:可以建模为维度属性和度量属性的数据。
- 度量属性:度量某个值,可以聚合
- 维度属性:定义查看度量属性的维度
-
数据立方体和交叉表
交叉表(Cross-Tab)也称为数据透视表:
- 一个维度属性的值形成行标题
- 另一个维度属性的值形成列标题
- 其他维度属性列在顶部
- 单元格中的值是指定单元格的维度属性的(聚合)值
数据立方体是交叉表的多维概括,可以有n个维度。
-
OLAP操作
CUBE操作:计算指定属性的每个子集上的GROUP BY联合
ROLLUP操作:在指定属性列表的每个前缀上生成联合
其他OLAP操作:
- 旋转:改变交叉表中使用的维度
- 切片:仅为固定值创建交叉表
- 上卷:从细粒度数据移动到粗粒度数据
- 下钻:从粗粒度数据移动到细粒度数据
-
OLAP实现
OLAP系统类型:
- MOLAP(多维OLAP):使用内存中的多维数组存储数据立方体
- ROLAP(关系OLAP):仅使用关系数据库功能实现OLAP
- HOLAP(混合OLAP):在内存中存储某些汇总,在关系数据库中存储基础数据和其他汇总
早期OLAP系统预先计算所有可能的聚合以提供在线响应,但这需要大量空间和时间。现代系统使用各种优化技术来减少计算和存储需求。
OLAP案例与应用
OLAP技术被广泛应用于各种商业智能和数据分析场景,如:
OLAP应用场景
- 销售数据分析:按产品、地区、时间等维度分析销售情况
- 财务报表:按不同财务指标、部门、时间周期等分析财务数据
- 客户行为分析:按客户类型、产品类别、购买时间等分析客户行为
- 供应链监控:按供应商、物流渠道、时间等维度分析供应链效率
- 人力资源管理:按部门、职位等分析员工绩效和其他指标
数据立方体表示:
总结
SQL的高级特性极大地扩展了数据库系统的功能和灵活性:
- 编程接口(嵌入式SQL、ODBC和JDBC)提供了与各种应用程序集成的方式
- 函数和存储过程增加了过程化编程能力,允许在数据库中执行复杂逻辑
- 触发器提供了自动响应数据库更改的机制
- 递归查询使得可以表达传递闭包等复杂查询
- 高级聚合特性(如排名和窗口函数)提供了强大的数据分析能力
- OLAP功能支持复杂的多维数据分析和数据挖掘
这些高级特性使SQL从简单的查询语言发展成为一个完整的数据处理系统,能够满足现代企业和应用程序的各种数据管理需求。