SQL语言作为关系数据库管理系统中的一种通用的结构查询语言,已经被众多的数据库管理系统所采用,如ORACLE、Sybase、Informix等数据库管理系统,它们都支持SQL 语言。Delphi与使用SQL语言的数据库管理系统兼容,在使用Delphi开发数据库应用程序时,我们可以使用SQL语言编程,支持SQL编程是Delphi的一个重要特征,这也是体现Delphi作为一个强大的数据库应用开发工具的一个重要标志。
17.1 SQL语言简介
17.1.1 SQL的历史
在70年代初,E.E.Codd首先提出了关系模型。70年代中期,IBM公司在研制 SYSTEM R关系数据库管理系统中研制了SQL语言,最早的SQL语言(叫SEQUEL2)是在1976 年 11 月的IBM Journal of R&D上公布的。1979年ORACLE公司首先提供商用的SQL,IBM公司在DB2 和SQL/DS数据库系统中也实现了SQL。
1986年10月,美国ANSI采用SQL作为关系数据库管理系统的标准语言(ANSI X3. 135-1986),后为国际标准化组织(ISO)采纳为国际标准。1989年,美国ANSI采纳在ANSI X3.135-1989报告中定义的关系数据库管理系统的SQL标准语言,称为ANSI SQL 89, 该标准替代ANSI X3.135-1986版本。该标准为下列组织所采纳:
● 国际标准化组织(ISO),为ISO 9075-1989报告“Database Language SQL With Integrity Enhancement”
● 美国联邦政府,发布在The Federal Information Processing Standard Publication(FIPS PUB)127
目前,所有主要的关系数据库管理系统支持某些形式的SQL语言,大部分数据库打算遵守ANSI SQL89标准。
17.1.2 SQL的优点
SQL广泛地被采用正说明了它的优点。它使全部用户,包括应用程序员、DBA管理员和终端用户受益非浅。
(1) 非过程化语言
SQL是一个非过程化的语言,因为它一次处理一个记录,对数据提供自动导航。SQL允许用户在高层的数据结构上工作,而不对单个记录进行操作,可操作记录集。所有SQL 语句接受集合作为输入,返回集合作为输出。SQL的集合特性允许一条SQL语句的结果作为另一条SQL语句的输入。
SQL不要求用户指定对数据的存放方法。这种特性使用户更易集中精力于要得到的结果。所有SQL语句使用查询优化器,它是RDBMS的一部分,由它决定对指定数据存取的最快速度的手段。查询优化器知道存在什么索引,哪儿使用合适,而用户从不需要知道表是否有索引,表有什么类型的索引。
(2) 统一的语言
SQL可用于所有用户的DB活动模型,包括系统管理员、数据库管理员、应用程序员、决策支持系统人员及许多其它类型的终端用户。基本的SQL 命令只需很少时间就能学会,最高级的命令在几天内便可掌握。
SQL为许多任务提供了命令,包括:
● 查询数据
● 在表中插入、修改和删除记录
● 建立、修改和删除数据对象
● 控制对数据和数据对象的存取
● 保证数据库一致性和完整性
以前的数据库管理系统为上述各类操作提供单独的语言,而SQL 将全部任务统一在一种语言中。
(3) 是所有关系数据库的公共语言
由于所有主要的关系数据库管理系统都支持SQL语言,用户可将使用SQL的技能从一个RDBMS转到另一个。所有用SQL编写的程序都是可以移植的。
17.2 TQuery部件在SQL编程中的运用
在Delphi中是通过TQuery部件来实现对SQL语言支持的,也就是说用Delphi 开发数据库应用程序时,使用SQL语言操作数据库中的数据的唯一途径是经过TQuery部件。 TQuery部件在Delphi中使用SQL语言编程时占居着绝对重要的地位。在使用Delphi 开发的数据库应用中,可以使用SQL语言访问下列三个方面的数据库:
● Paradox或dBASE数据库中的表
在访问这些桌面数据库系统中的数据时,只能使用ANSI标准的SQL语言中的部分SQL 语句,它们主要包括:Select、Insert、Update和Delete语句;即本地SQL语句。有关详细情况请参见附录“局部SQL语句的使用”。
● 本地InterBase数据库服务器中的数据库
在InterBase数据库中支持的SQL语句, 在Delphi中都可以使用。有关InterBase中SQL语句的语法和限制,请参看“InterBase的语言参考”。
● 远程数据库服务器中的数据库
当然这要求在Delphi中必须安装相应的SQL Link。只要是数据库服务器上的DBMS支持的SQL语句,在Delphi中都可以使用。有关语法及限制请参看相关的数据库管理 系统的文档。
值得一提的是,Delphi还支持异构查询,即可以同时查询多个数据库服务器中相同的或不同类型的数据库表,例如查询的数据可以是来自ORACLE数据库中的表和Sybase数据库中的表或者其它多个数据库中的表。
17.2.1 TQuery部件的使用
TQuery部件是一个数据集部件,它在Delphi部件选择板上的数据访问页(Data Access)上,它与TTable部件具有很多共同的特性,我们在第十五章“数据访问部件的应用及编程”中较详细地进行了介绍。 TQuery 部件在 SQL 编程中占居了十分重要的地位。 它实现了Delphi对SQL语言的支持,在Delphi开发的数据库应用中,SQL语句是通过TQuery部件传递到要访问的数据库系统的数据库引擎中,由数据库引擎具体执行SQL语句,以实现对数据的操作,而不是传递给Delphi中的BDE,由BDE实施具体的SQL动作。
我们已经知道了TTable部件在访问数据库时已经具备很强大的功能。TTable部件通过Delphi内置的BDE可以实现对各种数据库系统的访问,然而TQuery部件提供了一些 TTable部件不具备的功能,它们是:
●多表联接查询
●复杂的嵌套查询(Select中包含着Select子查询)
●明确需要按SQL语言进行的操作
因为TTabel部件不能使用SQL语言,而在TQuery部件可以使用SQL语言,因而TQuery部件也就具备了强大的关系查询能力。当然这也使数据库应用程序本身变得更复杂了。
在Delphi应用程序中编写和使用的SQL语句有两种:即静态SQL语句、动态SQL 语句。静态SQL语句是在程序设计阶段,将SQL命令文本作为TQuery部件的SQL属性值设置。而动态SQL语句编程是SQL语句中包含一系列的参数,在程序运行过程中各参数值是可变的,即可以动态地给SQL语句中的参数赋值。
静态方式是把SQL命令文作为TQuery部件的SQL属性值进行设置,这样,当执行应用程序时,Delphi便执行TQuery部件SQL属性中设置的SQL命令。如果是SQL中的查询命令,把TQuery部件通过TDataSource部件与数据控制部件相连,查询的结果将会显示在与 TQuery部件相连接的数据浏览部件中。动态SQL语句是指SQL语句中包含一些参数变量,在程序中可以为这些参数赋值,在程序运行过程中,各个参数值是变化的。TQuery部件的SQL 属性中的SQL语句的编写也有两种方法,一种方法是在程序设置阶段便将相应的SQL语句写入到TQuery的SQL属性中,另一种方法是在Delphi开发的应用程序将SQL语句,包含在Pascal代码单元中。
在SQL编程中使用TQuery部件的具体方法步骤如下:
①为TQuery部件设置DatabaseName属性,它可以是用BDE建立的数据库的别名,或桌面数据库系统中的目录名或数据库服务器中的文件名,如果在应用中使用了TDatabase 部件,那么TQuery部件的DatabaseName属性可以是TDatabase部件中定义的一个数据库别名。详细情况请参看“TDatabase部件的使用”;
②为TQuery部件设置SQL属性,TQuery部件的SQL属性值就是应用程序要执行的SQL 命令文本,设置SQL属性有两种方法:
● 在程序设计过程中,我们可以通过对象浏览器(Object Inspector)编辑SQL属性在Object Inspector中选择SQL属性,这样会打开String List Editor窗口,在其中我们便可以编写SQL命令,我还可以打开Visual Query Builder来编写SQL命令 (只有Delphi的客户/服务器版本才具有这一工具)。
● 将SQL命令包含在Pascal代码单元中
在程序运行过程中,首先调用TQuery部件的Close方法关闭当前的TQuery部件,然后调用Clear方法清除SQL属性,并说明新的SQL命令文本,然后调用Add方法,将新的SQL命令文本加入到SQL属性中。
③通过调用TQuery部件的Open方法或ExecSQl方法执行 SQL 命令。 Open 方法只执行Select命令,ExecSQL方法还可以执行其它的SQL命令。Open方法和ExecSQL 方法的区别我们在后面的章节里会进一步地加以讨论的。
如果使用动态SQL语句,首先调用prepare方法,给动态SQL语句中的参数赋值,然后再调用Open方法或ExecSQL方法。调用propare 方法并不是必须的,但是对于要多次执行TQuery部件中SQL属性中的动态SQL语句,调用Prepare可以大大提高TQuery部件执行SQL语句的性能。
17.2.2 在TQuery部件中编写简单的SQL查询命令
在这一节里我们将学习如何使用TQuery部件编写简单的SQL查询命令,并在Delphi 应用程序中实现SQL查询。
例如,如果我们想查询出表Customer.DB中客户的编号和公司名称,我们按下列步骤来实现:
①在应用窗体中放置一个TQuery部件、一个TDataSource部件一个TDataGrid部件,并将它们连接起来
②设置窗体TQuery 部件Query1的DatabaseName属性值为DBDEMOS
③双击Object Inspector窗口中Query1的SQL 属性, Delphi 将显示 String List Editor窗口。
④在图17.3中的窗口中输入SQL语句:
Select CustNo,Company From Custormer;
⑤单击OK按钮,关闭String List Editor窗口。
⑥设置Query的Open属性为True。
17.3 SQL语言编程概述
在Delphi应用程序中的SQL命令语句是包含在TQuery部件的SQL属性中,TQuery部件的SQL属性是TString类型的,也就是说SQL属性值是一个字符串列表,这个字符串列表非常类似于一个字符串类型的数组,有关TString类型的信息请参看联机帮助。在前一节里我们介绍了TQuery部件可以执行两种SQL语句:
● 静态SQL语句
● 动态SQL语句
静态SQL语句在程序设计时便已固定下来,它不包含任何参数和变量,例如下面的语句便是一条静态SQL语句:
Select * From Cusromer Where CustNo = 1234;
而动态SQL语句,也被称作参数化的语句,在其中间包含着表示字段名或表名的参数,例如下面的语句是一条动态SQL语句:
Select * From Customer Where CustNo =: Number;
其中的变量Number便是一个参数变量,它由一个冒号引导,在程序运行过程中,必须要为该参数赋值,该条SQL语句才能正确执行,每次运行应用程序时可以为该参数变量赋予不同的值。
17.3.1 SQL命令文本的编写
1. 使用String List Editor编写
我们要为TQuery部件的SQL属性设置SQL命令文本时,可以在应用窗体中选择TQuery部件且双击Object Inspector窗口中的SQL属性,这样便打开了String List Editor 窗口,在该窗口中我们便可以编写各种SQL命令,如图17.3所示。
在编写完适当的SQL语句之后,选择 OK 按钮便可以将编辑器中的 SQL 命令文装入到TQuery部件的SQL属性中,选择SAVE按钮可以将编写好的SQL命令保存到一个文件中供以后编程时使用。我们在编写SQL命令文本时还可以选择Load按钮从一个 SQL 命令文件中调入SQL命令。在程序运行过程中,要想设置TQuery部件的SQL属性,必须首先调用Close方法,关闭TQuery部件,然后再调用Clear方法清除SQL属性中现存的SQL命令语句,最后再调用Add方法为SQL属性设置新的SQL命令语句。例如:
Query1.Close {关闭Query1)
Query1.SQL.Clear {清除SQL属性中的SQL命令语句}
Query1.SQL.Add(\'Select * From Country\');
Query1.SQL.Add(\'Where Name ="ARGENTINA" \');
在为TQuery部件设置SQL属性时调用Close方法总是很安全的,如果TQuery部件已经被关闭了,调用Close方法时不会产生任何影响。在应用程序中为SQL属性设置新的SQL 命令语句时,必须要调用Clear方法以清除SQL属性中现存的SQL命令语句,如果不调用Clear方法,便调用Add方法向SQL属性中设置SQL命令语句,那么新设置的SQL命令语句会追加在现存SQL命令语句后面,在程序运行时常常会出现出乎意料的查询结果甚至程序无法运行下去。
在这里要特别注意的,一般情况下TQuery部件的SQL属性只能包含一条完整的SQL语句,它不允许被设置成多条SQL语句。当然有些数据库服务器也支持在TQuery部件的SQL属性中设置多条SQL语句,只要数据库服务器允许这样,我们在编程时可以为 SQL 属性设置多条SQL语句。
2. 使用Visual Query Builder编写
客户/服务器版本的Delphi还包含一个可视化的查询构造器Visual Query Builder ,用这个可视化的工具我们只能编写Select语句。在应用程序窗体中选择TQuery部件后,单击鼠标右键,弹出一个弹出式菜单,从中选择Run Visual Query Builder后便会弹出一对话框提示你选择要访问的数据库,选择想要访问的数据库之后选择OK按钮,紧接着会出现一个弹出式对话框提示你选择要查询的数据库表,一次可以选择多个数据库表,若要选择多个数据库表,每选择一个表之后单击Add按钮,接着选择另一个表,选择完要查询的表之后单击Close按钮,这样,可视化的查询构造器中将会显示出用户选择的数据库表。
有关如何使用可视化的查询构造器Visual Query Builder 请参看联机帮助信息, 在Visual Query Builder中构造完一个查询并退出Visual Query Builder时,其中的SQL 命令语句会自动地写入相应的TQuery部件的SQL属性。
17.3.2 SQL程序的执行
在为TQuery部件设置完SQL属性的属性值之后,也即编写好适当的SQL程序之后,可以有多种方式来执行SQL程序。
在设计过程中,设置完TQuery部件的SQL属性之后将其Active属性的值置为True,这样便可以执行SQL属性中的SQL程序,如果应用中有与TQuery部件相连的数据浏览部件( 如TDDGrid TDBEdit等)那么在这些数据浏览部件中会显示SQL程序的执行结果。
在应用程序运行过程中,通过程序调用TQuery部件的Open方法或ExecSQL 方法可以执行其SQL属性中的SQL程序。Open方法和ExecSQL方法是不一样的。大家在程序设计过程中一定要注意。Open方法只能用来执行SQL语言的查询语句(Select命令),并返回一个查询结果集,而ExecSQL方法还可以用来执行其它常用的SQL语句(如Insert、UPDATE、 DELETE等命令)例如:
Query1.Open (这样会返回一个查询结果集)
如果调用Open方法,而没有查询结果时,会出错。此时应该调用ExecSQL 方法来代替Open方法。如:
Query1.ExecSQL (没有返回结果)
当然在设计应用程序时,程序设计人员是无法确定TQuery部件中的SQL 语句是否会返回一个查询结果的。对于这种情况应当用Try…Except模块来设计程序。在 Try 部分调用Open方法,而在Except部分调用ExceSQL方法,这样才能保证程序的正确运行。
例如:
Try
Query1.Open
Except
Query1.ExecSQL
End
在应用程序中使用TQuery部件时,还可以设置它的UniDirectional属性为True,这样会加快检索数据库表的速度,但是这样只能往一个方向移动记录指针, 在缺省情况下,UniDirectional属性的值为False。
17.3.3 通过TQuery部件如何获得活动的数据
我们在前面的章节里介绍TTable部件时,我们知道通过TTable部件从数据库中获得的数据都是活动的,也就是说用户可以直接通过数据浏览部件对这些数据进行编辑修改。而通过TQuery部件可以获得两种类型的数据:
● “活动”的数据
这种数据就跟通过TTable部件获得的数据一样,用户可以通过数据浏览部件来编辑修改这些数据,并且当调用Post方法或当焦点离开当前的数据浏览部件时,用户对数据的修改自动地被写回到数据库中,详细情况请参看第四章“数据浏览部件的应用及编程”。
● 非活动的数据(只读数据)
用户通过数据浏览部件是不能修改其中的数据。在缺省情况下,通过TQuery部件获得的查询结果数据是只读数据,要想获得“活动”的数据,在应用程序中必须要设置TQuery部件的RequestLive属性值为True,然而并不是在任何情况下(通过设置RequestLive的属值True)都可以获得“活动”的数据的,要想获得“活动”的数据,除了将TQuery部件的RequestLive属性为True外,BDE要能够返回“活动”的数据,相应的SQL命令语句还要满足附录C中的语法规则和下列的约束条件:
TQuery部件获得“活动”的查询结果数据的约束条件:
当查询Paradox或dBASE数据库中的表:
● 查询只能涉及到一个单独的表
● SQL语句中不能包含ORDER BY命令
● SQL语句中不能含聚集运算符SUM或AVG
● 在Select后的字段列表中不能有计算字段
● 在Select语句WHERE部分只能包含字段值与常量的比较运算,这些比较运算符是: Like,>,<,>=,<=,各比较运算之间可以有并和交运算:AND和OR。
当通过SQL语句查询数据库服务器中的数据库表:
● 查询只能涉及到一个单独的表
● SQL语句中不能包含ORDER BY命令
● SQL语句中不能含聚集运算符SUM或AVG运算
另外,如果是查询Sybase数据库中的表,那么被查询的表中只能有一个索引。
如果在应用程序中要求TQuery部件返回一个“活动”的查询结果数据集,但是SQL 命令语句不满足上述约束条件时,对于本地数据库的SQL查询,BDE只能返回只读的数据集。对于数据库服务器中的SQL查询,只能返回错误的代码。当TQuery 部件返回一个“活动”的查询结果数据集时,它的CanModify属性的值会被设置成True。
表17.1 TQuery部件返回查询结果数据的类型
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
RequestLive属性值 CanModify属性值 查询结果的类型
────────────────────────────────
False False 只读数据
True(SQL语句满足约束条件) True “活动”数据
True(SQL语句不满足约束条件) False 只读数据
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
当TQuery部件返回只读的查询结果数据集,而用户又希望修改这只读的数据集时,一般这样来处理,在应用程序中另外增加一个 TQuery 部件 Query2( 假设获得只读结果的TQuery部件的名字是Query1),在Query2中设置修改语句UpDATE对Query1 中的数据进行修改操作,这样会实现对只读数据的修改。
17.4 动态SQL语句的编程
在17.3节中,我们已经介绍了动态SQL语句(又被称为参数化的SQL语句),在其中包含在程序过程中可以变化的参数,在实际的程序设计中使用得更多的是动态SQL语句,因而在这一节里我们重点介绍如何给动态SQL语句的参数赋值,以在应用程序中灵活地使用SQL语句。动态SQL语句的编写、执行等等与17.3节中介绍的SQL语句的编写、执行是一样的。
动态SQL语句中的参数,我们可以通过两种途径来为它赋值:
1. 利用参数编辑器(Parameter Editor)来为参数赋值
具体方法是:选中TQuery部件,单击鼠标右键,然后从中选择Define Parameters 便可以打开参数编辑器。
例如,在TQuery部件的SQL属性中我们设置如下的SQL语句:
Setect * From Customer Where CustNO=:Number;
TQuery的DatabaseName属性为DBDEMOS,其中Number为参数变量。我们便可以为参数Number赋值,在Datetype组合框中选择该参数的数据类型为整数Integer,在Value编辑框中可以为参数Number赋一个值,也可以单击Null Value检查框为参数Number赋一个空值Null。给参数赋值之后,单击OK按钮,这样TQuery部件中的SQL 查询便准备好了,而且参数值也被赋给了动态SQL语句中相应的参数,此时当把TQuery 部件的Active属性设置成True时,在与TQuery部件相连的数据浏览部件中会显示出查询结果,通过参数编辑器为参数赋值,这种方式缺乏应有的灵活性,在实际应用中用得较少,在实际应用中程序设计人员希望用更灵活方便的方式为参数赋值,那就是我们接下来要介绍的另一种途径:
2. 在运行过程中,通过程序为参数赋值
用这种方式为参数赋值有三种方法:
①根据参数在SQL语句中出现的顺序,设置TQuery部件的Params属性值为参数赋值。
②直接根据SQL语句中各参数的名字,调用ParamByName方法来为各参数赋值。
③将TQuery部件的DataSource属性设置为另一个数据源,这样将另一个数据源中与当前TQuery部件的SQL语句中的参数名相匹配的字段值赋给其对应的参数。
这三种方法我们将在下面的三小节中具体地介绍
17.4.1 使用Params属性为参数赋值
TQuery部件具有一个Params属性,它们在设计时不可用,在程序运行过程中可用,并且是动态建立的,当为TQuery部件编写动态SQL 语句时, Delphi 会自动地建立一个数组Params,数组Params是以0下标开始的,依次对应动态SQL 语句中的参数, 也就是说动态SQL语句中第一个参数对应Params[0],第二个参数对应params[1],依此类推。
例如:一个TQuery部件Query1,我们为它编写的动态SQL语句是:
Insert Into Customer(CustNo,Name,Country)
Values(:CustNo,:Name, : Country)
对于上述这条动态SQL语句中的参数,我们可以利用TQuery部件的params 属性为参数赋值:
Query1.params[0].AsString := "1988";
Query1.params[1].AsString := "Lichtenstein";
Query1.params[2].AsString := "USA";
上述语句将把"1988"赋给参数:Cuse_No,"Lichtenstein"赋给参数:Name,"USA"赋给参数:Country。
17.4.2 使用ParamByName方法为参数赋值
ParamByName是一个函数,用动态SQL语句中的参数作为调用ParamByName函数的参数,这样便可以为它们赋值,使用这种赋值方法,必须要知道动态SQL语句参数的名字。
例如在17.4.1节中的例子中,也可以用下述方法给参数赋值:
Query1.ParamByName(\'CustNo\').AsString := "1988";
Query1.ParamByName(\'Name\').AsString := "Lichtenstein";
Query1.ParamByName(\'Country\').AsString := "USA";
使用这种方法同样可以为各参数赋值,而且更加直观一些。
17.4.3 使用Datasource属性为参数赋值
上述两种方法的共同特点是:我们在为各参数赋值时,我们是知道各参数对应的具体参数值的。而在具体的应用程序中,有些参数值常常是无法确定的,例如参数值来自于另一个查询结果,对于这种情况,Delphi提供了使用Datasource属性为动态SQL 语句中尚存在没有赋值的参数时, Delphi 会自动检查 TQuery 部件的 Datasource 属性, 如果为Datasource属性设置了属性值(该属性的值是另一个TDatasource部件的名字),Delphi 会把没有赋值的参数与TDatasource部件中的各字段比较,Delphi 会将相应的字段值赋给与其相匹配的参数,利用这种方法也能实现所谓的连接查询,我们在学习使用TTable部件时,便会创建主要--明细型数据库应用,用TQuery部件创建的连接查询与主要- -明细型应用是相似的。
例如:在如图17.7所示的应用中,设置了下列部件:
● 一个TTable部件
名字为Cust,它的DatabaseName属性为DEMOS,TableName属性为Customer。
● 一个TDatasource部件
名字为Custsource,其Dataset属性被设置为Cust。
● 一个TQuery部件
名字为ORDERS,其DatabaseName被设置为DEMOS,SQL属性值为:
Select Orders.CustNo,Orders.OrderNo,Orders.SaleDate FROM Orders
WHERE Orders.CustNo =: CustNo
ORDERS的DataSouce属性被设置为CustSource
● 一个TDatasource部件
名字为OrderSource,其DataSet属性被设置为Orders。
● 两个TDBGrid部件
它们分别连接CustSource和OrderSource。
TQuery部件Orders中的动态SQL语句中的参数:CustNo在程序设计过程中没有给它赋值,当该应用程序运行时Delphi会自动地到其Datasource属性中说明的数据源CustSource中查找与参数:CustNo匹配的字段,而CustSource中正好有一个名字为 CustNo 的字段与参数:CustNo匹配,这样Customer表中的CustNo字段值被赋给了参数 : CustNo , 而当每移动Customer表中的记录指针,参数:CustNo的值会随之改变,而参数:CustNo的值发生改变时,Orders中的动态SQL语句会根据新的参数值重新查询,从数据库表中获取相应的订单数据,这样也变实现了类似于主要--明细型应用。即连接查询。
17.4.4 Prepare方法的使用
在使用动态SQL语句编程时,常常用到一个很重要的方法prepare,调用prepare 方法之后,Delphi会将带参数的SQL语句传送给与其对应的数据库引擎,对动态SQL语句进行语法分析和优化。虽然在用动态SQL语句编程时,调用prepare方法并不是必须的,但是这里我们要极力推荐调用prepare方法,因为调用prepare方法后,会大大提高动态SQL 语句的执行性能,特别是当要反复多次执行同一条动态SQL语句时,其优越性会更加明显。如果在应用程序中执行一条SQL语句之前并没有显式地调用prepare方法,每次在执行SQL 语句时,Delphi会隐含地调用propare方法以准备这个查询。
TQuery部件还有一个prepare属性,这是一个布尔型属性,当其属性值为True时,表明该查询已被准备好了( SQL 语句已被传送到数据库引擎中 ) ,当我们使用参数编辑器Parameters Editor来为动态SQL语句中的参数赋值时,当设置完相应的参数值并退出参数编辑器时,Delphi会隐含地调用prepare方法以准备好查询。
当SQL语句执行完之后,要想准备下一个查询,首先必须调用close方法,然后才能调用prepare方法准备下一个查询。一般来说,在一个应用程序中应该调用一次prepare方法,常常在窗体的OnCreate事件处理过程中调用prepare方法,然后用上述介绍的方法为参数赋值,最后调用Open方法或ExecSQL方法执行SQL语句,以完成查询。
当然在调用prepare方法准备好一个查询时,会消耗一些数据库资源,因而每当一个查询执行完毕之后,要养成调用Unprepare方法以撤消查询的好习惯。在运行程序过程中,通过程序改变TQuery部件的SQL属性值时,Delphi会自动地调用Close方法和Unprepare 方法,以撤消查询。
17.5 SQL编程实例
我们在学习了SQL程序的编写方法之后,我们便可以着手创建自己的应用程序了,通过创建应用程序我们对Delphi的强大功能就会有更深刻的印象,同时会进一步全面掌握有关SQL编程的知识,在本节中我们主要介绍两个例子,前一个例子主要是用静态的SQL语句编程,后一个例子是用动态SQL语句编程。
17.5.1 设计简单的SQL程序编辑器
例17.1:在这个例子中,我们设计一个交互式的SQL程序编辑器,在这个编辑器中,我们可以根据SQL语言的语法规则,编写常用的SQL命令,并通过单击编辑器中的有关的按钮,直接执行编写好的SQL命令,SQL命令的执行结果也会及时地通过一个TDBGrid 部件显示出来。
表17.3 SQL编辑器中个主要部件的属性
━━━━━━━━━━━━━━━━━━━━
部 件 属 性 值
────────────────────
Form1 Caption=SQL程序编辑器
DBGrid1 DataSource=DataSource1
Button1 Caption=执行(&E)
Button2 Caption=清除(&C)
Button3 Caption=退出(&X)
Button3 kind=bkClose
Memo1
DataSource1 DataSet=Query1
Query1 DatabaseName=DEMOS
━━━━━━━━━━━━━━━━━━━━
因为我们在设置Query1的DatabaseName属性时将其设置为DEMOS,所以我们设计的这个SQL程序编辑器只能对DEOMS中的数据库表进行操作。
单击按钮Button1的事件处理过程代码为:
程序清单17.1
procedure TForm1.Button1Click(Sender:TObject);
begin
Query1.close;
Query1.SQL.clear;
Query1.SQL.Add(Memo1.text);
Query1.Open;
end;
单击按钮Button2的事件处理过程为:
程序清单17.2
procedure TForm1.Button2Click(Sender:TObject);
begin
Query1.close;
Query1.SQL.clear;
Query1.ExceSQL;
end;
下面我们对程序清单17.1和程序清单17.2中的程序代码进行简要的分析:
程序清单17.1中的程序代码是用来执行查询的。
Query1.close;
这一行程序是用来关闭Query1的,我们在前面的章节中介绍过,只有在调用close 方法将TQuery部件关闭之后,才能修改其SQL属性值,执行close命令关闭查询是很安全的,如果查询已经被关闭了,调用该方法不会产生任何影响。
Query1.SQL.clear;
因为TQuery部件的SQL属性只能包含一条SQL语句,调用Clear 方法的目的是为了清除SQL属性原来的属性值即原来的SQL命令语句,如果不调用clear方法清除原来的SQL命令语句,当在后面的程序中调用Add方法为SQL属性设置新的SQL命令语句时,Delphi 会将新的SQL命令语句加在原来的SQL命令语句,这样使得SQL属性中包含两条独立的SQL语句,这是不允许的。
Query1.SQL.Add(Memo.text);
该条命令是将SQL编辑器的编辑区内的内容(TMemo部件Memo1)设置成Query1的SQL属性值。
Query1.open;
该语句用来执行Query1中的SQL命令语句,如果执行查询从数据库中获得查询结果,查询结果会在数据网格DBGrid1中显示出来。
程序清单2是用来清除查询的,其前两行语句跟程序清单1中的代码是一样的。Query1.ExecSQL有一些特别,调用ExecSQL方法也是打开Query1,ExecSQL方法与open方法不一样的,请参看前面的章节,当Query1中SQL属性值为空时,即没有SQL语句时,只能调用ExecSQL方法来打开Query1,如果调用 open 方法会返回一个错误。 在执行完 Query1.ExecSQL语句之后,应用程序将会清除数据网格DBGrid1中的所有内容。
17.5.2 设计一个数据库查询器
例17.2:在数据库查询器中,用户可以选择要查询的数据库,查询数据库中的那一个表、根据数据库表中那一个字段进行查询,并且可以方便地指定查询条件,指定查询条件主要包括指定逻辑运算符(=、>、<、<=、>=、like、in、NOT like、NOT in)和字段值。
例子全部的程序清单如下:
unit main;
interface
uses
SysUtils, Windows, Messages, Classes, Graphics, Controls,
Forms, Dialogs, StdCtrls, DB, DBTables, Buttons, ComCtrls, Tabnotbk;
type
TQueryForm = class(TForm)
BitBtn1: TBitBtn;
DataSource1: TDataSource;
Table1: TTable;
GroupBox1: TGroupBox;
CheckBox1: TCheckBox;
CheckBox2: TCheckBox;
PageControl1: TPageControl;
TabSheet1: TTabSheet;
Label5: TLabel;
Label1: TLabel;
Label2: TLabel;
Label3: TLabel;
Label4: TLabel;
ListBox1: TListBox;
ListBox2: TListBox;
ListBox3: TListBox;
Edit1: TEdit;
ComboBox1: TComboBox;
BitBtn2: TBitBtn;
TabSheet2: TTabSheet;
Memo1: TMemo;
procedure FormCreate(Sender: TObject);
procedure ListBox1Click(Sender: TObject);
procedure ListBox2Click(Sender: TObject);
procedure BitBtn2Click(Sender: TObject);
end;
var
QueryForm: TQueryForm;
implementation
{$R *.DFM}
uses RSLTFORM;
procedure TQueryForm.FormCreate(Sender: TObject);
begin
Screen.Cursor := crHourglass;
{ Populate the alias list }
with ListBox1 do
begin
Items.Clear;
Session.GetAliasNames(Items);
end;
{ Make sure there are aliases defined }
Screen.Cursor := crDefault;
if ListBox1.Items.Count < 1 then
MessageDlg( \'There are no database aliases currently defined. You
need at least one alias to use this demonstration.\',
mtError, [mbOK], 0 );
{ Default the drop-down list to the first value in the list }
ComboBox1.ItemIndex := 0;
end;
procedure TQueryForm.ListBox1Click(Sender: TObject);
var
strValue: string; { Holds the alias selected by the user }
bIsLocal: Boolean; { Indicates whether or not an alias is local }
slParams: TStringList; { Holds the parameters of the selected alias }
iCounter: Integer; { An integer counter variable for loops}
begin
{ Determine the alias name selected by the user }
with ListBox1 do
strValue := Items.Strings[ItemIndex];
{ Get the names of the tables in the alias and put them in the
appropriate list box, making sure the user\'s choices are reflected
in the list. }
ListBox2.Items.Clear;
Session.GetTableNames(strValue, { alias to enumerate }
\'\', { pattern to match }
CheckBox1.Checked, { show extensions flag }
CheckBox2.Checked, { show system tables flag }
ListBox2.Items); { target for table list }
{ Make sure there are tables defined in the alias. If not, show an
error; otherwise, clear the list box. }
Screen.Cursor := crDefault;
if ListBox2.Items.Count < 1 then
MessageDlg(\'There are no tables in the alias you selected. Please
choose another\', mtError, [mbOK], 0 );
ListBox3.Items.Clear;
end;
procedure TQueryForm.ListBox2Click(Sender: TObject);
begin
Screen.Cursor := crHourglass;
try
{ First, disable the TTable object. }
if Table1.Active then
Table1.Close;
{ Open the selected table }
with ListBox1 do
Table1.DatabaseName := Items.Strings[ItemIndex];
with ListBox2 do
Table1.TableName := Items.Strings[ItemIndex];
{ Open the table and put a list of the field names in the Fields
list box. }
Table1.Open;
if Table1.Active then
Table1.GetFieldNames(ListBox3.Items);
finally
Screen.Cursor := crDefault;
end;
end;
procedure TQueryForm.BitBtn2Click(Sender: TObject);
var
strAlias, { Alias name selected by the user }
strTable, { Table name selected by the user }
strField, { Field name selected by the user }
strValue, { Field Value entered by the user }
strWhere, { WHERE clause for the user\'s query }
strQuote, { Holds quotes is the query field is text }
strQuery: string; { String used to construct the query }
frmQuery: TResultForm; { The Results form }
type
{ The following type is used with the Type drop-down
list. The text values corresponding with each item is
described in comments, along with the relevant SQL operators. }
etSQLOps = (soNoCondition, { not field conditions: no WHERE clause }
soEqual, { equals: = }
soNotEqual, { is not equal to: <> }
soLessThan, { is less than: < }
soLessEqual, { is less than or equal to: <= }
soMoreThan, { is greater than: > }
soMoreEqual, { is greater than or equal to: >= }
soStartsWith, { starts with: LIKE xx% }
soNoStartsWith, { doesn\'t start with: NOT LIKE xx% }
soEndsWith, { ends with: LIKE %xx }
soNoEndsWith, { doesn\'t end with: NOT LIKE %xx }
soContains, { contains: LIKE %xx% }
soNoContains, { doesn\'t contain: NOT LIKE %xx% }
soBlank, { is blank: }
soNotBlank, { is not blank: }
soInside, { contains only: IN ( xx, yy, zz ) }
soOutside); { doesn\'t contain: NOT IN (xx, yy, zz) }
begin
{ Initialize the variables needed to run the query }
with ListBox1 do
if ItemIndex = -1 then
raise Exception.Create(\'Can\'\'t Run Query: No Alias Selected\')
else
strAlias := Items.Strings[ItemIndex];
with ListBox2 do
if ItemIndex = -1 then
raise Exception.Create(\'Can\'\'t Run Query: No Table Selected\')
else
strTable := Items.Strings[ItemIndex];
with ListBox3 do
if ItemIndex = -1 then
begin
if ComboBox1.ItemIndex > Ord(soNocondition) then
raise Exception.Create(\'Can\'\'t Run Query: No Field Selected\')
else
strField := \'\';
end
else
strField := Items.Strings[ItemIndex];
if (Edit1.Text = \'\') and
(ComboBox1.ItemIndex > Ord(soNoCondition)) and
(ComboBox1.ItemIndex < Ord(soBlank)) then
raise Exception.create(\'Can\'\'t Run Query: No Search Value Entered\')
else
strValue := Edit1.Text;
{ See if the field being search is a string field. If so, then pad the
quote string with quotation marks; otherwise, set it to a null value. }
if strField <> \'\' then
with Table1.FieldByName(strField) do
if (DataType = ftString) or (DataType = ftMemo) then
strQuote := \'"\' else
strQuote := \'\';
{ Construct the WHERE clause of the query based on the user\'s choice
in Type. }
case etSQLOps(ComboBox1.ItemIndex) of
soNoCondition: strWhere := \'\';
soEqual: strWhere := strField + \' = \' + strQuote + strValue+ strQuote;
soNotEqual: strWhere := strField + \' <> \' + strQuote + strValue +
strQuote;
soLessThan: strWhere := strField + \' < \' + strQuote + strValue +
strQuote;
soLessEqual: strWhere := strField + \' <= \' + strQuote + strValue +
strQuote;
soMoreThan: strWhere := strField + \' > \' + strQuote + strValue +
strQuote;
soMoreEqual: strWhere := strField + \' >= \' + strQuote + strValue +
strQuote;
soStartsWith: strWhere := strField + \' LIKE \' + strQuote +
strValue + \'%\' + strQuote;
soNoStartsWith: strWhere := strField + \' NOT LIKE \' + strQuote +
strValue + \'%\' + strQuote;
soEndsWith: strWhere := strField + \' LIKE \' + strQuote +
\'%\' + strValue + strQuote;
soNoEndsWith: strWhere := strField + \' NOT LIKE \' +
strQuote + \'%\' + strValue + strQuote;
soContains: strWhere := strField + \' LIKE \'+ strQuote+\'%\'+ strValue
+ \'%\' + strQuote;
soNoContains: strWhere := strField + \' NOT LIKE \' + strQuote + \'%\'
+ strValue + \'%\' + strQuote;
soBlank: strWhere := strField + \' IS NULL\';
soNotBlank: strWhere := strField + \' IS NOT NULL\';
end;
if ComboBox1.ItemIndex = Ord(soNoCondition) then
strQuery := \'SELECT * FROM "\' + strTable + \'"\'
else if Table1.FieldByName(strField).DataType = ftString then
strQuery := \'SELECT * FROM "\' + strTable + \'" t WHERE t.\' + strWhere
else
strQuery := \'SELECT * FROM "\' + strTable + \'" t WHERE t.\' + strWhere;
{ Create an instance of the browser form. }
frmQuery := TResultForm.Create(Application);
{ Use a resource protection block in case an exception is raised. This
ensures that the memory allocated for the Results form is released. }
try
with frmQuery do
begin
Screen.Cursor := crHourglass;
if Query1.Active then Query1.Close;
Query1.DatabaseName := strAlias; {set the alias the query poitns to}
Query1.SQL.clear; { empty existing SQL in the query }
Query1.SQL.Add(strQuery); { add query string to query object }
Query1.Active := True; { try to run the query }
Screen.Cursor := crDefault;
if Query1.Active then
begin
{ If the query didn\'t return any records, there\'s no point in
displaying the form. In that event, raise an exception. }
if Query1.RecordCount < 1 then
raise Exception.create(\'No records matched your criteria.
Please try again.\' );
{ write a message to the browse form\'s status line }
if strField = \'\' then
Panel3.Caption := \'Now showing all records from \' + strTable
+ \'...\'
else
Panel3.Caption := \'Now showing \'+ strTable +\' where \'+ strField
+\' contains values equal to \'+ strValue + \'...\';
{ show the form }
ShowModal;
end;
end;
finally
frmQuery.Free;
end;
end;
end.
unit RSLTFORM;
interface
uses
SysUtils, Windows, Messages, Classes, Graphics, Controls, StdCtrls, DB,
Forms, DBCtrls, DBGrids, DBTables, Buttons, Grids, ExtCtrls, Dialogs;
type
TResultForm = class(TForm)
DBGrid1: TDBGrid;
DBNavigator: TDBNavigator;
Panel1: TPanel;
DataSource1: TDataSource;
Panel2: TPanel;
Panel3: TPanel;
Query1: TQuery;
SpeedButton2: TSpeedButton;
Panel4: TPanel;
SpeedButton1: TSpeedButton;
procedure SpeedButton1Click(Sender: TObject);
procedure SpeedButton2Click(Sender: TObject);
end;
var
ResultForm: TResultForm;
implementation
{$R *.DFM}
procedure TResultForm.SpeedButton1Click(Sender: TObject);
begin
Close;
end;
procedure TResultForm.SpeedButton2Click(Sender: TObject);
var
strText: string; { Variable to hold display text }
iCounter: Integer; { Loop counter variable }
begin
{ Build a string containing the query }
strText := \'\';
for iCounter := 0 to Query1.SQL.Count - 1 do
strText := strText + Query1.SQL[iCounter];
{ Display the query text }
MessageDlg(\'The underlying query is: \' + #10 + #10 + strText,
mtInformation, [mbOK], 0 );
end;
end.