【数据库05】玩转SQL的高阶特性 1

简介: 【数据库05】玩转SQL的高阶特性

1.使用程序设计语言访问SQL

SQL查询不是万能的,我们还需要使用通用程序设计语言,至少有两点原因。

SQL不能表达所有的查询,对于复杂查询,我们可以把SQL嵌入到一种更加强大的语言做到。

非声明式动作不能够在SQL中完成(比如打印一份报告,和用户交互)。

可以通过两种方式从通用语言中访问SQL。


动态SQL(dynamic SQL)。通用程序可以通过一组函数或者方法连接数据库并与之通信,动态SQL允许在程序运行时以字符串形式构建SQL查询,提交查询,然后每次以一个元组的方式把结果存入程序变量中。这一篇文章我们将介绍用于java的应用程序接口JDBC,以及ODBC(最初为C开发,后来应用于C,C++,C#,Ruby,Go,PHP和Visual Basic等)。并介绍Python Database Api怎么连接到数据库。对于为VB和C#语言设计的ADO.NET API,本文不做介绍,可以参考相关手册。

嵌入式SQL(embedded SQL)。SQL语句在编译时采用预处理器来进行识别,预处理器用嵌入式SQL表达的请求转换为函数调用。在运行时,这些函数调用将使用动态SQL设施的API连接到数据库,但这些API可能只适用于正在使用的数据库。

把SQL与通用语言相结合的主要挑战是SQL与这些语言操作数据的方式不匹配,在SQL中,数据的主要类型是关系,SQL操作关系,返回结果也是关系,在程序设计语言中,数据操作的基本单元是变量。需要提供一种机制做这样的转换。


1.1 JDBC

JDBC提供了java程序连接到数据库服务器的应用程序接口。


下面示例是Java使用JDBC的一个示例,Java程序必须加在java.sql.*,它包含了JDBC所提供函数的接口定义。

public static void JDBCexample(String userid,String passwd){
  try(
    // 获取连接
    // 参数1,通信协议:主机名称:端口号:使用的特定数据库
    // 参数2,数据库用户标识
    // 参数3,密码
    Connection conn = DriverManager.getConnection("jdbc:oracle:thin:@db.yale:edu:1521:univdb",userid, passwd);
    // 创建一个Statement(在获取连接后执行SQL语句的对象)
    Statement stmt = conn.createStatement();
  ) {
    try{
    //以字符串形式构建SQL语句
      stmt.executeUpdate("insert into instructor values('77987','Kim','Physics',98000)");
    }
    catch(SQLException sqle) {
      System.out.println("Could not insert tuple:" + sqle);
    }
    // 查询获取结构,并将结果以"元组"方式存储到变量中
    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));
    }
  }
  catch(Exception sqle)
  {
    System.out.println("Exception:" + sqle);
  }
}

1.1.1 连接到数据库

getConnection()有三个参数。


参数1,通信协议:主机名称:端口号:使用的特定数据库。JDBC驱动会支持很多种协议,我们需要选择一个数据库和驱动器都支持的协议,协议的详细内容是由产商设定的;


参数2,数据库用户标识;


参数3,密码。注意,在JDBC代码中直接指定密码会带来安全性风险,这里仅仅是为了简便这么写。


所有的主流产商都支持JDBC,这些数据库产品都会提供一个JDBC驱动程序,该驱动程序必须在连接到数据库前被动态的加载才能数显Java对数据库的访问。如果已经从产商的网站下载了合适的驱动程序,getConnection()方法将定位所需要的驱动程序,从而实现面向产品的调用。

38c3bba8fe4e4509b167821553cec9f0.jpg

用于与数据库交换数据的协议实际上取决于JDBC驱动,协议是根据数据库产品的支持进行选择的,示例中使用的是:jdbc:oracle:thin,Mysql支持的协议是:jdbc:mysql


1.1.2 向数据库系统中传递SQL语句

我们通过连接句柄conn创建了Statement 对象,该对象用来向数据库系统中传递SQL语句。


1.1.3 异常与资源管理

try...catch结构用来处理异常。


打开连接、语句和其他JDBC对象都会消耗系统资源,必须及时关闭,否则数据库资源池会耗尽。关闭资源的一种方式是显示调用关闭,这种方式存在隐患,如果代码异常退出,此方法将会失效,你可以通过将其放在finally结构中解决或者使用示例中推荐的第二种方式。第二种方式是try-with-resources结构,它将连接和语句对象放在try中完成,简洁明了,自动隐式关闭该语句的对象,是首选方式。


1.1.4 获取查询结果

示例通过stmt.executeQuery获取查询结果,存储到ResultSet 变量中,通过getString可以获取所有类型的结果对象并且将其转化为String,也可以通过getFloat这种约束性更强的方式提取结果中的属性。提取属性可以通过名称(getString(dept_name))或者位置(getFloat(2))进行.

1.1.5 预备语句

我们可以创建一条预备语句,其中用"?"来代替某些值,以此指明以后会对其提供实际的值。数据库系统在预备查询的时候对其进行编译,在执行查询时(用新值代替“?”),数据库系统可以重用此前编译的查询形式,将新的值作为参数来应用。下面是一个示例。

9f4958de1bdb47ec90c0f5143f2f2a5a.jpg


在同一查询编译一次然后带不同的参数值运行多次的情况,预备语句使得执行更加高效。而且预备查询有个更大的有事,他可以避免用户手动拼接sql引入特殊字符(如多余的单引号,空格),从而生成具有语法错误的SQL。setString会自动检查用户输入,并且插入必须的转义确保语法的正确性,因此,预备语句是执行SQL的首选方法。


除此之外,使用预备语句还可以避免SQL注入来破坏或者窃取数据。


假如一个Java程序SQL如下。


"select * from instructor where name = '" + name + "'"

如果用户输入的参数name不是姓名,而是:


X' or 'Y' = 'Y

那么执行的SQL会变成:

select * from instructor where name = 'X' or 'Y' = 'Y'

本来用户只可以按姓名查找数据,现在他窃取了整个关系的数据!!!还有很多诡计多端的注入手段,窃取篡改数据。


使用预备语句可以避免这样的问题,因为查询的格式已经被预编译,用户输入的数据都被视为普通的字符串,会被插入转义字符,所以最后的查询会变成:

select * from instructor where name = 'X\' or \'Y\' = \'Y'

这是无害的查询,只会返回空的关系。


有些数据库系统允许在单个JDBC的execute方法执行多条SQL语句,语句之间用分号分隔。该特性在某些JDBC驱动中默认关闭了,因为它也可能带来SQL注入的风险。


对前面的SQL注入示例中,用户可以输入:

X';drop table instructor;--

这将导致很严重的问题。因此程序员必须使用预备语句进行查询。


1.1.6 可调用语句

JDBC还提供了CallableStatement接口,它允许调用SQL的存储过程和函数,它也用"?"来代替某些值,以此指明以后会对其提供实际的值,其返回值用registerOutParameter()方法注册,通过与结构集类似的get方法检索,可以参阅手册获取详情。


1.1.7 元数据特性

我们发现java应用程序中并不包含所存储数据的声明,这些声明是SQL DDL的一部分,因此只看java程序可能并不知道存储在数据库中的具体数据模式(当然你可以查看数据库,文档等),ResultMap接口提供了一个getMetaData()方法解决你的困难。

ResultSetMataData rsmd = rs.getMetaData();
// 获取属性个数并遍历
for(int i = 1; i <= rsdm.getColumnCount(); i++) {
  // 获取属性名称
  System.out.println(rsdm.getColumnName(i));
  // 获取属性类型
  System.out.println(rsdm.getColumnTypeName(i));
}

除了关系的属性信息,还有很多其他的元数据:产品名称,版本号,数据库系统所支持的特性等。Connection接口可以获得一个DatabaseMetaData对象,DatabaseMetaData接口提供了查找关于数据库的元数据的方法。下图使用DatabaseMetaData查找列信息,其中getColumns第一个参数为null,表示其目录名称将被忽略,最后一个参数使用通配符%,表示匹配所有的列(名称)。

1ab14733a7074713b5ca9bc31214b50f.jpg


还有其它API请自行查阅手册。

元数据接口可以用于各种任务,例如,他们可以用于编写数据库浏览器,该浏览器允许用户查找数据库中的表,检查他们的模式,检查表中的行,应用选择来查看所需要的行等


1.1.8 其他特性

JDBC还有很多其他的特性。


可更新的结果集。根据在数据库关系上执行选择或者投影来创建出可更新的结果集,对结果集的更新将导致对数据库关系对应元组的更新。

事务的自动提交开启/关闭,事务回滚。通过Connection接口的setAutoCommit()方法与rollback()方法实现。

大对象处理接口。Result提供getBlob()和getClob()方法。PreparedStatement类提供setBlob,setClob方法

行集特性,允许收集结果集将其发送给其它应用程序,行集可以向前,向后扫描,并且可以被修改。

1.2 从Python访问数据库

可以通过如下方式完成。

cabc35854e504431b73ba77de42d0b19.jpg

注意,上面示例中查询语句不会自动提交到数据库,需要调用commit()方法。


程序第一行导入的是psycopg2驱动程序,这是连接到PostgreSQL的驱动程序。其他的产商驱动与python访问数据库的语法细节可以查阅手册。


1.3 ODBC

开放数据库连接(Open DataBase Connectivity, ODBC)标准定义了一个API,应用程序可以用它来与一个数据库的连接、发送查询和更新并获取返回结果。诸如图形化用户界面、统计程序包及电子表格那样的应用程序可以使用相同的ODBC API来连接到支持ODBC的任何数据库服务器。

32f4b6040fef47d2be87f1416873318c.jpg

ODBC的语法这里不做展开,SQL标准定义了一个与ODBC接口类似的调用层接口(Call level Interface,CLI)

1.4 嵌入式SQL

SQL标准允许将SQL嵌入到其他高级程序语言,嵌入了SQL查询的语言被称为宿主语言,在宿主语言中允许使用的SQL结构构成了嵌入式SQL。


嵌入式SQL程序在编译之前必须由特殊的预处理器进行处理,该预处理器将嵌入的SQL请求替换为宿主语言的声明以及允许运行时执行数据库访问的过程调用。然后,所产生的程序由宿主语言编译器进行编译。这就是嵌入式SQL与JDBC或者ODBC的主要区别。


为了使预处理器识别出嵌入式SQL请求,我们使用EXEC SQL语句,其格式如下。


EXEC SQL <嵌入式SQL语句>;

在执行任何SQL语句之前,程序必须受限连接到数据库,在嵌入式SQL语句中可以使用宿主语言的变量,不过他们的前面必须加上冒号:以将它们与SQL变量分开来。

要遍历一个嵌入式SQL查询的结果,我们必须声明一个游标变量,它可以随后被打开,并在宿主语言循环中发出获取(fetch)命令来获取查询结果的连续行。行的属性可以提取到宿主语言变量中,数据库更新也可以通过以下方式实现:使用关系上得游标来遍历关系的行,或者使用where子句来仅遍历所选的行。嵌入式SQL命令可用于更新游标所指向的当前的行。

嵌入式SQL请求的确切语法取决于嵌入SQL的语言,请参考手册。

JDBC中,SQL语句在运行时才进行解释,但在使用嵌入式SQL时,在预处理时就有可能捕获一些与SQL程序相关的错误(包括数据类型错误)。与在程序中使用动态SQL相比,嵌入式SQL程序中的SQL查询更容易理解。但是,嵌入式SQL也存在一些缺点,预处理器会创建新的宿主语言代码,这使得程序的调试变得更加复杂。并且当宿主语言的语法迭代时,还有可能发生语法冲突。

微软语言集成查询(LINO)使用嵌入式SQL,它扩展了宿主语言以包括对查询的支持,而不是使用预处理器将嵌入式SQL查询转换为宿主语言。除此意外,动态SQL仍然是主流。


相关文章
|
5天前
|
SQL 数据库 数据安全/隐私保护
SQL Server数据库Owner导致事务复制log reader job无法启动的解决办法
【8月更文挑战第14天】解决SQL Server事务复制Log Reader作业因数据库所有者问题无法启动的方法:首先验证数据库所有者是否有效并具足够权限;若非,使用`ALTER AUTHORIZATION`更改为有效登录名。其次,确认Log Reader使用的登录名拥有读取事务日志所需的角色权限。还需检查复制配置是否准确无误,并验证Log Reader代理的连接信息及参数。重启SQL Server Agent服务或手动启动Log Reader作业亦可能解决问题。最后,审查SQL Server错误日志及Windows事件查看器以获取更多线索。
|
3天前
|
SQL 存储 关系型数据库
数据库SQL入门指南
数据库SQL入门指南
|
1天前
|
SQL 关系型数据库 MySQL
解决:Mybatis-plus向数据库插入数据的时候 报You have an error in your SQL syntax
该博客文章讨论了在使用Mybatis-Plus向数据库插入数据时遇到的一个常见问题:SQL语法错误。作者发现错误是由于数据库字段中使用了MySQL的关键字,导致SQL语句执行失败。解决方法是将这些关键字替换为其他字段名称,以避免语法错误。文章通过截图展示了具体的操作步骤。
|
3天前
|
SQL 关系型数据库 数据库
数据库空间之谜:彻底解决RDS for SQL Server的空间难题
【8月更文挑战第16天】在管理阿里云RDS for SQL Server时,合理排查与解决空间问题是确保数据库性能稳定的关键。常见问题包括数据文件增长、日志文件膨胀及索引碎片累积。利用SQL Server的动态管理视图(DMV)可有效监测文件使用情况、日志空间及索引碎片化程度。例如,使用`sp_spaceused`检查文件使用量,`sys.dm_db_log_space_usage`监控日志空间,`sys.dm_db_index_physical_stats`识别索引碎片。同时,合理的备份策略和文件组设置也有助于优化空间使用,确保数据库高效运行。
10 2
|
16天前
|
SQL 安全 关系型数据库
关系型数据库SQL server DELETE 语句
【8月更文挑战第3天】
44 10
|
16天前
|
SQL 关系型数据库 数据库
关系型数据库SQL server UPDATE 语句
【8月更文挑战第3天】
43 10
|
16天前
|
SQL 关系型数据库 BI
关系型数据库SQL server INSERT 语句
【8月更文挑战第3天】
39 9
|
19天前
|
SQL 关系型数据库 数据库
|
19天前
|
SQL 关系型数据库 数据库
|
19天前
|
SQL 算法 关系型数据库
(二十)MySQL特性篇:2022年的我们,必须要懂的那些数据库新技术!
 MySQL数据库从1995年诞生至今,已经过去了二十多个年头了,到2022.04.26日为止,MySQL8.0.29正式发行了GA版本,在此之前版本也发生了多次迭代,发行了大大小小N多个版本,其中每个版本中都有各自的新特性,所有版本的特性加起来,用一本书的篇幅也无法完全阐述清楚,因此本章主要会挑重点特性来讲,具体各版本的特性可参考MySQL官网的开发手册。