Oracle PL/SQL程序设计(第6版)(上下册)

978-7-115-44875-0
作者: 【美】Steven Feuerstein(史蒂芬 弗伊尔斯坦) Bill Pribyl(比尔 普里比尔)
译者: 方鑫
编辑: 傅道坤

图书目录:

详情

本书介绍PL/SQL的新特性,包括数据类型和声明、扩展正则表达式、本地化代码生成、性能优化工具、分级优化器、动态SQL优化、PL/SQL表达式序列化等。内容涵盖语法、特性、最佳实践、最优化建议以及扩展代码。

图书摘要

版权信息

书名:Oracle PL/SQL程序设计(第6版)(上下册)

ISBN:978-7-115-44875-0

本书由人民邮电出版社发行数字版。版权所有,侵权必究。

您购买的人民邮电出版社电子书仅供您个人使用,未经授权,不得以任何方式复制和传播本书内容。

我们愿意相信读者具有这样的良知和觉悟,与我们共同保护知识产权。

如果购买者有侵权行为,我们可能对该用户实施包括但不限于关闭该帐号等维权措施,并可能追究法律责任。

• 著    [美] Steven Feuerstein Bill Pribyl

  译    方 鑫

  责任编辑 傅道坤

• 人民邮电出版社出版发行  北京市丰台区成寿寺路11号

  邮编 100164  电子邮件 315@ptpress.com.cn

  网址 http://www.ptpress.com.cn

• 读者服务热线:(010)81055410

  反盗版热线:(010)81055315


Copyright © 2014 by O’Reilly Media, Inc.

Simplified Chinese Edition, jointly published by O’Reilly Media, Inc. and Posts & Telecom Press, 2016. Authorized translation of the English edition, 2014 O’Reilly Media, Inc., the owner of all rights to publish and sell the same.

All rights reserved, including the rights of reproduction in whole or in part in any form.

本书中文简体字版由O’Reilly Media, Inc.授权人民邮电出版社出版。未经出版者书面许可,对本书的任何部分不得以任何方式复制或抄袭。

版权所有,侵权必究。


本书基于Oracle数据库12C,从用PL/SQL编程、PL/SQL程序结构、PL/SQL程序数据、PL/SQL中的SQL、构造PL/SQL应用程序、高级PL/SQL主题这6个方面详细而系统地讨论了PL/SQL以及如何有效地使用它。本书能够帮助你充分利用PL/SQL来解决数据库开发中遇到的各种问题,引导你掌握各种构建应用的技巧和技术,以便使你编写出高效、可维护的代码。本书不但介绍了大量Oracle数据库12C的PL/SQL新性能,还提供了许多优化PL/SQL性能的新方法。

本书结构清晰,示例丰富,实践性强,适用于Oracle数据库开发人员、Oracle数据库管理员等相关数据库从业人员,也可以作为各大、中专院校相关专业师生的参考用书和相关培训机构的培训教材。


O’Reilly Media通过图书、杂志、在线服务、调查研究和会议等方式传播创新知识。自1978年开始,O’Reilly一直都是前沿发展的见证者和推动者。超级极客们正在开创着未来,而我们关注真正重要的技术趋势——通过放大那些“细微的信号”来刺激社会对新科技的应用。作为技术社区中活跃的参与者,O’Reilly的发展充满了对创新的倡导、创造和发扬光大。

O’Reilly为软件开发人员带来革命性的“动物书”;创建第一个商业网站(GNN);组织了影响深远的开放源代码峰会,以至于开源软件运动以此命名;创立了《Make》杂志,从而成为DIY革命的主要先锋;公司一如既往地通过多种形式缔结信息与人的纽带。O’Reilly的会议和峰会集聚了众多超级极客和高瞻远瞩的商业领袖,共同描绘出开创新产业的革命性思想。作为技术人士获取信息的选择,O’Reilly现在还将先锋专家的知识传递给普通的计算机用户。无论是通过书籍出版、在线服务或者面授课程,每一项O’Reilly的产品都反映了公司不可动摇的理念——信息是激发创新的力量。

“O’Reilly Radar博客有口皆碑。”

——Wired

“O’Reilly凭借一系列(真希望当初我也想到了)非凡想法建立了数百万美元的业务。”

——Business 2.0

“O’Reilly Conference是聚集关键思想领袖的绝对典范。”

——CRN

“一本O’Reilly的书就代表一个有用、有前途、需要学习的主题。”

——Irish Times

“Tim是位特立独行的商人,他不光放眼于最长远、最广阔的视野并且切实地按照Yogi Berra的建议去做了:‘如果你在路上遇到岔路口,走小路(岔路)。’回顾过去Tim似乎每一次都选择了小路,而且有几次都是一闪即逝的机会,尽管大路也不错。”

——Linux Journal


我的妻子Vera Silva,她的智慧、力量、

美貌和艺术气质,极大地丰富了我的生活。

——Steven Feuerstein

 

献给我的妻子Norma,在相识1/4个世纪之后,

她仍拥有融化我内心的能力。

——Bill Pribyl


在世界各地,有数百万应用程序开发人员和数据库管理员使用Oracle公司提供的软件创建复杂的系统,以及管理庞大的数据。众多Oracle软件的中心是PL/SQL——一种编程语言,它为Oracle版本的SQL(结构化查询语言)提供过程性功能扩展,并成为Oracle开发工具箱中的编程语言(最有名的开发工具是Forms Developer和Reports Developer)。

在Oracle公司发布的几乎每一个新产品中,PL/SQL都是其中突出的一个支持技术。软件专业人员使用PL/SQL来完成各种编程功能,包括:

PL/SQL是Ada的仿写。Ada是一种高级语言,一种为美国国防部设计的程序语言,它强调数据抽象、信息隐藏以及现代设计策略等关键要素。Oracle明智的设计决策,使得PL/SQL成为一种功能强大的语言,结合了许多程序语言中最先进的元素,包括下面这点。

PL/SQL是紧密集成到Oracle的SQL语言:我们可以直接从我们的过程式程序中执行SQL语句,而无需依赖任何类型的中间应用程序编程接口(API),如Java Database Connectivity(JDBC)或Open Database Connectivity(ODBC)。相对应地,我们也可以在一个SQL语句中调用自己的PL/SQL函数。

想要在21世纪取得成功的Oracle开发者必须学习并充分利用PL/SQL语言。这需要两个阶段:第一,我们必须熟悉和学习使用语言的不断增多的功能集;第二,在掌握了个体特性之后,我们必须学会把这些功能合在一起来创建复杂的程序。

因为这些原因,对于基础PL/SQL语言,Oracle的开发者需要一个坚实的、全面的资源。我们需要知道PL/SQL的基本构建块,并需要通过示例来学习,这样就可以避免一些试错。类似于任何编程语言,PL/SQL有一个正确的方式和很多错误的方式(至少是“非正确的”方式)来处理任何任务。我希望本书能够帮助大家尽可能地以最有效的方式学习如何使用PL/SQL语言。

本书将帮助我们实现如下目标。

充分利用PL/SQL

Oracle的参考手册描述了PL/SQL语言的所有特性,但里面不会告诉我们如何应用这些特性。事实上,在某些情形下,我们可以在复杂的环境中通过摸索和自学,掌握如何使用一个给定的功能。一般的图书和培训课程往往以相同的有限方式照本宣科。而在本书中,我们将超越基础知识,达到语言的高级层面,并探索特殊的方法,以利用特定的功能达到预期的结果。

利用PL/SQL来解决实际问题

我们夜以继日地编写PL/SQL模块,不是为了提高我们的技术水平,而是使用PL/SQL为公司或客户解决问题。在这本书中,作者将尽力帮助大家解决现实世界的问题、开发人员每天面对的问题(至少可以是能够用软件解决的问题)。为了做到这一点,我们在书中添加了示例——不只是小的代码片段,而是大量的应用组件,大家可以在各自的环境中立即使用它们。这本书中有很多很好的代码,在随附的网站中还有更多。在一些内容中,作者会利用示例代码指导大家通过分析来想出解决方案。用这种方式,我们会在大多数的具体任务中看到,如何在特定的任务中应用这些PL/SQL特性,以及这些特性的没有文档说明的潜在功能。

编写高效、易维护的代码

PL/SQL和Oracle的其他产品为惊人的开发效率提供了潜力。而如果我们不够细心,这种潜力也容易让我们自己陷入更深、更黑暗的陷阱。如果本书只能帮助程序员在更少的时间里写出更多的代码,那就失败了。作者的目的是要帮助大家建立开发的技能和技巧来创建应用程序,这样的程序易于适应变化,且更容易理解和维护。本书要教会大家使用全面的策略和代码架构,使得我们以强大、通用的方式利用PL/SQL来解决问题。

本书的作者和O’Reilly Media致力于提供覆盖整个生命周期的PL/SQL语言的全面的、有效的技术。本书的当前版本描述了直到Oracle数据库12c第1版的PL/SQL的特性和功能。在这个版本中,我们把Oracle数据库12c作为基础PL/SQL版本。当然,在适当的情况下,我们也介绍了其他的早期版本引入的(或可用的)具体功能。本书提供了一张表格,列出了不同版本的主要特点(请参阅第1章中1.4节)。

从在Oracle 6数据库中发布1.0版本以来,PL/SQL在之后的多年间已经有了大幅改进。本书也经历了一系列改版以适应PL/SQL不断改进的功能。

在本书的当前版本中,最大的变化是全面涵盖了Oracle数据库12c第1版中新的PL/SQL功能。我们在第1章中对这些主要功能进行了汇总,并给出了详细讨论这些功能的章节信息。

作者对当前版本的内容很满意,希望读者也有同样的感受。这一版本有着比以往任何版本都有更多的信息,但作者认为,我们应继续保持幽默感和轻松的语气,多年来读者告诉我们,这样做可以使书本更易阅读,易于被理解,而且非常有用。

本书的作者不止一人,下面是具体每章的作者列表。在后面的“致谢”部分,对各位作者的贡献进行了详述。

作者

前言

Steven

1

Steven

2

Bill和Steven

3

Steven和Bill

4

Steven、Chip和Jonathan

5

Steven和Bill

6

Steven

7

Chip、Jonathan和Steven

8

Chip、Jonathan和Steven

9

Chip、Jonathan和Steven

10

Chip、Jonathan和Steven

11

Steven

12

Steven和Bill

13

Chip和Jonathan

14

Steven

15

Steven

16

Steven

17

Steven

18

Steven

19

Darryl和Steven

20

Steven

21

Steven和Adrian

22

Bill和Steven

23

Arup

24

Bill、Steven和Chip

25

Ron

26

Bill和Steven

27

Bill和Steven

28

Bill和Steven

本书分为6个部分。

第1部分

从第1章开始介绍PL/SQL是从哪里来的,它有什么好处。作者提供了一个对PL/SQL语言的一些主要特点的快速浏览。第2章可以帮助读者创建PL/SQL程序,并尽可能快地运行它,包括明确的、简单的指令,可以让我们在SQL * Plus和其他一些常见的环境中执行PL/SQL代码。第3章回顾了PL/SQL语言的基础:介绍了PL/SQL语句的构成和块结构,以及如何在PL/SQL中写注释,等等。

第2部分

第4~6章,探讨了PL/SQL语言中的条件(IF和CASE)和顺序(GOTO和NULL)控制语句,循环和CONTINUE语句,以及异常处理。本书这一部分将教大家创建与程序复杂需求相关联的代码块。

第3部分

与我们所写的用来处理数据的每一个程序相关,大部分数据对于PL/SQL过程或函数都是本地(定义)的。第7~13章的侧重点在于我们可以在PL/SQL中定义的各种程序数据的类型,如数字、字符串、日期、时间戳、记录和集合等。读者会了解Oracle 11g数据库引入的新数据类型(SIMPLE_INTEGER、SIMPLE_FLOAT和SIMPLE_DOUBLE),以及许多其他的新版本引入的二进制、日期和时间戳类型。这一部分也涵盖了Oracle所提供的各种内置的功能,可以用来操作、修改数据。

第4部分

第14~16章,介绍了PL/SQL代码结构的一个核心要素——连接到底层数据库,这需要通过SQL语言实现。这一部分将告诉你如何定义事务,以便在数据库中进行更新、插入、合并和删除表,如何在PL/SQL程序中从数据库中查询用于处理的信息,以及如何使用本地动态SQL(NDS)来动态执行SQL语句。

第5部分

在这里,前面所提到的内容被汇到了一起。我们已经知道如何声明和处理变量,已经是错误处理和循环构建的专家。现在,从第17~22章,我们会学习创建程序的模块,其中包括过程、功能、包和触发器,以及如何把信息导入到PL/SQL程序或从导出。第20章讨论管理PL/SQL代码库,包括测试和调试程序,以及管理的依赖关系;它还提供了基于版本的重定义功能的概述,这是在Oracle数据库11g第2版中引入的。第21章的重点在于如何使用各种工具和技术,以在PL/SQL程序以外获得最佳性能。第22章涵盖了PL/SQL的I/O技术、DBMS_OUTPUT(将结果输出到屏幕上)和UTL_FILE(读和写文件),以及UTL_MAIL(发送邮件)和UTL_HTTP(从网页检索数据)。

第6部分

PL/SQL是一种成熟和丰富的语言,其功能是全面的,这些功能我们可能不会每天都使用,但可能是成功和失败的关键。第23章探讨了我们创建PL/SQL程序时所面临的安全挑战。第24章对PL/SQL架构进行了探索,包括PL/SQL对内存的使用。第25章为需要解决全球化和本地化问题的PL/SQL开发人员提供指导。第26章提供了针对Oracle的面向对象的功能的指导(对象类型和对象浏览)。

附录A到附录C,总结了正则表达式语法的细节,以及数字和日期格式。

本书第4版包含的如何在PL/SQL程序中调用Java和C代码的相关知识,已经转移到了本书的网站上。

如果读者是刚开始了解PL/SQL,那么通读本书将会提高你的PL/SQL技能并加深你对语言的理解。如果读者是已经精通PL/SQL的程序员,则可以跳到相应的部分直接采用特定的技术,来立即完成你的程序。无论你使用本书作为教学的指导还是参考,作者都希望它能够帮助你高效地使用PL/SQL。

本书尽管很厚,但它并不是包罗万象的。Oracle的环境是宏大复杂的。在本书中,我们把注意力集中于核心的PL/SQL语言本身,因此以下主题自然不会被涵盖在本书的范围内,除了偶尔或附带提及它们。

SQL语言

本书假定读者已经具有SQL语言的知识,而且知道如何使用SELECT、UPDATE、INSERT、MERGE和DELETE语句。

Oracle数据库管理

数据库管理员(DBA)可以用本书来学习如何写必要的PL/SQL程序以创建和维护数据库,本书不探讨Oracle的SQL的Data Definition Language(DDL)所具有的细微差别。

程序及数据库调优

本书不包括详细的调优内容,虽然在第21章中讨论了许多工具和技术,它们有助于优化我们的PL/SQL程序的性能。

与PL/SQL无关的Oracle工具的具体技术

本书并不试图教大家如何在Oracle工具(如Oracle的Forms Developer)中创建程序,即使使用的语言是PL/SQL。我们集中于核心语言的功能,专注于在数据库中我们利用PL/SQL所能做的工作。然而,几乎本书内包括的所有任务都适用于Forms Developer和 Reports Developer内部的PL/SQL。

 提示 

表示提示、建议或一般的说明。例如,如果某个设置为特定版本所有,作者会告诉大家。

 警告 

表示警告或警示。例如,如果某设置对系统有一定的负面影响,作者会警示大家。

在一般情况下,这本书的所有的讨论和示例适用于任何我们使用的机器和/或操作系统。有些与版本有关的功能——例如,如果我们只能在Oracle数据库11g(或特定的版本,如Oracle数据库11g第2版)中使用它,那么作者会在文本中注明。

PL/SQL有很多版本,我们有时会发现在开发过程中需要使用多个版本。第1章介绍了PL/SQL的各种版本以及我们应该了解的要点,详见1.4节的内容。

本书引用的所有代码可从http://oreil.ly/oracle-plsqlsixth找到。我们也会在网站中找到新版本中被删除或被压缩的早期版本的一些内容。这些将有助于还在使用旧版本Oracle的读者。

关于Steven的所有图书和相关资源的信息可以在http://www.stevenfeuerstein.com找到。我们也可以访问PL/SQL Obsession(Steven Feuerstein的PL/SQL的门户),在那里我们会找到一些可供下载的培训资料、代码等。

要在本书的网站上寻找一个特定的示例,可以通过查找文本中引用的文件名来找到它。在本书中,我们在很多示例开头的注释中列出了文件名,如下所示:

/* File on web: fullname.pkg */

如果读者感兴趣的代码段中没有“文件在Web上”的注释,应该去检索相应章的代码文件。

每章的代码文件包含所有的代码片段和示例,方便我们进行复制和粘贴。这些文件也包含用DDL语句来创建其他对象的代码可能依赖的表。

每一章的代码文件被命名为chNN_code.sql,其中NN为章数。

最后,hr_schema_install.sql脚本将创建标准的Oracle Human Resources示范表,如员工、部门等。书中的示例将会使用这些表。

本书的补充材料(如代码示例、练习等)可从http://oreil.ly/oracle-plsql-sixth下载。

本书的目的是帮助读者完成工作。一般而言,你可以在你的程序和文档中使用本书中的代码,而且也没有必要取得我们的许可。但是,如果你要复制的是核心代码,则需要和我们打个招呼。例如,你可以在无需获取我们许可的情况下,在程序中使用本书中的多个代码块。但是,销售或分发O’Reilly图书中的代码光盘则需要取得我们的许可。通过引用本书中的示例代码来回答问题时,不需要事先获得我们的许可。但是,如果你的产品文档中融合了本书中的大量示例代码,则需要取得我们的许可。

在引用本书中的代码示例时,如果能列出本书的属性信息是最好不过的。一个属性信息通常包括书名、作者、出版社和ISBN。例如,“Oracle PL/SQL Programming, Six Edition by Steven Feuerstein and Bill Pribyl (O’Reilly). Copyright 2014 Steven Feuerstein and Bill Pribyl, 978-1-4493-2445-2.”

在使用书中的代码时,如果不确定是否属于正常使用,或是否超出了我们的许可,请通过permissions@oreilly.com与我们联系。

如果你想就本书发表评论或有任何疑问,敬请联系出版社。

美国:

O’Reilly Media, Inc.

1005 Gravenstein Highway North

Sebastopol, CA 95472

中国:

北京市西城区西直门南大街2号成铭大厦C座807室(100035)

奥莱利技术咨询(北京)有限公司

我们还为本书建立了一个网页,其中包含了勘误表、示例和其他额外的信息。你可以通过如下地址访问该网页:

http://www.oreil.ly/oracle-plsql-sixth

关于本书的技术性问题或建议,请发邮件到:

bookquestions@oreilly.com

欢迎登录我们的网站(http://www.oreilly.com),查看更多我们的书籍、课程、会议和最新动态等信息。

Facebook: http://facebook.com/oreilly

Twitter: http://twitter.com/oreillymedia

YouTube: http://www.youtube.com/oreillymedia

本书在1995年首次出版,作为广大程序员如何使用PL/SQL语言的“帮助”文档,它的发展史是繁忙而富有成效的。在此,作者对所有读者表示感谢。

一直保持本书的定位:PL/SQL程序设计准确、可读性强和及时更新的参考资料,从一开始就是一个艰巨的(嗯,有时甚至是过于艰巨的)任务。没有许多Oracle的专家、朋友、家人的帮助是不可能实现的,当然还有那些在O’Reilly Media工作的非常优秀的伙伴们给予的支持。

我们在下面详细列出了那些帮助完成本书第6版的人员名单并对他们表示感谢。名单的最后,是对早期版本有所帮助的需要感谢的人员。

首先,感谢那些为本书贡献章(节)和/或实质内容的人们,按照名字的字母顺序,他们是Adrian Billington、Chip Dawes、Jonathan Gennick、Ron Hardman、Darryl Hurley和Arup Nanda。对于第6版,Chip Dawe负责更新了6章。Jonathan Gennick在过去的版本中撰写并在新版本中更新了6章。Darryl Hurley已为几个版本更新了关于数据库触发器的内容,并为Oracle的国际化功能贡献了意见。Arup Nanda针对安全控制写出了优秀篇章。Ron Hardman自发地为全球化和本地化写了说明。Adrian Billington在第21章中为管道表函数提供了极好的材料。

这里邀请了每一位撰稿人发表自己的感想。

Adrian Billington是数据库设计、开发和性能调优的专家,自1999以来一直从事Oracle数据库相关的工作。他是oracle-developer.net的幕后人员,这个网站为Oracle开发者提供完整的SQL和PL/SQL的功能、工具和技术。Adrian也是一个Oracle ACE以及OakTable Network的成员。他要感谢James Padfield(Padders)、Tom Kyte和Steven Feuerstein,在他作为Oracle专家产生动摇的时期,这些人鼓励他成为一个更好的开发者。他与他的妻子Anji和3个孩子Georgia、Oliver和Isabella生活在英国。

Chip Dawes在过去的20年里作为一个DBA、开发者、教师和指导者,一直与Oracle数据库技术合作。他目前是PwC的经理,他在那里帮助客户在他们的数据中寻找价值。Chip与妻子和孩子生活在Chicagoland。

Jonathan Gennick(http://gennick.com)是一位经验丰富的技术人员,他以他的Oracle数据库专业知识而闻名。他过去的经验包括软件开发和数据库管理。作为一名开发人员,他一直很喜欢问题诊断和调试。他喜欢用SQL和PL/SQL,以有关这些主题的书和文章而闻名。在他的业余时间里,Jonathan享受低技术的生活方式。他积极地为当地教堂服务,经常与高中以及有时是大学的班级一起投入到圣经学习中,或在讲坛上演讲。他也是一名狂热的山地骑行者,甚至在隆冬时节骑着装上芬兰进口防滑轮胎的拉风自行车。在他的Oracle工作中,他目前正以他自己的方式探索Oracle SQL内置的统计函数。

Ron Hardman是SettleOurEstate.com的创始人,该网站是一个建在Oracle Apex和Oracle Cloud Database上的房产管理解决方案。他也是世界各地Oracle Text与Oracle全球化技术的顾问,他同时作为员工和客户与Oracle合作超过17年。Ron除了技术还喜欢写作,在2010年他的第一部历史小说出版,书名为Shadow Fox: Sons of Liberty,是他与他的女儿合著的。

Darryl Hurley与Oracle技术打交道超过20年,关注于PL/SQL和DBA的工作。他与他的可爱的妻子Vanessa和美丽的女儿Bianca,生活在英属哥伦比亚的里士满。

Arup Nanda从1993以来就任职Oracle DBA,从事这个工作的各个方面——建模、性能故障排除、PL/SQL编码、备份、灾难恢复等。他是一个大公司的首席数据库架构师,写了约500篇文章,合著了5本书,并在各种会议中进行了约300次的演讲。他提供培训课程,从事特殊项目如审计和DR,他在自己的博客arup.blogspot.com上发表Oracle技术文章。他是Oracle Magazine的2003年的年度DBA和2012年的年度设计师。他是一个OCP,一个OTN ACE Director,是OakTable Network的成员。他与他的妻子Anu和儿子Anish住在康涅狄格州。

对于这么厚的一本书,我们需要很多的审查人员,主要是因为需要测试这本书中每一个代码段和程序,以确保在变成印刷本时尽可能少出错误。非常感谢Oracle PL/SQL世界的人们,他们贡献了业余时间来帮助本书成为最好的书。

对于第6版,首先感谢Valentin Nikotin,他是这本书的最好的技术评审员之一。他不仅检查了Oracle 12c数据库的内容准确性,而且还帮助剔除了一些歧义,并为一些关键内容修正了错误。其他的技术评审员也对提高本书的质量有很大的贡献。谢谢Patrick Barel 和Arup Nanda!

接下来,要对Oracle的PL/SQL产品经理Bryn Llewellyn表示深深的谢意,并感谢PL/SQL开发团队的其他成员,尤其是Charles Wetherell。Bryn为Oracle数据库12c的新功能提供了关键信息和反馈,并以无尽的耐心回答了关于PL/SQL的特点的许多问题。毫无疑问,作者对PL/SQL的理解以及在表达的准确性上,要归功于Bryn。

从非Oracle的角度来看,非常感激Joel Finkel,最令人喜欢的万事通,在计算机和软件方面,他弥补了作者的狭隘的专业化,这种专业化虽然有益,但是也限制了作者的知识宽度。

当然,上述都只是技术内容。在我们完成了PL/SQL内容的编写后,是时候轮到在O’Reilly Media的优秀员工——在编辑Ann Spencer的领导下——把我们如此多的内容和代码示例变为一本书,然后在O'Reilly出版。非常感谢Julie Steele(第5版的编辑)、Nicole Shelby(本版的责任印制)、Rob Romano(他创建了优秀的图表),以及其他员工。这是Ann第一次编辑作者的书。对于所有以前的版本(也就是从1994到2007),作者很荣幸和高兴与Debby Russell合作。谢谢,Debby,你多年的努力使O'Reilly Media的整个Oracle系列图书大获成功!

这里我们还要感谢(这种感谢将一直维持下去)许多人,他们对这本书的前5版做出了贡献:Sohaib Abassi、Steve Adams、Don Bales、Cailein Barclay、Patrick Barel、John Beresniewicz、Tom Berthoff、Sunil Bhargava、Jennifer Blair、Dick Bolz、Bryan Boulton、Per Brondum、Boris Burshteyn、Eric Camplin、Joe Celko、Gary Cernosek、Barry Chase、Geoff Chester、Ivan Chong、Dan Clamage、Gray Clossman、Avery Cohen、Robert A. G. Cook、John Cordell、Steve Cosner、Tony Crawford、Daniel Cronk、Ervan Darnell、Lex de Haan、Thomas Dunbar、Bill Dwight、Steve Ehrlich、Larry Elkins、Bruce Epstein、Joel Finkel、R. James Forsythe、Mike Gangler、Beverly Gibson、Steve Gillis、Eric Givler、Rick Greenwald、Radhakrishna Hari、Gerard Hartgers、Donald Herkimer、Steve Hilker、Bill Hinman、Gabriel Hoffman、Chandrasekharan Iyer、Ken Jacobs、Hakan Jakobsson、Giovanni Jaramillo、Dwayne King、Marcel Kratochvil、Thomas Kurian、Tom Kyte、Ben Lindsey、Peter Linsley、Vadim Loevski、Leo Lok、Debra Luik、James Mallory、Raj Mattamal、Andrew McIlwrick、Nimish Mehta、Ari Mozes、Steve Muench、Jeff Muller、Kannan Muthukkaruppan、Dan Norris、Alex Nuijten、James Padfield、Rakesh Patel、Karen Peiser、Fred Polizo、Dave Posner、Patrick Pribyl、Nancy Priest、Shirish Puranik、Chris Racicot、Sri Rajan、Mark Richter、Chris Rimmer、Alex Romankevich、Bert Scalzo、Pete Schaffer、Drew Smith、Scott Sowers、JT Thomas、David Thompson、Edward Van Hatten、Peter Vasterd、Andre Vergison、Mark Vilrokx、Zona Walcott、Bill Watkins、Charles Wetherell、Edward Wiles、Daniel Wong、Solomon Yakobson、Ming Hui Yang和Tony Ziemba。

我的妻子Veva Silva,支持我在软件世界的职业生涯中所走出的每一步,我非常感谢她。我的孩子Christopher Tavares Silva和Eli Silva Feuerstein,容忍了我将本应投入到他们身上的注意力转移到PL/SQL(当他们是青少年时,很欢迎这种转移)当中。最后,我感谢Chris和他可爱、聪明、有创造力的妻子Laure,他们给我带来了第一个孙女Loey Lucille Silva。


本书的第1部分对PL/SQL进行说明,介绍如何创建和运行PL/SQL代码,以及PL/SQL语言的基础知识。在第1章中,我们提出这样的基本问题:PL/SQL从何而来?它能干什么?PL/SQL的主要特性是什么?在第2章中,我们让读者能够尽快上手使用PL/SQL语言,包括清晰、简单的PL/SQL代码执行指导,以及常见的环境说明。第3章中,我们回答了关于语言结构和关键字的常见问题:如何创建一个PL/SQL声明?PL/SQL块结构是什么?如何在PL/SQL代码中添加注释?


PL/SQL是“结构化查询语言的过程化语言扩展”(Procedural Language extensions to the Structured Query Language)的英文缩写。SQL是无处不在的关系型数据库查询和更新使用的语言。Oracle公司引入PL/SQL来克服SQL中的一些短板,以给那些意欲在Oracle数据库上运行关键应用的企业提供一个更完整的编程解决方案。本章介绍PL/SQL的起源、它的不同版本,以及PL/SQL在最新的Oracle版本(Oracle 12c)中新特性的简单汇总,并为PL/SQL开发者提供了一些信息资源和建议。

Oracle的PL/SQL语言有一些决定性的特征,如下所示。

它是高度结构化、可读和易懂的语言

如果我们刚开始学习编程,PL/SQL可以作为一个绝好的开端。我们会发现,PL/SQL语言易于学习,它有丰富的关键字和结构来清晰地表达代码的内容。而如果我们已有其他编程语言的使用经验,则会很容易熟悉PL/SQL的语法。

它是标准的和可移植的Oracle开发语言

如果我们编写了一些PL/SQL过程或函数,并运行在自己的笔记本电脑上的Oracle数据库中,那么我们就可以不做修改(当然,数据库版本要兼容)地把这些过程或函数移植到我们所在公司的数据库中并运行它们。“一次编程,各地运行”是在Java出现之前长期流传的PL/SQL的口头禅。对于PL/SQL来说,“各地”意味着“有Oracle数据库的各个地方”。

它是内嵌式语言

PL/SQL设计的初衷不是作为一种独立的语言,而是在宿主环境中被调用使用。因此,我们可以在数据库中运行PL/SQL程序(当然,通过SQL*Plus接口),或者,我们可以在Oracle Developer form或report(这种方式称为客户端PL/SQL)中定义和执行PL/SQL程序。但我们不能创建一个自己直接运行的PL/SQL可执行程序。

它是高性能、高度集成的数据库语言

近年来,当我们想针对Oracle数据库编写软件时,我们有多种选择:我们可以使用Java和JDBC;我们可以用Visual Basic和ODBC;我们还可以用Delphi、C++等。然而,我们会发现在处理Oracle数据库时,PL/SQL比其他语言更易写出高效率的代码。尤其是,Oracle提供了针对PL/SQL的特定的增强,如FORALL声明,可以把数据库性能提高一个数量级甚至更多。

Oracle一度引领着声明式、非过程性数据库和应用设计的发展,Oracle数据库服务器技术是世界上最先进、最强大、最稳定的关系数据库。它的应用开发工具,如Oracle Forms,通过“在屏幕上作画”的方式,以及它提供的大量默认功能,使得开发者避免了繁重的定制开发工作量,由此极大地提高了开发的效率。

在Oracle早期,SQL的声明方式结合其突破性的关系型技术,足以满足开发者的需要。但随着行业的成熟和人们期望值的提高,人们对SQL有了更高的要求。开发者需要深入到产品的内层,在它们的表单和数据库脚本中构建复杂的公式和异常处理以及规则。

在1988年,Oracle公司发布了Oracle版本6,在关系型数据库技术中迈进了一大步。此版本中有一个叫作“过程性选项”或“PL/SQL”的关键组件,几乎就在同时,Oracle发布了令人期待已久的升级版本SQL*Forms 2.3(Oracle Forms或Forms Developer最初的名字)。在工具方面,SQL*Forms v3第一次纳入了PL/SQL引擎,使得开发者能以自然、简单的方式开发自己的过程性逻辑代码。

PL/SQL最早版本的功能非常有限:在服务器端,我们只能使用PL/SQL来构建“批处理”脚本以及SQL语句,而不能构建模块化的应用或存储业务规则;在客户端,SQL*Formsv 3.0倒是允许我们创建过程和函数,但其对函数的支持并没有对应文档,因此在之后的几年间都没有大量的开发人员在使用它。而且,此版本的PL/SQL不支持数组,不能与操作系统进行交互(输入和输出),它还是一个远远不够成熟的编程语言。

尽管存在局限性,但 PL/SQL 在开发人员社区受到了友好的甚至是热烈的欢迎,他们对在SQL*Forms里实现简单IF语句编码的渴望是强烈的。批处理执行多条SQL语句的需求压倒一切。

当时,少数开发者意识到,在PL/SQL背后的原始动机和主要期望,已经不再止于在像SQL*Forms这样的产品里,能够对控制进行编程的水平。在Oracle数据库和工具的生命周期早期,Oracle公司就意识到,在它们的架构中,存在两个关键弱点:缺乏可移植性和执行权限管理。

对作者这些了解Oracle公司市场及技术战略的人来说,对应用可移植性存在顾虑显得有点奇怪,从20世纪70年代初开始,可移植性就是Oracle解决方案的标志之一。随着PL/SQL的出现,建筑在C语言之上的数据库就运行在许多不同的操作系统和硬件平台上。SQL*Plus和SQL*Forms可以轻松适应不同配置的终端。但尽管有着这些适应性,仍然有一些应用程序需要Oracle提供更复杂的和更细粒度的控制,像它们的宿主语言如COBOL、C和FORTRAN所做的那样。一旦开发者不采用端口中立的Oracle工具,就会导致应用程序不再可移植。

PL/SQL语言曾经(现在也是)的意图是,在完全独立于操作系统的编程工具里,扩大对应用需求的处理能力。现在,Java和其他编程语言也提供类似的可移植性。当然,PL/SQL是这个领域的先锋,它继续支持着开发者们的高可移植性应用代码。

比可移植性更重大的缺陷是执行权限控制。数据库和SQL语言使得我们对任意特定表中数据的访问和变更有着完全的控制,例如,用GRANT命令,我们可以限定只有特定的角色和用户可以对一个给定表进行UPDATE操作。而另一方面,这个GRANT命令,却没法保证让用户按照正确的顺序对一个或多个表进行变更,而通常大多数商业规则都对交易顺序有所要求。

PL/SQL语言在逻辑交易处理上,能够提供严格控制和管理。PL/SQL实现其控制和管理的方法之一,就是实施了执行权限控制。我们不再对一个角色或用户授权以允许其对表进行更改,取而代之的,是只授权其执行一个过程,由这个过程控制并提供对其内部隐含的数据结构的访问。过程的所有者是数据库中的另一个schema(程序的“定义者”),这个schema被真正赋予了过程执行时所涉及的表的修改权限。由此,过程成为了交易的“守门员”。一个程序要执行某项交易,唯一的方法是调用这个过程,通过这个方法,整个应用交易的完整性得到了保证。

从Oracle数据库8i开始,Oracle通过AUTHID子句,给PL/SQL执行权限控制模块增加了相当的灵活性。有了AUTHID,我们不但可以在前面描述的模式下继续运行我们的程序,我们也可以选择使用AUTHID CURRENT_USER(调用者的权限),在这种情况下,程序是运行在调用者(当前)schema的权限之下的。这里我们只是将调用者权限作为一个示例,来展示PL/SQL随着时间的推移,如何变得成熟以及更灵活。

SQL虽然强大,但仍然无法提供开发一个完整应用程序所需的灵活性和强大功能。而Oracle的PL/SQL语言,能够确保我们在独立于操作系统的Oracle环境中,开发出满足用户需求的高效率应用程序。

PL/SQL是低调开始的,在PL/SQL 1.0时,这样的情形屡见不鲜:开发者告诉他们的经理“PL/SQL做不了这个”。现在,这句话已经从事实变成了托词。如果我们曾经接到某个需求并发现“PL/SQL做不了”,请不要立刻去向老板汇报,而是先对PL/SQL进行深度挖掘,或搜寻Oracle提供的PL/SQL程序包。时至今日,PL/SQL已经基本能够实现任何我们想要的功能。

多年来,Oracle公司展示了它对PL/SQL,其编程语言旗舰的承诺。随着每个新数据库版本的发布,Oracle也对PL/SQL语言本身进行了稳步的、重大的改进。它自带(内置)了大量程序包,用不同的方法和从各个角度,对PL/SQL进行了功能扩充。它引入了面向对象的能力,实施了一系列类似数组的数据结构,增强了编辑器,可以对代码进行优化和对代码质量、性能进行告警,在总体上对语言的深度与广度做了提升。

本章的下一节将展示一些PL/SQL的示例,使我们了解并熟悉基本的PL/SQL程序设计。

如果你是个编程新手,或完全未接触过PL/SQL(或SQL),那么学习PL/SQL可能看起来令人生畏。如果是这样,请不要担心,我相信你会发现它比想象的要简单。作者的乐观依据包括以下两点。

下面,让我们看一些示例,从结构和功能两个方面展示PL/SQL的关键要素。

PL/SQL的一个最重要的表现就是其与SQL的紧密集成。我们不必依赖其他中间软件作为“胶水”去运行PL/SQL中的SQL语句,如ODBC(开放数据库连接)或JDBC(Java数据库连接)。相反,我们只需在代码中插入UPDATE或SELECT语句,如下所示:

 1  DECLARE
 2     l_book_count INTEGER;
 3
 4  BEGIN
 5     SELECT COUNT(*)
 6       INTO l_book_count
 7       FROM books
 8      WHERE author LIKE '%FEUERSTEIN, STEVEN%';
 9
10     DBMS_OUTPUT.PUT_LINE (
11        'Steven has written (or co-written) ' ||
12         l_book_count ||
13         ' books.');
14
15     -- Oh, and I changed my name, so...
16     UPDATE books
17        SET author = REPLACE (author, 'STEVEN', 'STEPHEN')
18      WHERE author LIKE '%FEUERSTEIN, STEVEN%';
19  END;

让我们仔细看看下面表中对代码的分析。

行  号

说  明

1~3

这是所谓“匿名”PL/SQL块的声明部分,作者(即“我”)在其中声明了一个整型变量,以存储我编写或合编的书的数量(第3章将更详细地讲解PL/SQL块结构)

4

BEGIN关键字,表示执行部分的开始——当我把“块”传递给SQL*Plus时

5~8

我运行一个查询,得到我编写或合编的书的数量。第6行最有意思:INTO子句严格地说并不是SQL语句的一部分,而是作为数据库到本地PL/SQL变量的桥梁

10~13

我利用DBMS_OUTPUT.PUT_LINE内置过程(Oracle提供的DBMS_OUTPUT软件包中的一个过程)来显示书的数量

15

一个单行注释行,解释UPDATE的目的

16~18

我决定把我名字的拼写改成“Stephen”,所以我对书本表进行了一个修改。我利用内置的REPLACE函数,把所有“STEVEN”替换成了“STEPHEN”

PL/SQL 提供了全方位的语句,使得我们可以严格控制程序中哪些行会被执行。这些语句如下所示。

IFCASE语句

它们进行逻辑条件判断,例如“如果一本书的页数多于1000,则……”

一个完整的循环或迭代控制

它们包括FOR循环、WHILE循环,以及简单循环。

GOTO语句

是的,PL/SQL甚至还提供了一个GOTO语句,使得我们可以无条件地将执行跳转到程序的另一个部分。然而,这并不意味着,我们当真应该去“使用”这个功能。

下面的存储过程(一个可以根据名字被调用,可重复使用的代码块),演示了前面提到的一些特性:

 1  PROCEDURE pay_out_balance (
 2     account_id_in IN accounts.id%TYPE)
 3  IS
 4     l_balance_remaining NUMBER;
 5  BEGIN
 6     LOOP
 7        l_balance_remaining := account_balance (account_id_in);
 8
 9        IF l_balance_remaining < 1000
10        THEN
11           EXIT;
12        ELSE
13           apply_balance (account_id_in, l_balance_remaining);
14        END IF;
15     END LOOP;
16  END pay_out_balance;

在下面的表格中,我们对上述代码做了详细分析。

行  号

说  明

1~2

为过程的头部,指明过程的名称(其意义是用账户余额来支付账单)。第2行是过程的参数列表,在本例中,它是一个输入值,表示账户的识别号

3~4

过程的声明段。请注意,我们没有像之前的例子那样使用DECLARE这个关键字,而是使用了IS(或AS)作为关键字来分开头部和声明

6~15

一个简单循环的示例,这个循环依赖EXIT语句(第11行)来终止循环;而FOR循环和WHILE循环则要分别指明终止条件

7

这里调用了一个叫作account_balance的函数,来获取对应账号的余额。这本身又是一个示例,演示如何在一个可复用的程序块里调用另一个可复用程序块;第13行演示了如何在本过程里调用另一个过程

9~14

这是一个IF语句,其含义是:如果账户余额少于1000美元,则不再用此账户来支付账单;否则,下次账单支付时,还用此账户

无论是发现错误还是处理错误,PL/SQL语言都有一个强大的机制。在下面的过程中,我们根据ID获得一个账户的名称和余额,随后判断余额是否过少,如果过少,就要显式地引发异常,终止过程的继续执行:

 1  PROCEDURE check_account (
 2     account_id_in IN accounts.id%TYPE)
 3  IS
 4     l_balance_remaining       NUMBER;
 5     l_balance_below_minimum   EXCEPTION;
 6     l_account_name            accounts.name%TYPE;
 7  BEGIN
 8     SELECT name
 9       INTO l_account_name
10       FROM accounts
11      WHERE id = account_id_in;
12
13     l_balance_remaining := account_balance (account_id_in);
14
15     DBMS_OUTPUT.PUT_LINE (
16        'Balance for ' || l_account_name ||
17         ' = ' || l_balance_remaining);
18
19     IF l_balance_remaining < 1000
20     THEN
21        RAISE l_balance_below_minimum;
22     END IF;
23
24  EXCEPTION
25     WHEN NO_DATA_FOUND
26     THEN
27        -- No account found for this ID
28        log_error (...);
29        RAISE;
30     WHEN l_balance_below_minimum
31     THEN
32        log_error (...);
33        RAISE VALUE_ERROR;
34  END;

在下面的表格中,我们详细地对代码的异常处理进行了分析。

行  号

说  明

5

我们声明了自己定义的“异常”,取名为l_balance_below_minimum。Oracle提供了一组预先定义的异常,比如DUP_VAL_ON_INDEX,但在本例中,我们需要为自己的应用定义一些特定的异常

8~11

此查询用以获取账户名称。如果此ID没有对应账户,则数据库返回预先定义的NO_DATA_FOUND异常,并停止程序运行

19~22

如果余额太少,则意味着此账户出现严重问题,这时显式地报告我们自定义的异常

24

EXCEPTION关键字引导程序直接跳转到执行段的末端,即异常段的开端,在这里,进行针对异常的处理

25~28

这一段是异常处理段,进行ID找不到对应账户时的出错处理。如果异常是NO_DATA_FOUND,则错误被捕获,并被log_error过程进行记录。随后,同样的异常被再次报告,外层的程序块得到“ID没有对应账户”的信息

30~33

异常处理段,对应的是账户余额太少的错误(我们为应用定义的特定异常)。如果l_balance_below_minimum被报告,它会被捕捉并记录。随后系统定义的VALUE_ERROR异常被报告,以通知外层程序块

第6章会带我们全面了解PL/SQL的异常处理机制。

当然,关于PL/SQL,我们还有许多可说的——这就是为什么我们这本书有现在这么厚了!前面的几个示例,应该已经给了我们一些感觉:PL/SQL代码长什么样,它的一些最重要的语法元素,以及它的易读与易写。

每个版本的Oracle数据库,都有对应于它的PL/SQL版本,使用越新的PL/SQL版本,就能获得越多的功能。作为PL/SQL程序员,我们遇到的最大挑战就是要不断跟随其发展。我们需要不断地学习每个版本中的新特性——掌握如何使用它们,如何把它们实施到我们的应用中去,以及判断哪些新技术非常有用,我们需要修改现有应用程序以使用它们。

表1-1总结了数据库中PL/SQL每个版本(过去和当前)的主要元素(请注意,在早期数据库版本中,PL/SQL版本号与数据库版本号是不同的,自从Oracle数据库8以后,它们的版本号开始保持一致)。该表格高度概括地列出了每个版本中出现的新特性。在表1-1之后,我们会稍微具体地列出,在最新的Oracle版本Oracle数据库12c中,PL/SQL的“新特点”。

 提示 

Oracle Developer产品套装,也有自己对应的PL/SQL版本,通常会落后于数据库中的PL/SQL版本。本章(和作为一个整体的本书)侧重在服务器端的PL/SQL编程。

表1-1 Oracle数据库和相应的PL/SQL版本

Oracle数据库版本

PL/SQL对应版本的显著特点

6.0

PL/SQL的最早版本(1.0),主要在SQL*Plus中被当作脚本语言使用(这个版本还不能创建命名的、可复用、可被调用的程序),也在SQL*Forms 3中被当作编程语言使用

7.0

PL/SQL 2.0在1.0版本基础上有了很大的提升,增加了对存储过程、函数、程序包、程序员定义记录、PL/SQL表(现在称为集合)及许多扩展程序包的支持

7.1

此PL/SQL版本(2.1)支持程序员定义子类型;允许在SQL语句中使用存储函数;并使用DBMS_SQL程序包提供动态SQL。在PL/SQL 2.1版本中,我们可以从PL/SQL程序内部执行SQL DDL语句

7.3

此PL/SQL版本(2.3)提供了增强的集合功能;提高了远程依赖关系管理;通过UTL_FILE程序包,增加了文件的I/O能力;完整地实施了游标变量功能

8.0

新的版本号(8.0)反映出Oracle努力在相关的产品之间进行版本号的统一。PL/SQL 8.0能够支持在Oracle 8数据库中出现的功能增强,包括大对象(LOB)、面向对象的设计和开发、集合(数组和嵌套表),以及Oracle/Advanced Queuing(Oracle/AQ)

8.1

Oracle数据库8.1是Oracle i系列的第一个版本,对应的PL/SQL版本提供了一组非常引人注目的新增功能,其中包括:动态SQL的新版本;在数据库中支持Java;调用者权限模型;执行授权选项;自治事务;以及高性能的“批量”DML和查询

9.1

Oracle数据库9i第1版追随着前一版的脚步,很快就推出了。这个版本的首次发布包括了对象类型的集成、表函数和游标表达式(允许PL/SQL函数并行执行)、多级集合、CASE语句和CASE表达式

9.2

Oracle数据库9i第2版,把重点放在了XML(Extensible Markup Language,可扩展标记语言)上,同时也增强了对PL/SQL开发者的支持,包括关联数组(除了可以用整数做索引外,还可用作VARCHAR2字符串索引)、基于记录的DML(例如,允许使用一条记录执行一个插入),以及对UTL_FILE进行了很多改进(允许在PL/SQL程序内读写文件)

10.1

Oracle数据库10g第1版在2004年推出,增强了数据库的自主管理功能,重点支持网格计算。从PL/SQL的角度看,最重要的新特性是优化了编译器和编译时的告警,并直接地提供给开发人员

10.2

Oracle数据库10g第2版在2005年问世,为PL/SQL开发者提供了少量的新功能,最显著的提高是开始支持预处理器语法,允许根据布尔表达式的定义对程序不同分支进行编译

11.1

Oracle数据库11g第1版于2007年发布,对于PL/SQL开发者而言,最重大的新特性在于函数结果缓存;同时还有其他一些诱人的改进,诸如组合触发器、CONTINUE语句、产生机器码的本地编译等

11.2

Oracle数据库11g第2版在2009年秋天推出,整体而言,最重要的新特性就是基于版本的重新定义功能,它允许管理员能够对正在被用户使用的程序进行“热修补”

12.1

Oracle数据库12c在2013年6月问世。它对程序单元和视图的访问及管理权限做了诸多优化;对SQL和PL/SQL语言进行了更多的一致性调整,尤其针对VARCHAR2类型的最大长度和动态SQL绑定方面;新的版本支持在SQL语句中定义简单的函数;增加了UTL_CALL_STACK过程包;对堆栈的访问、错误堆栈和错误的回溯进行细粒度更高的权限控制

Oracle数据库12c提供了一些新特性以提高PL/SQL的性能和易用性,同时也对原语言的粗略之处进行了雕琢。下面我们总结出了对于PL/SQL开发者而言,最重要的一些变化。

在PL/SQL-to-SQL接口处,增加了更多的仅适用于PL/SQL的数据类型

在12.1版本之前,我们不能将PL/SQL特有的数据类型(如关联数组)放到一个动态的SQL语句中,但12.1版本中,允许在匿名过程块、SQL查询的函数调用,CALL语句、SQL语句中的表操作中绑定PL/SQL特有数据类型的值。

ACCESSIBLE_BY子句

我们现在可以在包声明中包括ACCESSIBLE_BY子句,用以指定哪些程序单元可以调用过程包中的子程序。这个特性,使得我们可以在帮助程序包中“公开”那些只能由特定程序单元调用的子程序。此特性为程序包提供了一种类似“白名单”的功能。

隐式语句结果

在Oracle数据库12c之前,一个PL/SQL存储子程序的结果,是通过SQL查询的OUT REF CURSOR参数或RETURN子句显式地返回的。客户端的程序则需要显式地绑定这些参数,以便收到结果集。而现在,一个PL/SQL存储过程子程序可以隐式地将查询结果返回到客户端,不再使用OUT REF CURSOR参数,而是使用PL/SQL中的DBMS_SQL程序包。此功能将使那些需要从存储过程子程序(Transact SQL等语言支持的)中得到查询结果的应用程序,轻松地从第三方数据库平台迁移到Oracle数据库平台上。

BEQUEATH CURRENT_USER视图

在Oracle 12c数据库之前的版本中,视图是被作为一个定义者的权限单元(AUTHID DEFINER)来进行访问控制的,即使它是在调用者的权限单元中被调用的(AUTHID CURRENT_USER)。而在新的版本中,一个视图既能是BEQUEATH DEFINER(默认设置),表现为定义者的权限单元,也能是BEQUEATH CURRENT_USER,即类似调用者权限单元的表现。

给程序单元定义角色

在Oracle数据库12c之前的版本中,一个调用者的权限单元,总是以调用者的权限进行运行的。如果它的调用者的权限大于它的所有者,在这样的情况下,程序单元的运行就可能越过所有者预设的边界,造成意想不到的后果。

在12.1版本中,我们可以给单个PL/SQL过程包甚至是里面单独的子程序定义角色,我们不是使用定义者权限单元,而是创建一个调用者权限单元,然后将角色赋给它。调用者权限单元就在调用者和角色的权限规定之下运行,不再拥有从定义者schema下继承的任何其他权限。

现在,只有当它的所有者对调用者拥有INHERIT PRIVILEGES权限或INHERIT ANY PRIVILEGES权限时,一个调用者权限单元才能够在它的调用者的权限范围内运行。

 提示 

在安装或升级时,INHERIT PRIVILEGES权限就被授予了所有的schema。

新增的条件编译指令

在12.1版本中,Oracle新增了两个预定义的查询指令,$$PLSQL_UNIT_OWNER和$$PLSQL_UNIT_TYPE,它们的返回值是当前PL/SQL程序单元的所有者和类型。

在SQL中优化函数的执行

在SQL语句中,Oracle目前提供两种提高PL/SQL函数执行性能的方法:我们可以用WITH语句,直接在SQL语句中定义函数;或者我们可以在程序单元中增加UDF编译指示,告知编译器此函数将会主要在SQL语句中使用。

在不可见列使用%ROWTYPE

Oracle 12c数据库中,我们可以定义“不可见”列。在PL/SQL内部,已经对%ROWTYPE这种列的各种属性有了对应的处理方法。

FETCH FIRST子句和BULK COLLECT

在12.1版本中,我们可以使用FETCH FIRST子句,来限制一个查询返回的行的数量,相比通常使用“TOP N”来进行限定的查询,可以显著降低SQL的复杂度。FETCH FIRST最大的益处在于,当从第三方数据库平台迁移应用到Oracle数据库平台时,可以极大地简化迁移难度。FETCH FIRST子句也可以帮助提高一些SELECT BULK COLLECT INTO语句的性能。

UTL_CALL_STACK过程包

在12.1版本之前,DBMS_UTILITY过程包提供3个函数(FORMAT_CALL_STACK、FORMAT_ERROR_STACK和FORMAT_ERROR_BACKTRACE)来提供程序运行调用堆栈、错误堆栈和错误回溯的对应信息。在12.1版本中,新的过程包UTL_CALL_STACK不仅可以提供相同的功能,同时对访问字符串的内容进行了更细粒度的控制。

早在1995年,O’Reilly出版社就出版了本书的第1版。本书在当时上市时甚至引起了一些轰动。它是第一本独立介绍PL/SQL的书(书名甚至没有带上Oracle字样),在当时急需PL/SQL资源——图书、开发环境、实用工具和网站的情况下,满足了世界各地开发者对PL/SQL技术的强烈渴求。(当然,本书在所有这些资源中,占据着最重要的地位!)

下面的段落,会粗略地对这些资源进行介绍,它们大多是可以免费或是花很少的费用就能够获得的,我们应该加以充分利用,迅速提高我们的开发经验(和编码水平)。

多年以来,O’Reilly的PL/SQL系列图书已经是长长一张书单了。下面这些是目前仍在印刷的图书,我们可以通过访问O’Reilly出版社网站对应Oracle的版块,获得更完整的信息。

Oracle PL/SQL Programming

作者:Steven Feuerstein和Bill Pribyl

就是我们正在阅读的上千页的这部鸿篇巨著,PL/SQL程序员桌上的好伙伴。本书涵盖了PL/SQL语言核心的每个特性。本书的当前版本已经延伸到Oracle数据库11g第2版。

Learning Oracle PL/SQL

作者:Bill Pribyl和Steven Feuerstein

对PL/SQL进行概括介绍,适用于编程新手或是已经熟悉其他编程语言的程序员。

Oracle PL/SQL Best Practice

作者:Steven Feuerstein

一本简短的书,介绍了几十种编程的最佳实践,帮助程序员提高PL/SQL编码质量。这本书就像是一个PL/SQL专家写的“经验教训总结”。第2版的内容进行了完全的重编,内容是为一个虚构公司进行代码开发时,开发团队面对挑战所进行的最佳实践。本书还有对应的网站:http://myflimsyexcuse.com。

Oracle PL/SQL Developer’s Workbook

作者:Steven Feuerstein和Andrew Odewahn

本书包括了一系列的问题及回答,旨在帮助PL/SQL程序员检验和提高他们对语言的掌握程度。本书覆盖到Oracle 8i版本,当然,其中绝大多数的练习也适用于8i之后数据库的版本。

Oracle Built-in Package

作者:Steven Feuerstein、Charles Dye和John Beresniewicz

一本关于Oracle在数据库服务器内预置的程序包的参考书。通过使用这些程序包,经常可以使得困难的功能得以轻松实现。本书介绍的特性涵盖到Oracle 8数据库,但其对程序包功能的深入解析,对使用后续版本的程序员也很有帮助。

Oracle PL/SQL for DBAs

作者:Arup Nanda和Steven Feuerstein

随着数据库版本的更新,PL/SQL语言对DBA而言也变得更加重要,原因有两点:一方面,有大量的DBA工作任务可以通过用API调用PL/SQL程序包来实现,为了实现这些工作任务,DBA必须要编写和运行PL/SQL程序;另一方面,DBA必须对PL/SQL有一定的了解,才能发现程序员的编码中可能存在的错误。本书提供了丰富的素材,能够帮助DBA迅速加深对PL/SQL的掌握,更好地完成数据库管理工作。

Oracle PL/SQL Language Pocket Reference

作者:Steven Feuerstein、Bill Pribyl和Chip Dawes

一本开本不大,能够装进衣兜的工具书,可以快速查阅核心PL/SQL语法,覆盖到Oracle数据库11g版本,非常实用。

OraclePL/SQL Built-ins Pocket Reference

作者:Steven Feuerstein、John Beresniewicz和Chip Dawes

另一本实用简明的工具书,汇集了Oracle数据库的内置函数和程序包,覆盖到Oracle数据库8版本。

PL/SQL程序员可以在网络上找到无数的在线资源。下面是本书的合著者推荐的或是直接管理着的资源。

Steven FeuersteinPL/SQL Obsession (http://www.toadworld.com/sf)

PL/SQL Obsession是Steven所管理的PL/SQL在线资源的门户,里面包括了他的培训教程及其代码、免费的实用工具(有一些会在本书中涉及)、视频录像等。

PL/SQL Challenge (http://www.plsqlchallenge.com)

PL/SQL Challenge是这样一个网站,与被动地阅读或浏览网页内容不同,它推崇“主动式学习”,可以在线选择PL/SQL、SQL、逻辑、数据库设计和数据库应用方面的测试,来检验我们的技能水平。

PL/SQL Channel (http://www.plsqlchannel.com)

PL/SQL Channel提供了超过27小时的PL/SQL语言视频培训,都是由Steven Feuerstein录制的。

Oracle Technology Network (http://otn.oracle.com)

Oracle Technology Network(OTN)基于Oracle技术,“提供开发者创建、测试、实施应用所需的服务和资源”。OTN拥有百万级的会员,里面有大量的Oracle软件、文档和实例代码可供下载。PL/SQL在OTN网站上有自己的独立网页。

Quest Error Manager (http://toadworld.com/m/freeware/560.aspx)

Quest Error Manager(QEM)是一个标准化框架,帮助我们对基于PL/SQL的应用程序的错误进行管理。使用QEM,我们可以在API界面上登记、提出或者报告错误,所有开发者以统一的方式进行错误管理,所花费的精力也大大减小。错误信息会记录在实例(关于错误的一些通用信息)表和上下文(具体应用的名-值对)表中。

oracle-developer.net

由Adrian Billington(本书第21章中关于管道化的表函数的21.5节的作者)维护的网站,这个网站适合Oracle数据库开发者。里面收集了大量的文章、指南和使用工具。Adrian还针对Oracle数据库的每个版本的新特性,提供了深度的说明,同时有诸多的示例、性能优化脚本等。

ORACLE-BASE (http://oracle-base.com)

Oracle-BASE是另一个有关ORACLE技术的神奇网站,它是由Oracle技术专家Tim Hall个人维护的。Tim是Oracle ACE总监、OakTable Network成员,并于2006年获得Oracle杂志ACE编辑大奖。自从1994年起,他就一直从事DBA、数据库设计和开发工作。其网址是http://oracle-base.com。

自从1995年本书第1版问世以来,作者有幸培训、帮助和合作过的PL/SQL开发者数以万计。在此期间,作者向他们学到了很多,也逐渐找到了如何在PL/SQL世界进行工作的一些方法。作者在此给出一些建议,说明如何利用PL/SQL强大的编程语言提高工作效率,希望读者们不要嫌太唠叨。

我们几乎总是被工作交期压迫着,忙着解决一个又一个问题,很多代码还等着我们去完成,没时间慢悠悠了,现在就开始吧——我们是这样想的,对吧?

错了!如果我们一头深深扎进构建代码的工作中去,机械地将客户需求转化为成百、上千甚至上万行代码,最终我们得到的是一团乱七八糟、无法调试和维护的代码垃圾。不要因为恐惧交期而仓促应对,相反,如果我们仔细规划,反而更有可能按时交工。

作者强烈建议,在开始构建一个应用,甚至仅仅是一个应用的特定程序时,一定要顶住时间的压力,做好以下几件事。

在开始编码前,构建测试实例和测试脚本

在编码开始前,我们要先确定好将来如何去判断实施的成功与否。我们只有构建了测试实例和脚本,才能做出正确的程序界面,同时有助于我们彻底了解我们的程序到底要实现什么。

在为应用编写SQL语句时,建立清晰的开发规则

一般来说,作者不建议每个开发者为了实现一些功能而编写各自的SQL语句。相反,那些单行的查询和插入、修改语句应该隐藏在预定义和测试好的过程或函数中(所谓的“封装”)。这些过程和函数可以在不同地方被不同程序重复调用,对它们的优化、测试和维护会更有效率。

在处理应用中的异常时,有对应的清晰规则

团队中所有的开发者都应遵循相同的报告、处理、记录规则。最佳方法是创建一个错误处理过程包,把如何进行错误报告、如何对错误进行处理,以及如何在不同设计模块进行通告这些细节隐藏起来,同时避免将异常处理硬编码在应用程序中。应该严格要求开发者使用同样的异常处理过程包进行异常处理,而不允许他们编写各自的异常处理代码,那将是复杂、耗时、易出错和和难于维护的。

采用从上至下(又称为“逐步细化)的设计方法,以便将复杂的需求逐步分解完成,从而降低集中处理的难度

如果我们使用这种设计方法,就会发现各模块的执行单元变得简短且易于理解,从而使得将来维护和升级这些代码相对轻松。本地或者嵌套模块在这种设计原则中起着关键作用。

以上这些,只是在编码开始前,需要牢记的一部分原则。请记住:在软件开发的世界里,仓促动工不仅会资源浪费,而且一定会带来大量的bug处理工作量,以及花在加班上的悲惨的周末时间。

大概率事件是:如果我们是软件专家,那么我们肯定是聪明人。我们学习刻苦、技能出众,现在我们过的是编写代码的美好人生,我们为能够解决大多数人生难题而自豪。不幸的是,我们的成功同时也让我们变得自负和傲慢,遇到阻碍也不愿寻求帮助。对于软件开发而言,自负基本上是最危险的和最有破环性的个性了。

软件是由人编写的,因此,必须承认,人的心理特性在软件开发中起着重要的作用。下面举个例子。

Joe是一个6人开发团队中的高级开发人员,他在开发过程中遇到一个难题。他研究了数个小时,越来越焦虑,但一直找不到bug的源头,他不愿向小组其他伙伴求助,因为那些人都比他资历浅。最后,他无可奈何地“放弃”了,叹着气,他拿起电话拨了个内线:“Sandra,我遇到个难题,你能过来帮我看看吗?”Sandra走过来,很快地浏览了Joe的程序,指出了他一直没能发现却很明显的问题。欧耶,问题解决了,Joe表示了感谢,但实际上,他感到很尴尬。

类似这样的“我怎么就没发现”和“如果再多花5分钟,我自己就能找到错误”的想法不断出现在Joe的脑海,这很容易理解,但也很蠢。其实原因很简单,我们太熟悉自己的编码了,以至于不能发现错误,我们需要一个客观的人,从一个不同的视角来对代码进行审查,这跟资历、经验、能力无关。

作者强烈建议,在组织内建立这样的行为准则。

奖励那些承认自己不知道的人

明明对应用或代码不懂却又不说出来,这对团队的开发工作而言是很危险的。我们的行为准则应该鼓励问问题和寻求帮助。

寻求帮助

如果我们无法在30分钟内找到问题所在,就应该立即寻求帮助。我们甚至可以建立一个“互助系统”,每个人都有一个互助伙伴,当遇到问题时,直接找互助伙伴帮忙。不要让自己一个人(或组里的任何一个人)绞尽脑汁数小时,徒劳无功地寻找答案。

建立一个正式的同僚代码复核流程

在代码还没有经过组里一个或多个开发人员阅读、挑剔(本着积极的、建设性的态度)之前,先不要进到QA流程,更不要说直接投产了。

人是习惯性动物,所以我们在生活的各个方面,经常重蹈覆辙,我们在编写代码时总是用同一种方法,我们脑海中会认定某种产品的功能有这种那种局限,我们不经深思熟虑,仅仅凭借“我以为”,就否定可能的解决方案。开发者自以为对自己开发的程序完全了解(经常是消极的),经常会这样说:

但实际上,我们的程序几乎总能运行得更快一些。屏幕的显示,实际上,也可以调整得跟用户要求的一样。虽然每个产品都有功能的优势、劣势和局限,但是我们也不能总是等下一个版本来满足需求。如果你能告诉你的治疗师,你认真面对挑战、不找借口找方法,设计出了解决方案,是不是很酷?

要如何才能做到这一点?打开僵化的思维边界,从一个全新的角度看世界(或者我们的格子间),重新审视我们已经形成的编程习惯,变得更有创造性——从传统的方式向前迈进一步,逐步摆脱那些在我们日常工作中不断强化的局限的、机械的方法。

做些新的尝试:试试那些看起来似乎离经叛道的方法,我们会惊讶地发现,作为程序员或问题解决人员,我们由此学到很多。这些年来,作者一次次惊讶地发现,一旦停止说“那样做不行!”,而只是默默点头,轻轻说“现在,我们换个方法……”之后,总会有一种方法能够真的奏效。


即使一个PL/SQL程序员从来没考虑过诸如系统设计或单元测试这样的任务,他也应该熟悉下面这些基本的操作:

与C等独立语言不同,PL/SQL是寄生在Oracle内部执行环境中的(所以它是“嵌入式语言”),所以PL/SQL完成上述任务时有一些细微的差别,有一些差别是令人愉快的,而另一些,则相反。本章将展示如何在一个最基本的层面上(使用SQL*Plus)完成上述任务,同时对这些细小差别进行大致的讲述。它包括一些示例,介绍如何在一些常见的编程环境中调用PL/SQL,如PHP和C。有关代码编译的详细信息,以及其他更高级的任务的完成,在第20章中有详述。

一旦我们决定使用PL/SQL进行开发,我们就将不可避免地开始与Oracle数据库里的内容打交道。既然我们的代码要在Oracle数据库里运行,因此,我们就要知道如何在数据库里面“游荡”。我们需要检查数据结构(表、列、序列、用户自定义数据类型等),还需要知道我们将要调用的、已存在的存储过程。当然,我们可能还需要知道表中的实际内容(列、约束等)。

在数据库中导航有两种不同的方法。

1.使用集成开发环境(Integrated Dvelopment Environment,IDE)。一个奇妙编辑工具的奇妙名字),像Toad、SQL Developer或SQL Navigator。它们都提供可视化浏览器,支持鼠标点击导航操作。

2.使用命令行方式,如在SQL*Plus环境,运行脚本来查询数据字典视图ALL_OBJECTS或USER_OBJECTS中的内容(本章稍后演示)。

作者强烈建议大家使用图形化的IDE,除非我们已在Oracle环境工作了很久,已经对命令行方式着迷,编写的各种脚本运行也相当高效。否则对于大多数人而言,图形界面更容易上手、更易于理解,相比需要编写脚本完成任务而言,也更高效。

在第20章里,提供了用PL/SQL代码查询数据字典视图的一些示例。

从最简单朴实的文本编辑器到最炫的开发环境,可供当今程序员选择的代码编辑器多种多样,他们也的确做了不同的选择。本书的作者之一Steven Feuerstein,对Toad IDE情有独钟,他是那种非常典型的用户——熟悉全部功能和按钮的10%,几乎完全依赖它们。Bill Pribyl则相反,他说自己是“一个非常古怪的人,只喜欢用最简单的文本编辑器来写PL/SQL程序,一个基本要求就是,它能把我录入的代码缩进处理,并能用不同的颜色显示关键字、注释、文字和变量”。

除了文字缩进和给关键字标注颜色,最复杂的编辑器还能做很多:它们能提供图形化的调试器,对关键字进行补全,当我们键入程序包中的子程序名称时,提供程序预览,将编译器报错的行或列设置为高亮。有些编辑器还提供“超链接”功能,使得开发人员可以快速跳转到变量或者子程序的声明之处。但对所有的编译性语言来说,这些功能需求都是大致相同的。

PL/SQL所特有的是,在程序被编译和执行之前,它所调用的存储过程的源代码必须加载到数据库中。这份保留在数据库中的备份能被有足够权限的程序员查看。说到这,我们立刻就会想到如下这些代码管理问题:

在我们开始开发一个应用系统之前,就应该成竹在胸。最好是根据选择的软件开发工具来进行。没有哪一种单一的工具或流程能够适合所有的开发团队,作者通常把“源”代码保存到文件中——强烈建议读者不要把RDBMS作为代码存放处。

下一节,将会演示如何使用SQL*Plus来完成PL/SQL开发的大多数基本任务,这些任务也可以在IDE环境中完成。

作为Oracle前端工具的鼻祖,Oracle的SQL*Plus提供了一个命令行解释器,既支持SQL,也支持SQL*Plus。也就是说,它接收用户端输入的语句,传递给Oracle服务器,然后显示结果。

尽管由于其用户界面的枯燥而受到指责,但SQL*Plus仍是作者钟爱的Oracle工具之一,原因就是喜欢它没有那些花哨的玩意和菜单。具有讽刺意味的是,当作者在大约1986年开始使用Oracle时,这个工具的前身竟然叫作UFI——User-Friendly Interface(友好的用户界面)。20年之后,即使是最新版的SQL*Plus,也绝不会获得任何关于界面友好的赞赏,但至少,它不会经常崩溃了。

这些年来,Oracle提供了几个版本的SQL*Plus,如下所示。

一个控制台程序

这是一个从SHELL或者命令行提示符下运行的程序(这种环境有时被称为控制台)。

一个类GUI程序

这种形式的SQL*Plus只存在于微软的Windows环境中。我称它为“类GUI”,因为除了所用的字体为位图字体之外,除了寥寥一些不同,它看上去和控制台程序没什么不同。请记住:Oracle几年前就宣布放弃对这个产品的支持了,自从Oracle 8i以来,这个产品也从未升级。

iSQL*Plus

这个程序从Web浏览器窗口执行,Web浏览器连接到一个中间层,这个中间层上运行着Oracle HTTP服务器和iSQL*Plus服务器。

从Oracle数据库11g开始,Oracle只提供控制台的SQL*Plus程序(sqlplus.exe)。

图2-1所示为控制台版的SQL*Plus会话截图。

图2-1 控制台会话中的SQL*Plus

通常,作者更偏爱控制台程序,原因是:

要启动控制台版的SQL*Plus,只需在操作系统提示符(以下简称“OS>”)下简单输入“sqlplus”即可:

OS> sqlplus

这种启动方式,在UNIX或Windows操作系统下均适用。SQL*Plus会首先显示一个启动横幅,随后提示你输入用户名和密码:

SQL*Plus: Release 11.1.0.6.0 - Production on Fri Nov 7 10:28:26 2008

Copyright (c) 1982, 2007, Oracle. All rights reserved.

Enter user-name: bob
Enter password: swordfish

Connected to:
Oracle Database 11g Enterprise Edition Release 11.1.0.6.0 - 64bit

SQL>

如果看到“SQL>”的提示符,就说明安装和启动均正常(实际环境中,为安全起见,密码是不会显示在屏幕上的)。

我们也可以在启动SQL*Plus的命令行里直接带上用户名和密码:

OS> sqlplus bob/swordfish

作者不建议如此操作,因为在某些操作系统中,其他用户可以看到你的命令行参数,因此会获得你的密码。在多用户系统中,我们可以在启动SQL*Plus时使用/NOLOG选项,这样就不会立刻连接数据库,随后我们在SQL*Plus环境中运行CONNECT命令,输入用户名和密码:

OS> sqlplus /nolog

SQL*Plus: Release 11.1.0.6.0 - Production on Fri Nov 7 10:28:26 2008

Copyright (c) 1982, 2007, Oracle. All rights reserved.
SQL> CONNECT bob/swordfish
SQL> Connected.

如果运行SQL*Plus的计算机已经正确地安装并配置了Oracle Net,我们也获得了数据库管理员的授权进行数据库的远程连接(即连接运行在另外一台计算机上的数据库),我们就可以通过SQL*Plus连接这些数据库了。要进行远程连接,还需要知道Oracle Net连接标识符(又叫作服务名),我们必须在输入用户名和密码的同时提供连接标识符,一个连接标识符大概长得像这样:

hqhr.WORLD

在使用时,需要把这个标识符加在用户名和密码后面,用符号“@”隔开:

SQL> CONNECT bob/swordfish@hqhr.WORLD
SQL> Connected.

若是启动SQL*Plus的类GUI版本,可以直观地提供验证信息(见图2-2),“Host String”就是指连接标识符。如果我们准备连接本机上的Oracle服务器,只需把Host String这一栏空着不填即可。

图2-2 SQL*Plus的GUI登录界面

SQL*Plus运行起来后,我们就可以做很多事情了,下面是一些常见操作:

在下面的段落里,我们来讲解如何进行上述操作。

在SQL*Plus环境中,默认的SQL语句结束符是分号,我们也可以把它更改成别的符号。

在控制台版本的SQL*Plus中,进行下面的查询:

SELECT isbn, author, title FROM books;

输出结果类似于上面图2-1所示的那样。

好,现在我们继续(掌声响起来!),在SQL*Plus中输入一个小PL/SQL程序:

SQL> BEGIN
  2     DBMS_OUTPUT.PUT_LINE('Hey look, ma!');
  3  END;
  4 /

PL/SQL procedure successfully completed.

SQL>

糟糕,此程序的目的是调用一个PL/SQL的内置小程序返回一些文本,虽然程序运行顺利结束,但我们期待的文本输出却不见踪影,SQL*Plus有时会莫名其妙地“吞掉”类似的输出。为了让结果能正确显示,我们必须使用一个SQL*Plus命令来打开SERVEROUTPUT参数:

SQL> SET SERVEROUTPUT ON
SQL> BEGIN
  2     DBMS_OUTPUT.PUT_LINE('Hey look, Ma!');
  3 END;
  4 /
Hey look, Ma!

PL/SQL procedure successfully completed.

SQL>

通常,我会把打开SERVEROUTPUT的命令放到我的启动文件里(参见2.3.6节中“在启动时自动加载用户自定义环境”的内容),这样这个开关就会常开,直到以下情况发生:

如果我们在控制台或类GUI的SQL*Plus环境下输入一条SQL或PL/SQL语句,SQL*Plus会为第一行之后的每一行分配一个数字行号。这样做有两个好处:首先,我们可以在内置的行编辑(你总是有一天会用到)里指定要编辑的行;其次,如果数据库检测到代码存在错误,它也会在错误报告中给出行号。在使用过程中,我们会充分体会到它的益处。

通过一个斜杠“/”(请见前面例子的第4行),我们告知SQL*Plus一个语句结束了,虽然在大多数情况下不会有什么危害,但斜杠“/”有一些重要特点需要注意:

作为一个简化方法,SQL*Plus提供了一个EXECUTE命令,可以省略BEGIN、END和斜杠“/”的输入。因此,下面的语句等价于我们刚才的示例:

SQL> EXECUTE DBMS_OUTPUT.PUT_LINE('Hey look, Ma!')

语句结尾的分号是可选的,作者更喜欢省略它。对于大多数SQL*Plus命令而言,EXECUTE可以简写,并且与大小写无关,所以大家都喜欢简略成这样:

SQL> EXEC dbms_output.put_line('Hey look, Ma!')

几乎所有能在SQL*Plus环境里交互运行的语句,都可以存成文件,以便重复执行。运行这个脚本最方便的方法是使用SQL*Plus的“@”命令。示例如下,我们需要运行文件abc.pkg里面的所有命令:

SQL> @abc.pkg

文件必须在目录中存在(或在SQLPATH覆盖的某个目录上)。

如果我们不喜欢符号,而希望用单词,那么下面的START命令与“@”命令等价:

SQL> START abc.pkg

运行的结果完全一样。对于这两个命令,SQL*Plus的动作分解是一样的。

1.打开叫作abc.pkg的文件。

2.顺序执行文件中的所有SQL、PL/SQL命令。

3.所有命令执行完成后,关闭文件,返回到SQL*Plus提示符下(除非文件中有EXIT命令,那就会直接退出SQL*Plus)。

示例如下:

SQL> @abc.pkg

Package created.

Package body created.

SQL>

默认的表现是仅仅把每条语句的执行结果显示在屏幕上,如果我们希望看到文件中的命令代码,可以利用SQL*Plus中的SET ECHO ON命令。

在所举的示例中,文件的扩展名是.pkg。如果我们省略扩展名,会得到如下结果:

SQL> @abc
SP2-0310: unable to open file "abc.sql"

我们可以看到,默认的文件扩展名是sql。顺便提一下,“SP2-0310”是Oracle提供的错误编号,“SP2”特指的是SQL*Plus(关于SQL*Plus错误的更多信息,可以参阅Oracle的SQL*Plus User’s Guide and Reference)。

任何时候,我们从操作系统提示符下启动SQL*Plus时,SQL*Plus把当时所在的操作系统目录视为自己的当前目录,换句话说,如果我们这样启动SQL*Plus:

C:\BOB\FILES> sqlplus

那么在SQL*Plus中所有的文件操作(例如打开或运行一个脚本),其默认目录都是“C:\BOB\FILES”。

如果我们通过快捷方式或是菜单选项来启动SQL*Plus,那么当前目录就由操作系统和不同的启动机制共同确定。那么,当我们已经进入SQL*Plus时,该如何改变当前目录呢?不同版本有不同的方式。如果是控制台版的SQL*Plus,将无法在SQL*Plus环境内改变当前目录,我们必须退出SQL*Plus,在操作系统提示符下改变目录,然后重新启动SQL*Plus。在GUI版本中,利用“File→Open”或“File→Save”菜单命令的副作用,就能够改变当前目录。

如果要执行其他目录下的脚本文件,我们可以在文件名前加上路径,示例如下:

SQL> @/files/src/release/1.0/abc.pkg

运行非当前目录的脚本文件会带来一个有趣的问题,如果abc.pkg文件存在于这个非当前目录,同时,还要调用其他文件,怎么办?比如,这个脚本里的命令是这样的:

REM  Filename: abc.pkg
@abc.pks
@abc.pkb

(用“REM”开头的行是一个注释行,SQL*Plus直接忽略)。执行abc.pkg脚本会调用abc.pks和abc.pkb,在我们没有指定路径信息的情况下,SQL*Plus会去哪里找这两个文件呢?让我们来看一下:

C:\BOB\FILES> sqlplus
...
SQL> @/files/src/release/1.0/abc.pkg
SP2-0310: unable to open file "abc.pks"
SP2-0310: unable to open file "abc.pkb"

原来它还是去我们启动SQL*Plus的当前目录里寻找。

为了解决这个问题,Oracle创建了“@@”命令。双“@”符号意味着,在本次运行中,“假定所执行文件所在的目录就是当前目录”,所以,abc.pkg脚本更好的写法是:

REM  Filename: abc.pkg
@@abc.pks
@@abc.pkb

这次,我们得到如下结果:

C:\BOB\FILES> sqlplus
...
SQL> @/files/src/release/1.0/abc.pkg

Package created.

Package body created.

这回,跟我们希望的一样了。

SQL*Plus有大量命令,但由于本书篇幅有限,我们只能介绍那些特别重要的、或是特别让人困惑的少数一些命令。如果想彻底了解这个神奇的产品,可以去买一本由Jonathan Gennick写的OracleSQL*Plus:The Definitive Guide。如果想要快速参考,那么可以选择他的另一本 Oracle SQL*Plus:Pocket Reference

个性化设置

我们可以对SQL*Plus进行个性化设置,通过命令行的方式改变它内置的变量值和设置。我们已在上面的示例中看到了SET SERVEROUTPUT命令,SQL*Plus中的SET命令有许多选项,如SET SUFFIX(改变默认的文件扩展名)、SET LINESIZE n (设置显示时每行最大字符数,超过部分进行折行处理)等,可以用下面的命令来查看当前会话的各选项的值:

SQL> SHOW ALL

SQL*Plus也可以创建和操作它自己的一些内存变量,它预留了一些特殊的变量,这些变量可以影响它的行为。实际上,SQL*Plus中有两类变量:DEFINE和绑定变量。我们可以使用DEFINE命令给一个DEFINE变量赋值:

SQL> DEFINE x = "the answer is 42"

如要查看x的值,可以这样:

SQL> DEFINE x
DEFINE X = "the answer is 42" (CHAR)

若要引用这样的变量,需要使用连接符号“&”。SQL*Plus在把语句发送给Oracle数据库之前,会对变量做一个简单的替代,所以当我们希望一个字符串被当作变量进行替代,我们就需要把它用单引号括起来:

SELECT '&x' FROM DUAL;

对于绑定变量,我们需要首先对它进行声明,然后就可以在PL/SQL里使用它了,如要显示它的值,就用SQL*Plus里的PRINT命令:

SQL> VARIABLE x VARCHAR2(10) 
SQL> BEGIN
  2     :x := 'hullo'; 
  3  END; 
  4  /

PL/SQL procedure successfully completed.

SQL> PRINT :x

X
--------------------------------
hullo

在这里,读者可能有点小困惑,因为现在出现了两个不同的变量“x”,一个是定义出来的,一个是声明出来的:

SQL> SELECT :x, '&x' FROM DUAL;
old   1: SELECT :x, '&x' FROM DUAL
new   1: SELECT :x, 'the answer is 42' FROM DUAL

:X                               'THEANSWERIS42'
-------------------------------- ----------------
hullo                            the answer is 42

只需记住,DEFINE的变量总是被SQL*PLUS当作扩展的字符串使用,而DECLARED的变量可以被SQL和PL/SQL当作真正的绑定变量使用。

将输出保存到文件

时不时地,我们需要把一个SQL*Plus会话的输出保存到一个文件中去——用于生成报告,或是记录我们的操作过程,或是我们需要把执行的命令记录下来以便日后再次运行。在SQL*Plus中,一个简便的方法是使用SPOOL命令:

SQL> SPOOL report
SQL> @run_report

……输出的内容不断向下滚动,同时写入名为report.lst的文件……

SQL> SPOOL OFF

上述示例中的第一条命令,SPOOL report,告诉SQL*Plus将后续的所有输出保存到report.lst文件中。文件扩展名.lst是默认的,可以通过在SPOOL命令中指定扩展名来改变默认扩展名:

SQL> SPOOL report.txt

SPOOL OFF命令告诉SQL*Plus,停止将输出保存到文件,同时关闭文件。

退出SQL*Plus

使用EXIT命令,可以退出SQL*Plus,返回到操作系统环境:

SQL> EXIT

如果我们退出之前还在进行SPOOL,那么SQL*Plus会自动停止SPOOL并关闭文件。

如果我们在会话中修改了一些表的数据,没有用显式的事务控制语句结束事务,就退出SQL*Plus,会有什么后果?默认地,SQL*Plus退出前会强制进行事务提交(COMMIT),特殊情况是因为SQL错误而导致会话终止,并且我们在SQL*Plus会话里发出过WHENEVER SQLERROR EXIT ROLLBACK指令(参见2.3.7节)。

若要断开数据库连接,但仍留在SQL*Plus环境,可以使用DISCONNECT命令,这个命令的表现是这样的:

SQL> DISCONNECT
Disconnected from Personal Oracle Database 10g Release 10.1.0.3.0 - Production
With the Partitioning, OLAP and Data Mining options
SQL>

切换连接时,我们无需使用DISCONNECT命令——我们只要再发一条CONNECT命令即可,SQL*Plus会断开前一个连接,再建立新的连接。但实际上,有时断开再重新连接实属必要:如果我们正在使用操作系统验证,脚本就可能会自动重新连接,但使用的是错误的账号,作者曾经见过这样的情况。

编辑语句

SQL*Plus会在缓存中保留最新接收的一些语句,我们可以对这些语句进行编辑,既可以使用内置的行编辑器,也可使用任意的外部编译器。下面我演示如何设置和使用一个外部编辑器。

EDIT命令,可以使得SQL*Plus把当前保留在缓存中的命令存储到一个文件去,暂时停止SQL*Plus并调用编译器:

SQL> EDIT

保存命令的文件名默认是afiedt.buf,我们也可以使用SET EDITFILE命令进行修改。或者,如果我们想编辑一个现有的文件,只需把文件名作为EDIT命令的变量:

SQL> EDIT abc.pkg

一旦我们保存文件并退出编辑器,SQL*Plus会话就将新边界的文件内容读入缓存,然后会话继续。

Oracle默认的外部编辑器是:

虽然默认编译器的选择已经被硬编码到sqlplus可执行文件中了,我们仍然可以通过设置SQL*Plus的_EDITOR变量的值,来轻松地改变当前的编码器。下面的示例就是作者经常用到的:

SQL> DEFINE _EDITOR = /bin/vi

/bin/vi是一款“怪人”们常用的编辑软件的全路径。为安全考虑,建议在这里使用编辑器的全路径名称。

如果我们只想使用SQL*Plus内置的行编辑器(也确实挺方便的),有一些基本的命令需要了解:

L

显示最后使用的语句。

n

把语句的第n行作为当前行。

DEL

删除当前行。

C/old/new

当前行中,把第一个出现的old替换成new。分隔符(这里是一个斜杠)可以是任意字符。

n text

把text作为第n行的内容。

I

在当前行之后插入一行。若要在当前行之前插入一行,使用0行命令(即“0 text”)。

在启动时自动加载自定义环境

若要自定义我们的SQL*Plus环境,在不同的会话中都保有我们的自定义,我们需要编辑一个或是多个自动启动脚本。SQL*Plus在启动时的表现是这样的:

1.搜索$ORACLE_HOME/sqlplus/admin/glogin.sql文件,如果成功,执行文件里面的命令,这是个“全局”登录脚本,任何人只要执行的是同一个Oracle home下的SQL*Plus,不管是从哪个目录登录的,都会执行这个脚本。

2.下一步,如果当前目录下存在login.sql文件,它就运行这个文件。

启动脚本内所包含的语句内容,可以跟任何SQL*Plus脚本内容一样:SET命令、SQL语句、列格式化命令,等等。

上述的两个启动脚本都不是必须存在的,如果都存在,则先执行glogin.sql,然后是login.sql,如果两个文件中的变量设置有冲突,则以后一个脚本为准。

下面是login.sql文件通常会进行的设置:

REM Number of lines of SELECT statement output before reprinting headers
SET PAGESIZE 999

REM Width of displayed page, expressed in characters
SET LINESIZE 132

REM Enable display of DBMS_OUTPUT messages. Use 1000000 rather than
REM "UNLIMITED" for databases earlier than Oracle Database 10g Release 2
SET SERVEROUTPUT ON SIZE UNLIMITED FORMAT WRAPPED

REM Change default to "vi improved" editor
DEFINE _EDITOR = /usr/local/bin/vim

REM Format misc columns commonly retrieved from data dictionary
COLUMN segment_name FORMAT A30 WORD_WRAP
COLUMN object_name FORMAT A30 WORD_WRAP

REM Set the prompt (works in SQL*Plus
REM in Oracle9i Database or later)
SET SQLPROMPT "_USER'@'_CONNECT_IDENTIFIER > "

SQL*Plus表达运行成功的方式与命令的种类相关。对于大多数SQL*Plus独有的命令,如果没有返回错误信息,那么就表示运行成功。另一方面,SQL和PL/SQL命令,通常会返回某种正面的文字来表示运行成功。

如果SQL或PL/SQL语句在SQL*Plus中执行时遇到错误,默认SQL*Plus会引发错误信息,同时继续执行后续的语句。如果我们工作在交互环境中,这种异常处理方式是不错的,但如果我们是在运行一个脚本,大多数情况下,我们希望当出现错误时,SQL*Plus会话被终止。我们可以通过下面的命令进行设置:

SQL> WHENEVER SQLERROR EXIT SQL.SQLCODE

这样一来,在当前会话中,如果数据库服务器返回了一个SQL或PL/SQL运行错误,那么SQL*Plus就会终止。它把返回代码设置成一个非0值,我们就可以在调用环境中捕捉到这个值。否则,SQL*Plus总是会以返回值0结束,让我们误以为脚本执行成功了。

这个命令的另一个形式是:

SQL> WHENEVER SQLERROR EXIT SQL.SQLCODE ROLLBACK

这个指令除了上个命令的含义,还指示SQL*Plus在退出前,回滚尚未提交的变更。

SQL*Plus除了上面我们介绍的特性,还有一些值得我们去了解并喜欢上的特点。

当然,和其他工具一样,SQL*Plus也有下面这些不足之处。

总而言之,SQL*Plus只是一个“真正的程序员”使用的工具,既不温暖也不模糊。但它无处不在,稳定不易崩溃,而且Oracle公司会一直提供对它的支持。

现在,我们把注意力集中,当SQL*PLUS被当作前端工具时,如何用它管理PL/SQL程序,如创建、执行、删除等。这里不会覆盖具体细节,后面的章节会有更细致的讲解,这里只是简单进行概述。

 

许多Oracle程序员都只知道一种显示错误的SQL*Plus命令:

他们错误地以为,若要查看最新的编译错误信息,就只能直接查询USER-ERRORS视图。实际上,我们可以在SHOW ERRORS命令后面加上对象类别和对象名称,它就可以显示此对象的最新编译错误信息了:

例如,若要查看“wordcount”函数的最新错误信息,我们可以这样进行指定:

在对输出进行解释时要注意:

这个返回信息包含三种可能的含义:(1)对象编译成功;(2)提供的是错误的类别(例如,给的类别是过程而不是函数);(3)没有对应此名称的对象。

这个命令支持的完整的对象类别列表根据Oracle版本的变化而不同,但以下类别是各版本均支持的:

我们可以使用SQL的CREATE语句创建一个新的存储程序。例如,如果我们要创建一个存储函数,统计一个字符串里的单词个数,我们就可以使用CREATE FUNCTION语句:

CREATE FUNCTION wordcount (str IN VARCHAR2)
   RETURN PLS_INTEGER
AS
   declare local variables here
BEGIN
   implement algorithm here
END;
/
SQL> SHOW ERRORS

上面只是一个简单的BEGIN-END程序块,如果要从SQL*Plus中运行它的话,需要在结尾加一斜杠,这个斜杠独占一行。

假定DBA已经赋予了我们CREATE PROCEDURE的权限(这个权限也允许创建函数),那么这个语句会触发Oracle对之进行编译并把这个存储函数保存到我们的schema中。当代码编译完成时,我们能看到如下的提示成功信息:

SQL> SHOW ERRORS category [schema.]object
Function created.

如果另一个数据对象,例如表或程序包,也叫“wordcount”,已经存在于我们的schema下,那么CREATE FUNCTION会失败,伴随的错误信息是ORA-00955: name is already used by an existing object。这就是为什么Oracle提供了OR REPLACE选项的原因,我们在99%的时间里都会用到它:

SQL> SHOW ERRORS FUNCTION wordcount
CREATE OR REPLACE FUNCTION wordcount (str IN VARCHAR2)
   RETURN PLS_INTEGER
AS same as before

使用OR REPLACE选项,可以避免先删除、再创建对象可能导致的副作用,换句话说,它保留了原来其他用户或角色对这个对象的权限。幸运的是,它仅仅能替代同类别的对象,所以如果我们想要创建一个叫“wordcount”的函数,它不会删除掉一个叫“wordcount”的表。

No errors.

和之前多次使用的匿名块一样,程序员们通常把这些语句保存在操作系统的文件中。我们可以为上面的代码创建一个名为wordcount.fun的文件,然后通过SQL*Plus的“@”命令来调用它:

SQL> @wordcount.fun

Function created.

之前提到过,SQL*Plus默认不回显脚本内容。我们可以通过SET ECHO ON命令,让脚本源码在屏幕上显示,显示还包含了数据库分配的行序号;这个设置有利于我们进行程序调试。

DIMENSION
FUNCTION
JAVA SOURCE
JAVA CLASS
PACKAGE
PACKAGE BODY
PROCEDURE
TRIGGER
TYPE
TYPE BODY
VIEW

我们现在注释掉一个变量声明(第4行),这样就使程序编译时发生错误:

/* File on web: wordcount.fun */
SQL> SET ECHO ON
SQL> @wordcount.fun
SQL> CREATE OR REPLACE FUNCTION wordcount (str IN VARCHAR2)
  2     RETURN PLS_INTEGER
  3  AS
  4  /* words PLS_INTEGER := 0; ***Commented out for intentional error*** */
  5     len PLS_INTEGER := NVL(LENGTH(str),0);
  6     inside_a_word BOOLEAN;
  7  BEGIN
  8     FOR i IN 1..len + 1
  9     LOOP
 10        IF ASCII(SUBSTR(str, i, 1)) < 33 OR i > len
 11        THEN
 12           IF inside_a_word
 13           THEN
 14              words := words + 1;
 15              inside_a_word := FALSE;
 16           END IF;
 17        ELSE
 18           inside_a_word := TRUE;
 19        END IF;
 20     END LOOP;
 21     RETURN words;
 22  END;
 23  /

Warning: Function created with compilation errors.

此信息告诉我们,函数已经创建了,但有一个编译错误,所以函数处于不可用状态。我们已经成功地把源码存入了数据库,现在我们需要对数据库引发的错误进行细致梳理。查看完整错误信息最方便的方法是使用SQL*Plus的SHOW ERRORS命令,简化形式是SHO ERR:

SQL> SHO ERR
Errors for FUNCTION WORDCOUNT:
LINE/COL ERROR
-------- ----------------------------------------------
14/13    PLS-00201: identifier 'WORDS' must be declared
14/13    PL/SQL: Statement ignored
21/4     PL/SQL: Statement ignored
21/11    PLS-00201: identifier 'WORDS' must be declared

编译器已经探到了变量的存在,指出了错误所在的具体行数。如果想要更仔细地了解任一Oracle服务器端的报错,我们可以在“Oracle’s Database Error Messages”文档中,用错误标识符(这里是PLS-00201)进行查找。

其实在后端,SHOW ERRORS的命令就是查询Oracle数据字典中的USER_ERRORS视图。我们也可以自己去查询这个视图,不过通常没必要那么做(见下面的备注框)。

显示其他错误

存储PL/SQL程序的CREATE语句后面都会加上一个SHOW ERROR命令,所以在SQL*Plus环境中,创建存储过程的“最佳实践”模板如下:

CREATE OR REPLACE program-type
AS
   your code
END;
/

SHOW ERRORS

(通常我们不应把SET ECHO ON放进脚本,而是在需要的时候手动输入)。

当编译器探查我们的程序包含的错误时,CREATE仍然会把这个程序存储到数据库中,但会把其状态置为“无效”。而当我们的CREATE语句出现语法错误时,数据库无法分辨我们的真实意图,就不会存储程序了。

我们已经看到过两种调用存储程序的方法了:封装在一个PL/SQL代码块中,或执行SQL*Plus的EXECUTE命令。我们还可以在一个存储程序中调用另一个存储程序。例如,我们可以在任意使用整数表达式的地方使用“wordcount”函数。下面是一个简短的演示,看看我是如何用一个奇怪的输入(CHR(9)是ASCII“tab”字符)来对函数“wordcount”进行测试的:

BEGIN
   DBMS_OUTPUT.PUT_LINE('There are ' || wordcount(CHR(9)) || ' words in a tab');
END;
/

我把“wordcount”函数当作表达式的一部分,而表达式的整体又是DBMS_OUTPUT.PUT_LINE的参数。在这里,PL/SQL会自动把整数转换为字符串,然后和其他两个文字表达式拼接起来,结果如下:

There are 0 words in a tab

我们也可以在SQL语句内调用多个PL/SQL函数。下面是如何使用“wordcount”函数的一些示例。

SELECT isbn, wordcount(description) FROM books;
VARIABLE words NUMBER
CALL wordcount('some text') INTO :words;
PRINT :words
CALL wordcount@test.newyork.ora.com('some text') INTO :words;
SELECT bob.wordcount(description) FROM books WHERE id = 10007;

时不时地,我们会想要一份我们所拥有的存储程序的名称列表,我们也会需要查看保存在数据字典中的程序代码的最新版本。对于这样的任务,如果我们使用的是某些基于图形界面(GUI-based)的导航工具,是可以轻松实现的,但如果我们没有图形界面工具,编写一小段SQL语句,从数据字典中获得上述信息也不是很难的事。

例如,为了查看我们所拥有的完成的程序(包括表、索引等)列表,可以查询USER_OBJECTS视图,像这样:

SELECT * FROM USER_OBJECTS;

这个视图会显示出名称、类型、创建时间、最近一次的编译时间、状态(可用或不可用)和其他一些有用的信息。

如果我们仅仅要了解一个PL/SQL程序在SQL*Plus中的调用接口信息,用DESCRIBE命令最简单:

SQL> DESCRIBE wordcount
FUNCTION wordcount RETURNS BINARY_INTEGER
 Argument Name                  Type                   In/Out Default?
 ------------------------------ ----------------------- ------ --------
 STR                            VARCHAR2               IN

DESCRIBE命令也可用于表、视图、对象类型、过程和程序包。通过USER_SOURCE或TRIGGER_SOURCE,我们可以查到存储程序的完整代码。(对这些数据字典视图的查询,我们在第20章会有详细讲述。)

对于一个刚刚创建出来的PL/SQL程序,除了创建者和DBA之外,通常没有其他人有权限执行它。要想给其他人进行授权,可以使用GRANT语句:

GRANT EXECUTE ON wordcount TO scott;

若想取消授权,则使用REVOKE语句:

REVOKE EXECUTE ON wordcount FROM scott;

我们也可以把EXECUTE权限授权给一个角色:

GRANT EXECUTE ON wordcount TO all_mis;

或者,如果合适的话,我们也可以授权给数据库中的所有用户运行程序:

GRANT EXECUTE ON wordcount TO PUBLIC;

如果把权限赋给了某个用户,比如Scott,同时也赋给了某个角色,而这个用户又被赋予了此角色(以all_mis为例),接着又把权限赋给了PUBLIC,数据库就会一直记录着这三次的授权,直到这些授权被取消。三次授权的任何一次,都已经使得这个用户可以运行这个程序了,所以如果我们决定不再允许Scott运行程序,我们必须一一取消对用户Scott的授权、对PUBLIC的授权和对角色all_mis的授权(或取消对Scott所赋予的该角色)。

若想查看我们对所有其他用户或角色的赋权列表,可以使用USER_TAB_PRIV_MADE数据字典视图,不过PL/SQL的程序名是保存在table_name列的,有点别扭:

SQL> SELECT table_name, grantee, privilege
  2    FROM USER_TAB_PRIVS_MADE
  3   WHERE table_name = 'WORDCOUNT';

TABLE_NAME                    GRANTEE                       PRIVILEGE
------------------------------ ------------------------------ -----------
WORDCOUNT                     PUBLIC                        EXECUTE
WORDCOUNT                     SCOTT                         EXECUTE
WORDCOUNT                     ALL_MIS                       EXECUTE

如果用户Scott拥有对“wordcount”的执行权限,为避免每次使用程序时都要加上所有者schema名称作为前缀,他会想要为程序创建一个别名:

SQL> CONNECT scott/tiger
Connected.
SQL>CREATE OR REPLACE SYNONYM wordcount FOR bob.wordcount;

现在,他只需通过别名来运行程序了:

IF wordcount(localvariable) > 100 THEN...

这个方法不错,因为如果函数的所有者发生了变化,Scott只需修改别名(而不是所有的存储程序)即可。

我们可以为过程、函数、程序包、用户自定义类型创建别名。过程、函数或程序包的别名不仅可以隐藏schema,还可以隐藏数据库信息,即我们可以为远程数据库中的程序创建别名,与本地数据库没什么不同。不过,别名也只能隐藏schema和数据库信息,我们不能为程序包中的子程序创建别名。

别名的删除也相当容易:

DROP SYNONYM wordcount;

如果我们真的不再需要一个特定的存储程序了,可以使用SQL的DROP语句来删除它:

DROP FUNCTION wordcount;

程序包是由两部分组成的(声明部分和包体),我们也可以一次性删除整个程序包:

DROP PACKAGE pkgname;

我们也可以只删除包体,而不会对声明部分造成影响(不会使它的状态变为“无效”):

DROP PACKAGE BODY pkgname;

下次当其他程序调用到这个程序包时,调用程序的状态会被标记为“无效”。

按照之前的方法创建PL/SQL程序时,源代码在数据字典中以明文的形式保存,任何DBA都可以查看甚至修改它。为保护商业机密或防止代码被随意篡改,我们需要一些手段把代码在发布之前“打乱”。

Oracle提供了一个命令行工具“wrap”,可以把许多CREATE语句转换成由明文和十六进制符号组成的混合体。它不是真正意义上的加密,但确实向代码隐藏方向迈进了一大步。下面的内容节选自一个“转换”后文件:

FUNCTION wordcount wrapped
0
abcd
abcd ...snip...
1WORDS:
10:
1LEN:
1NVL:
1LENGTH:
1INSIDE_A_WORD:
1BOOLEAN: ...snip...
a5 b 81 b0 a3 a0 1c 81
b0 91 51 a0 7e 51 a0 b4
2e 63 37 :4 a0 51 a5 b a5
b 7e 51 b4 2e :2 a0 7e b4
2e 52 10 :3 a0 7e 51 b4 2e
d :2 a0 d b7 19 3c b7 :2 a0
d b7 :2 19 3c b7 a0 47 :2 a0

如果我们需要真正的加密,例如,对密码信息进行真正的加密保护,那么我们就不能依赖这个工具了。

在第20章,我们还会详细讲到“wrap”工具。

作者早前曾经提到,我们可以选择像SQL*Plus这样极简的编辑和执行环境,也可以选择集成的开发环境(Integrated Development Environment),后者提供了丰富的图像界面以提高工作效率。本节罗列了一些流行的IDE工具。作者不打算力推哪一种工具,读者应该仔细考虑个人的主要需求,然后选择一款最合适的。

产  品

介  绍

Toad

Quest Software公司出品,最流行的PL/SQL IDE。它的免费版和专业版都拥有成千上万的拥趸

SQL Navigator

也是Quest Software公司出品,拥有上万的用户数量,其产品界面和一些提高效率的特性令人称道

PL/SQL Developer

由Allround Automations发售,很多PL/SQL开发者喜爱这款IDE,它是基于一种插件架构开发的,第三方厂商可以提供扩展插件

SQL Developer

经过了若干年的在PL/SQL编辑领域的无所作为,Oracle公司以JDeveloper为基础,开发了SQL Developer,产品是免费的,用户量增长迅速

还有很多其他的PL/SQL IDE,本节仅仅罗列了这几个口碑最佳和最流行的。

很快地,我们要用到从C、Java、Perl、PHP或其他语言中调用PL/SQL。这是一个合理的需求,如果我们有过跨语言的开发经验,我们一定知道,把不同语言的特有的数据类型——尤其是像数组、记录和对象这样的复杂数据类型——揉和在一起是多么的困难,更不用说不同的参数语法,或者厂商的“标准”应用程序接口(API,如Microsoft ODBC——开放数据连接)了。

现在来举几个从外部调用PL/SQL的简短示例。假设我们有一个函数,这个函数接收字符串类型的ISBN输入,返回对应的图书名称:

/* File on web: booktitle.fun */
FUNCTION booktitle (isbn_in IN VARCHAR2)
   RETURN VARCHAR2
IS
   l_title books.title%TYPE;
   CURSOR icur IS SELECT title FROM books WHERE isbn = isbn_in;
BEGIN
   OPEN icur;
   FETCH icur INTO l_title;
   CLOSE icur;
   RETURN l_title;
END;

在SQL*Plus中,有多种方式调用这个函数,最简便的方式像这样:

SQL> EXEC DBMS_OUTPUT.PUT_LINE(booktitle('0-596-00180-0'))
Learning Oracle PL/SQL

PL/SQL procedure successfully completed.

接下来,看看我如何从以下环境中调用这个函数。

这些示例纯粹是演示性质的——例如,用户名和密码都是硬编码,程序也仅仅把输出显示在标准设备上。而且,作者也不打算对每一行代码进行讲解。这些示例仅仅是让我们了解,在不同的语言环境中,我们会遇到哪些处理模式。

Oracle至少提供两种C语言接口:一种叫OCI(Oracle Call Interface),这个是专业牛人的主场;另一种叫Pro*C。OCI提供了上百个函数,就算是一个简单的查询,我们也要从底层操作开始编码:打开、解析、绑定、执行、获取结果等动作分解。即使是一段完成最简单功能的OCI程序,也要大约200行代码的长度……算了,还是演示Pro*C的例子好了。Pro*C是一种预编译技术,允许我们把C、SQL和PL/SQL代码汇合在一起编写。我们通过Oracle的proc程序运行这个混合编码,在输出端得到C编码:

/* File on web: callbooktitle.pc */
#include <stdio.h>
#include <string.h>

EXEC SQL BEGIN DECLARE SECTION;
    VARCHAR uid[20];
    VARCHAR pwd[20];
    VARCHAR isbn[15];
    VARCHAR btitle[400];
EXEC SQL END DECLARE SECTION;

EXEC SQL INCLUDE SQLCA.H;

int sqlerror();

int main()
{
    /* VARCHARs actually become a struct of a char array and a length */
    strcpy((char *)uid.arr,"scott");
    uid.len = (short) strlen((char *)uid.arr);
    strcpy((char *)pwd.arr,"tiger");
    pwd.len = (short) strlen((char *)pwd.arr);

    /* this is a cross between an exception and a goto */
    EXEC SQL WHENEVER SQLERROR DO sqlerror();

    /* connect and then execute the function */
    EXEC SQL CONNECT :uid IDENTIFIED BY :pwd;
    EXEC SQL EXECUTE
       BEGIN
          :btitle := booktitle('0-596-00180-0');
       END;
    END-EXEC;

    /* show me the money */
    printf("%s\n", btitle.arr);

    /* disconnect from ORACLE */
    EXEC SQL COMMIT WORK RELEASE;
    exit(0);
}

sqlerror()
{
    EXEC SQL WHENEVER SQLERROR CONTINUE;
    printf("\n% .70s \n", sqlca.sqlerrm.sqlerrmc);
    EXEC SQL ROLLBACK WORK RELEASE;
    exit(1);
}

我们可以看到,Pro*C不是一个追求纯粹的语言家所支持的。其实谁也不愿意和这种方式生成的C代码打交道,但无论如何,许多公司发现,Pro*C(或Pro*Cobol,或其他几种Oracle支持的语言)作为介于OCI(太难)和类似Visual Basic(太慢,笨重)语言之间的一种选择,还是有其存在的意义的。

从Oracle自己的文档中,可以找到最全面准确的关于Pro*C的信息。

跟C语言一样,Oracle提供了连接数据库的多种方式,其中嵌入的SQL方式,称为SQLJ,与Oracle的其他预编辑技术类似,但调试界面更友好一些。一种更通用和专门针对Java的方式称为JDBC(字面上来说,其实没有什么含义),通常解释为“Java Database Connection”:

/* File on web: Book.java */
import java.sql.*;
public class Book
{
  public static void main(String[] args) throws SQLException
  {
    // initialize the driver and try to make a connection

    DriverManager.registerDriver (new oracle.jdbc.driver.OracleDriver ());
    Connection conn =
       DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:o92",
                            "scott", "tiger");

    // prepareCall uses ANSI92 "call" syntax
    CallableStatement cstmt = conn.prepareCall("{? = call booktitle(?)}");

    // get those bind variables and parameters set up
    cstmt.registerOutParameter(1, Types.VARCHAR);
    cstmt.setString(2, "0-596-00180-0");

    // now we can do it, get it, close it, and print it
    cstmt.executeUpdate();
    String bookTitle = cstmt.getString(1);
    conn.close();
    System.out.println(bookTitle);
  }
}

这个示例使用的是瘦驱动器,有着很好的兼容性,并易于安装(在Java库中,所有的网络协议都已存在了),缺点是需要牺牲一点通信性能。另一个备选方式是使用 OCI驱动器。别担心,只是名字里有“OCI”字样而已,我们可以使用它,而无需写那些疯狂的代码!

Perl是所有开源代码的源头,系统管理员社区的至爱。Perl的当前版本是5.10,无处不在,无所不能。它还有一个帅呆了的自动配置工具,叫作CPAN(Comprehensive Perl Archive Network),它是安装社区提供的模块的安装纽带,如Database Interface(DBI)和对应Oracle驱动的DBD::Oracle:

/* File on web: callbooktitle.pl */
#!/usr/bin/perl

use strict;
use DBI qw(:sql_types);

# either make the connection or die
my $dbh = DBI->connect(
    'dbi:Oracle:o92',
    'scott',
    'tiger',
    {
        RaiseError => 1,
        AutoCommit => 0
    }
) || die "Database connection not made: $DBI::errstr";

my $retval;

# make parse call to Oracle, get statement handle
eval {
    my $func = $dbh->prepare(q{
      BEGIN
          :retval := booktitle(isbn_in => :bind1);
      END;
    });

# bind the parameters and execute
    $func->bind_param(":bind1", "0-596-00180-0");
    $func->bind_param_inout(":retval", \$retval, SQL_VARCHAR);
    $func->execute;
};

if( $@ ) {
    warn "Execution of stored procedure failed: $DBI::errstr\n";
    $dbh->rollback;
} else {
    print "Stored procedure returned: $retval\n";
}

# don't forget to disconnect
$dbh->disconnect;

Perl是一种易写难读的语言,它不是那种特别快速小巧的语言,但其编译的版本,至少能够解决速度慢的问题。

如果想要了解更多Perl和Oracle接口的信息,可以参阅Programming the Perl DBI(作者是Alligator Descartes和Tim Bunce)。关于Perl语言本身,也有不少好书,包括在线信息perl.com(一个属于O’Reilly的网站)、perl.org和cpan.org。

如果我们使用的是类似Apache这样的免费的通用Web服务器,我们应该也会乐意使用同样免费而且通用的PHP编程语言。PHP除了可以构建动态网页,还可以用来构建GUI应用,或运行命令行程序。与我们的期望一致,Oracle数据库环境支持PHP。实际上,Oracle公司与Zend合作,已经推出了一个带有PHP的数据库“测试”版本。

下面的示例使用PHP中被称为OCI8的函数家族,不要被函数名字中的“8”所迷惑——它适用的版本从Oracle数据库7直到Oracle数据库11g:

/* File on web: callbooktitle.php */
<?PHP
    // Initiate the connection to the o92 database
    $conn = OCILogon ("scott", "tiger", "o92");

    // Make parse call to Oracle, get statement identity
    $stmt = OCIParse($conn,
       "begin :res := booktitle('0-596-00180-0'); end;");

    // Show any errors
    if (!$stmt) {
       $err = OCIError();
       echo "Oops, you broke it: ".$err["message"];
       exit;
    }

    // Bind 200 characters of the variable $result to placeholder :res
    OCIBindByName($stmt, "res", &$result, 200);

    // Execute
    OCIExecute($stmt);

    // Stuff the value into the variable
    OCIResult($stmt,$result);

    // Display on stdout
    echo "$result\n";

    // Relax
    OCILogoff($conn);
?>

要在命令行模式运行这个程序,可以这样进行:

$ php callbooktitle.php
Learning Oracle PL/SQL

顺便提一下,PHP不是默认包含这些Oracle OCI函数的,但对于我们的系统管理员而言,重新构建一个带有Oracle扩展的PHP并不是什么难事。

我们可以在php.net网站获得PHP相关的更多信息,O’Reilly也有关于PHP的诸多书籍。PHP与Oracle相关的话题,可以访问Oracle Technology Network。

尽管PL/SQL Server Page(PSP)环境是为Oracle所专有的,但我觉得还是应该提到一下,因为它是最快的运行Web页面的方法。PSP是另一种预编辑技术,使得我们可以将PL/SQL内嵌到HTML页面中去。这里的“<%=%>”结构,含义是“把这部分当作PL/SQL处理,把结果返回给页面”:

/* File on web: favorite_plsql_book.psp */
<%@ page language="PL/SQL" %>
<%@ plsql procedure="favorite_plsql_book" %>
<HTML>
   <HEAD>
      <TITLE>My favorite book about PL/SQL</TITLE>
   </HEAD>
   <BODY>
      <%= booktitle( '0-596-00180-0') %>
   </BODY>
</HTML>

当我们把PSP正确地安装在一个Web服务器并建立了Oracle数据库连接时,页面的显示内容就会如图2-3所示。

作者个人很喜欢PL/SQL Server Page,因为它能把数据驱动的网页快速地整合在一起。

图2-3 PL/SQL Server Page输出

关于PL/SQL Server Page的更多信息,请参见本书作者的另一本书Learning Oracle PL/SQL

我们已经看到如何在SQL*Plus和其他一些常见环境及编程语言中使用PL/SQL了。还有其他许多地方和途径可以使用PL/SQL,具体如下。

遗憾的是,由于篇幅所限,本书无法覆盖所有的主题。


任何一种语言——无论是人类语言还是计算机语言——都有语法、词汇和字符集规则。如果要用某种语言进行交流,我们就要学习它的语言规则。许多人在学习一种新的计算机语言时,都显得很谨慎。改变总是让人担心,但总的说来,编程语言都是非常简单的,PL/SQL也不例外。语言是基于字节的,语言转换的难度不在于语言本身,而在于我们将要面对的编译器和计算机。大部分编译器都没什么“悟性”,它们不具创造性,无感知能力,不能进行源头性的思考,它们的词汇量有严重的局限性。编译器只能进行机械的、呆板的运作,但是非常快速。

当我们听到有人问“有一块钱吗?”时,在我们的脑海中,能很快地明白它的意思并决定如何回应。另一方面,如果我们对PL/SQL发出指令“查询后面的半打数据记录”,应用程序则完全不知所措。要想使用PL/SQL,我们必须遵循它的语法,对i’s和t’s正确地打点。所以,本章将介绍基本的语言规则,帮助我们与PL/SQL编译器打交道——语言规则包括PL/SQL块结构、字符集、词汇和PRAGMA关键字。

在PL/SQL中,和其他大多数结构化程序语言一样,最小的有意义的代码单元被称为块(block)。一个块就是一组代码,它是执行单位,也划定了变量声明和异常处理的边界。我们可以在PL/SQL中创建匿名块(即没有命名的代码块)和命名块,命名块可以是程序包、过程、函数、触发器或对象类型。

一个PL/SQL代码块最多由4个不同的部分组成,其中有一个部分是必须存在的。

头部(Header

仅存在于命名块,头部用于定义此命名块或程序将来的调用方式;是可选部分。

声明部分(Declaration Section

定义变量、游标以及被后面执行部分和异常处理部分引用的子块;是可选部分。

执行部分(Execution Section

包含了代码运行时PL/SQL引擎将要执行的部分;是必须存在的部分。

异常处理部分(Execption Section

对代码正常运行时出现的错误(警告或是报错)进行处理;是可选部分。

图3-1显示了一个过程的PL/SQL块结构。

图3-1 PL/SQL块结构

图3-2显示了一个包含了4个部分的过程块。这个块以关键字“PROCEDURE”开始,然后和所有的块一样,以关键字“END”结束。

图3-2 包含了4个部分的过程块

如果一个人想要隐名埋姓,他就被称为无名氏。在PL/SQL块中也有同样的匿名块,如图3-3所示:这个块没有头部分,直接以DECLARE或是BEGIN开始。这就意味着,它不能被其他代码块调用——因为它没有一个可以被引用的句柄。相反,匿名块可以作为一个执行PL/SQL语句的容器,通常用来调用其他过程和函数。因为匿名块可以有它自己的声明和异常处理部分,开发人员经常会在一个大的程序中嵌套一些匿名块,以提供一系列的标识符和异常处理。

图3-3 一个没有声明部分和异常处理部分的匿名块

一个定义PL/SQL匿名块的通用语法如下:

[ DECLARE ... declaration statements ... ]
BEGIN ... one or more executable statements ...
[ EXCEPTION
 ... exception handler statements ... ]
END;

方括号中的内容是语法的可选部分,BEGIN和END语句是必须存在的,同时必须要有至少一条可执行语句。实例如下。

BEGIN
   DBMS_OUTPUT.PUT_LINE(SYSDATE);
END;
DECLARE
   l_right_now VARCHAR2(9);
BEGIN
   l_right_now := SYSDATE;
   DBMS_OUTPUT.PUT_LINE (l_right_now);
END;
DECLARE
   l_right_now VARCHAR2(9);
BEGIN
   l_right_now := SYSDATE;
   DBMS_OUTPUT.PUT_LINE (l_right_now);
EXCEPTION
   WHEN VALUE_ERROR
   THEN
      DBMS_OUTPUT.PUT_LINE('I bet l_right_now is too small '
         || 'for the default date format!');
END;

运行时,匿名块执行一系列语句,然后终止,其行为和过程非常相似。实际上,所有的匿名块都是匿名过程。它们可以用于多种环境,在这些环境中,PL/SQL代码可能是直接执行的,也可能被包含在某些程序中。常见的例子包括:

数据库触发器(Database triggers

在第19章中将会介绍到。当特定事件发生时,数据库触发器运行匿名块。

临时命令或脚本文件(Ad hoc command or script files

在SQL*Plus或类似的执行环境中,匿名块是通过手工输入代码块或调用存储程序的脚本运行的。同时,SQL*Plus的EXECUTE命令把它的参数翻译成一个用一对BEGIN/END封装的匿名块。

编译后的3GL(第三代语言)程序

无论在Pro*C还是OCI中,匿名块都可以作为调用存储程序的工具。无论哪种情况,封装的对象——无论是触发器、命令行环境,还是一个编辑程序——都能给程序命名提供方法。

尽管PL/SQL是不可或缺的,但大多数我们所编写的代码还是以命名块的形式出现的。我们已经在本书中看到了一些简短的存储过程的示例(见图3-1),所以我们已经知道,二者的主要区别在于头部分。一个过程头的形式是这样的:

PROCEDURE [schema.]name [ ( parameter [, parameter ... ] ) ]
   [AUTHID {DEFINER | CURRENT_USER}]

函数的头部分也是类似的语法,但多了一个RETURN关键字:

FUNCTION [schema.]name [ ( parameter [, parameter ... ] ) ]
   RETURN return_datatype
   [AUTHID {DEFINER | CURRENT_USER}]
   [DETERMINISTIC]
   [PARALLEL ENABLE ...]
   [PIPELINED [USING...] | AGGREGATE USING...]

因为Oracle允许我们在SQL语句中调用函数,因此函数的头部分比过程的头部分有更多的选项,这些选项对应SQL运行环境的功能和性能。

在第17章,我们有关于过程和函数的详细讨论。

PL/SQL借鉴了Ada和Pascal作为块结构语言(block-structured language)而具有的更多的定义方式——也就是块可以“嵌套”到其他块中。作为对比,C语言也有块,但是标准C并不是严格的块结构,因为它的子程序不能嵌套。

下面的PL/SQL示例,是一个包含嵌套匿名块的过程:

PROCEDURE calc_totals
IS
   year_total NUMBER;
BEGIN
   year_total := 0;

   /* Beginning of nested block */
   DECLARE
      month_total NUMBER;
   BEGIN
      month_total := year_total / 12;
   END set_month_total;
   /* End of nested block */

END;

“/*”和“*/”分隔符代表“注释”(详见3.6节)。我们可以在匿名块中再嵌套匿名块,还可以多层嵌套,如图3-4所示。

图3-4 三层深度的嵌套匿名块

其他我们听到的有关嵌套块的称谓有:闭合块、子块;外层PL/SQL块一般称作包围块或父块。

一般来说,使用嵌套块的益处,是让我们在代码内方便地控制变量的作用范围。

在任何编程语言中,术语“作用范围”的意思,都是指明确规定一个给定的标识符所代表的“事物”。如果一个标识符多次出现,则语言中的作用范围规则决定了哪一个标识符是当前可用的。如果控制得好,标识符的作用范围可以增强我们对程序运行的控制,还可以减少程序员意外修改了错误变量的可能性。

在PL/SQL中,变量、异常、模块和其他一些结构,都是属于声明它们的块的。当块运行停止时,我们就无法再引用这些结构了。例如,在之前的calc_total过程中,我们可以对外部块中的任何内容进行引用,如year_total变量,然而,在内部块中声明的变量对外部块而言是不可见的。

每个PL/SQL变量都有其作用范围:能够调用这个变量的程序单元(块、子程序或程序包)。让我们看看下面这个程序包定义:

PACKAGE scope_demo
IS
   g_global NUMBER;

   PROCEDURE set_global (number_in IN NUMBER);
END scope_demo;

PACKAGE BODY scope_demo
IS
   PROCEDURE set_global (number_in IN NUMBER)
   IS
      l_salary NUMBER := 10000;
      l_count PLS_INTEGER;
   BEGIN

      <<local_block>>
      DECLARE
         l_inner NUMBER;
      BEGIN
         SELECT COUNT (*)
           INTO l_count
           FROM employees
          WHERE department_id = l_inner AND salary > l_salary;
      END local_block;

      g_global := number_in;
   END set_global;
END scope_demo;

任何schema的程序块,只要拥有对程序块scope_demo的执行权限,就可以调用变量scope_demo.g_global。

变量l_salary只能在set_global过程中被调用。

变量l_inner只能在局部块或嵌套块内调用。请注意,我特意使用“local_block”这个标签来命名嵌套块。

在上面的示例中,对变量和列的引用都没有使用规范的作用范围名称。下面是相同程序体的另一个版本,实施了规范化的引用规则(用粗体字进行提示):

PACKAGE BODY scope_demo
IS
   PROCEDURE set_global (number_in IN NUMBER)
   IS
      l_salary NUMBER := 10000;
      l_count PLS_INTEGER;
   BEGIN

      <<local_block>>
      DECLARE
         l_inner PLS_INTEGER;
      BEGIN
         SELECT COUNT (*)
           INTO set_global.l_count
           FROM employees e
          WHERE e.department_id = local_block.l_inner
            AND e.salary > set_global.l_salary;
      END local_block;

      scope_demo.g_global := set_global.number_in;
   END set_global;
END scope_demo;

经过这些改进,对每一个变量或列的引用都通过表的别名、程序包名、过程名或嵌套块的标签名称规范化了。

现在,我们知道这样做是可行的——但为什么要这样做?原因有以下几点:

下面我们来讨论前两个原因,第三个原因将在第20章进行详述。

提高可读性

总的来说,每个PL/SQL内嵌的SQL语句都包含了对变量和列的引用。在短小的SQL语句示例中,要区分引用的是变量还是列是比较容易的。然而,在几乎所有的实际应用中,我们遇到的都是冗长而又复杂的SQL语句,里面包含了数十个甚至上百个对变量和列的引用。

如果我们不对引用进行规范,那么要区分引用的是变量还是列将是非常困难的。采用了命名规范后,仅仅根据名称,我们就能基本分清到底引用的是变量还是列了。

“等一下。”猜到读者会这样说,“我们已经用定义清晰的命名规则来区分变量和列了,所有的局部变量都用l_开头,一看到这样的标识符就知道这是一个局部变量。”

这真是一个好主意,我们都应该制定(并且遵守)命名规则,这样通过标识符的名称,就能反映出它的大致内容(例如,它是一个参数还是一个变量?什么数据类型?)。

尽管命名规则很有帮助,但也不能完全保证随着时间的流逝,编译器能一直按照我们希望的那样去辨认标识符。

避免变量和列名一致时可能导致的bug

如果我们没有使用命名规范来引用内嵌在SQL语句中的PL/SQL变量,昨天还运行正常的代码,很有可能在明天就突然不能工作了,找到错误原因也会非常困难。

再来看看下面的嵌入式SQL语句,其中的引用是不规范的:

SELECT COUNT (*)
  INTO l_count
  FROM employees
 WHERE department_id = l_inner AND salary > l_salary;

当时,l_salary毫无疑问指的就是set_global过程中声明的l_salary变量。过程通过了测试——工作正常!随后,我们把它部署到正式环境中,皆大欢喜!

两年后,用户要求DBA在employees表中增加一列,用以记录“limited salary”信息。于是DBA把这列命名为“l_salary”。

现在,我们发现问题了吗?

在嵌入式SQL语句中,Oracle数据库首先把这些不规范的标识符当作列名来解释,如果找不到匹配的列,Oracle随后会试图把它当作当前作用范围内的PL/SQL变量来解释。现在,employees表中已经添加了l_salary列,在SELECT语句中,不规范的“l_salary”引用方式导致它在SELECT语句中不再被解释为PL/SQL变量,而是把它解释为表中的列了。

解释错误的后果是怎样的?我们的过程包scope_demo仍然会被编译成功,没有任何错误提示,但我们的查询的WHERE子句,无法再返回我们希望的结果。数据库不会使用l_salary变量的值,而是把employees表中每行记录的salary列的值与同一行的l_salary的值进行比较。

跟踪解决这个bug非常困难。

除了依靠清晰的命名规则来规避标识符之间的冲突,我们还应该把嵌入式SQL语句中对变量和列名的引用进行规范化。这样做的好处是,即使将来的底层数据表发生变化,我们的代码依然强壮,不会出现异常表现。

一个变量在其作用范围内时,它还有一个重要的属性——可见性,即,我们是否可以直接通过它的名字引用它,抑或是需要在名字上加上前缀。

可见的标识符

首先,我们观察一个小示例:

DECLARE
   first_day DATE;
   last_day DATE;
BEGIN
   first_day := SYSDATE;
   last_day := ADD_MONTHS (first_day, 6);
END;

变量first_day和last_day的声明和使用都是在同一个块中,我们可以非常方便地用“非规范化”使用的标识符名称来引用它们,在这种情况下,它们也被称作可见标识符。一个可见标识符可能是以下几种:

PL/SQL中也允许引用不直接可见、但是在作用范围内的对象或变量。

规范的标识符

说到不可见标识符,通常的例子是一个包含在程序包声明部分的变量、数据类型、过程或函数。在程序包外引用这些元素时,我们只需在元素名前增加一个带点的前缀来进行规范化,就像我们用表名做前缀来规范化列名一样,示例如下:

Price_util.compute_means

一个在price_util程序包中,叫作Price_util.compute_means的程序。

Math.pi

一个在Math程序包中声明并初始化的,叫作pi的变量。

(虽然从描述看,这些都像是全局变量,但我们并不能据此就做出判断——它们完全遵守了良好的命名规则!)

我们可以使用额外的标识符来指明对象的所有者,如:

scott.price_util.compute_means

就可以用来指向属于Oracle用户Scott的程序包price_util里的compute_means过程。

使用模块的名字来规范化标识符的名字

根据需要,PL/SQL提供了各种方法来规范化一个标识符,以确保对它的引用能够被正确解析。例如,利用程序包,我们可以创建全局标量。假定我创建了一个叫作company_pkg的程序包,并对其中的变量last_company_id进行了声明,像这样:

PACKAGE company_pkg
IS
   last_company_id NUMBER;
   ...
END company_pkg;

然后,我们就可以在程序包外引用变量last_company_id,前提是把程序包的名称作为标识符的前缀:

IF new_company_id = company_pkg.last_company_id THEN

在默认情况下,给一个包级别的变量进行赋值,其赋值仅仅在当前的数据库会话的生命周期内有效,当会话结束时赋值也就失效了。

我们也可以通过添加标识符所在的模块名称,来达到规范其命名的目的:

PROCEDURE calc_totals
IS
   salary NUMBER;
BEGIN
   ...
   DECLARE
      salary NUMBER;
   BEGIN
      salary := calc_totals.salary;
   END;
   ...
END;

第一个对salary的声明创建了一个作用范围为整个过程的标识符。在嵌套的程序块中,我们又声明了另一个同名的标识符。因此,当在内层程序块中引用变量salary时,它会被优先解释为内层块中声明的变量,因为这个标识符是无需任何规范化就可见的。如果我们希望在内层块中引用过程块中的全局变量salary,就必须在变量前加上过程的名称来进行规范化(cal_totals.salary)。

这种标识符规范化技术也适用于其他场合。现在,思考一下,如果我们运行下面的过程(order_id是orders表的主键),会发生什么?

PROCEDURE remove_order (order_id IN NUMBER)
IS
BEGIN
   DELETE orders WHERE order_id = order_id; -- Oops!
END;

运行这段代码,将导致orders表中的所有内容被删除,而不是我们所希望的,仅仅删除我们传递的特定order_id相关记录。原因在于SQL的名字解析规则:优先匹配列名,然后才是PL/SQL标识符,所以WHERE子句“order_id = order_id”条件永远为真,所有的记录都被删除了。解决这个问题的一种方法是:

PROCEDURE remove_order (order_id IN NUMBER)
IS
BEGIN
   DELETE orders WHERE order_id = remove_order.order_id;
END;

通过这种方法,可以强制解析器进行符合我们意图的解析(即使我们碰巧还有一个程序包函数叫作emove_order.order_id.,解析器也能正确工作)。

PL/SQL曾经遇到过很多类似麻烦,所以建立了一套规则来避免此类命名冲突。对类似的问题多一些了解当然很好,但最好是不要依赖这些指南。在编码的时候,就要有预见性。如果我们不希望为了保证变量的唯一性而对它们每一个都进行规范化,我们就需要小心地使用命名规范,从而避免此类冲突。

嵌套程序

现在,当我们对嵌套、作用范围、可见性进行讨论总结时,需要提到的是,PL/SQL还提供了一种特别重要的特性叫作嵌套程序。嵌套程序是这样的一个过程或函数,它是完整存在于包围块的声明部分的。值得注意的是,嵌套程序可以引用任何在外层块中声明的变量和参数,示例如下:

PROCEDURE calc_totals (fudge_factor_in IN NUMBER)
IS
   subtotal NUMBER := 0;

   /* Beginning of nested block (in this case a procedure). Notice
   | we're completely inside the declaration section of calc_totals.
   */
   PROCEDURE compute_running_total (increment_in IN PLS_INTEGER)
   IS
   BEGIN
      /* subtotal, declared above, is both in scope and visible */
      subtotal := subtotal + increment_in * fudge_factor_in;
   END;
   /* End of nested block */
BEGIN
   FOR month_idx IN 1..12
   LOOP
      compute_running_total (month_idx);
   END LOOP;
   DBMS_OUTPUT.PUT_LINE('Fudged total for year: ' || subtotal);
END;

使用嵌套程序,可以使我们的程序更易读,也更易于维护,也使得我们可以在块内的许多地方重复使用这个逻辑。关于更多的嵌套程序的讨论,参见第17章。

一个PL/SQL程序由一系列语句组成,而每一条语句都由一行或多行的文本构成。程序所能使用的字符集,依赖于数据库采用的字符集。例如,表3-1列出的是US7ASCII字符集中的可用字符。

表3-1 使用US7ASCII字符集时PL/SQL可用的字符

类  型

字  符

字母

A~Z,a~z

数字

0~9

符号

~ ! @ # $ % * () _ − + = | : ; “ ‘ < > , . ? / ^

空白

Tab、空格、换行、回车

每个PL/SQL关键字、操作符和记号,都是字符集中的各种字符的组合。我们要做的,就是思考如何把它们进行排列组合!

现在,是一些PL/SQL中的小细节——Oracle文档(以及本书的过去版本)把连接符、大括号、中括号都作为字符集的一部分。

& { } [ ]

尽管所有的字符都可以出现在文本字符串中,但似乎Oracle没有把上面5个字符用在任何PL/SQL的可见部分。进一步说,程序员也不把它们用在自定义的标识符内。

有一些细节我们可能记不住,但我们无论如何要记住,PL/SQL是一种忽略大小写的编程语言。也就是说,不管我们用大写还是小写输入关键字或是标识符,它们都会被当作小写来看待,除非我们用分隔符把它们括起来——这时它们被当作文本字符串来对待。按照惯例,本书对语言内置关键字(以及一些被Oracle用作内置函数和过程的名字的标识符)采用大写形式,而程序员自己定义的标识符则采用小写形式。

许多字符——单个字符和组合字符串——在PL/SQL中有一些特殊的含义。表3-2列出了这些特殊符号。

表3-2 PL/SQL中的单个字符和组合字符串

符  号

说  明

分号:代表声明或语句的结束

%

百分号:属性指示符(游标属性,如%ISOPEN;间接声明属性,如%ROWTYPE);也用作LIKE条件中的通配符

_

单条下划线:LIKE条件中的单个字符的通配符

@

At符号:远程位置指示符

冒号:宿主变量标识符,例如,Oracle Forms中的block.item

**

双星号:幂运算符

<>或 !=
或 ^= 或 ~=

几种代表“不相等”的关系运算符

||

双竖线:连接符

<<和>>

标签分隔符

<= 和>=

小于等于,大于等于关系运算符

:=

赋值操作符

=>

位置表示法使用的关联操作符

..

双句号:范围操作符

--

双横线:单行注释符号

/* 和 */

多行注释块的开始和结束符号

字符组合在一起,构成了词法单位,也被称为语言原子,它是最小的独立成员。PL/SQL中的词法单位可以是下面任何一种:

我们会在下面的内容中做详细介绍。

标识符就是一个PL/SQL对象的名字,包括以下类型:

PL/SQL标识符的默认属性有以下这些:

如果两个标识符的唯一区别就是有一个或几个字符的大小写不同,那么通常PL/SQL认为它们是完全一样的。例如,下面的几个标识符,对PL/SQL来说是一样的:

lots_of_$MONEY$
LOTS_of_$MONEY$
Lots_of_$Money$

下面这些,对PL/SQL而言,是有效的标识符名字:

company_id#
primary_acct_responsibility
First_Name
FirstName
address_line1
S123456

而下面这些名字,则被PL/SQL认为是非法无效的:

1st_year                                --未以字母开头
Procedure-name                          --含有无效字符“-”
minimum_%_due                            --含有无效字符“%”
maximum_value_exploded_for_detail        --超过30个字符的限制长度
company ID                               --含有空白字符

标识符是我们程序中对象的句柄,也是我们和其他程序员交流的形式,为达到交流顺畅的目的,许多组织都会定义命名规范。即使我们的项目没有命名规范的要求,我们在给变量命名时,也应多加小心……即使代码只是我们自己在看!

虽然在实践中很少有人会这么做,但事实上,我们可以通过使用双引号,来打破上述的某些规则。不建议编程时这样做,但在某些情况下,我们可以由此产生“漂亮”的代码,示例如下:

SQL> DECLARE
  2     "pi" CONSTANT NUMBER := 3.141592654; 
  3     "PI" CONSTANT NUMBER := 3.14159265358979323846; 
  4     "2 pi" CONSTANT NUMBER := 2 * "pi";
  5  BEGIN
  6     DBMS_OUTPUT.PUT_LINE('pi: ' || "pi");
  7     DBMS_OUTPUT.PUT_LINE('PI: ' || pi); 
  8     DBMS_OUTPUT.PUT_LINE('2 pi: ' || "2 pi");
  9  END; 
 10  /

pi: 3.141592654
PI: 3.14159265358979323846
2 pi: 6.283185308

请注意,第7行引用的pi没有带引号。因为编译器是通过把所有的标识符和关键字转换成大写来实现其“大小写不敏感”的特性的,所以第7行引用的变量实际上指向了第3行声明的“PI”。

如果数据库对象中存在大小写混用的名称,我们有时就要用到这类双引号的技巧了。我曾见过一个Microsoft Access的程序员在创建Oracle表时就是这么干的。

当然,我们不需(也不必)在程序中定义所有用到的标识符,PL/SQL语言中有一些特殊的标识符,它们有着特定的意义。

PL/SQL提供两种内置标识符:

在大多数情况下,我们都不应该而且不能在程序中对这些标识符进行重定义。

保留字

PL/SQL编译器保留了一些仅供其自身使用的标识符,换句话说,我们不能声明和这些标识符同名的变量。这些PL/SQL编译器保留的标识符就叫作保留字。例如,有一个常用和重要的保留字END,它表示程序块、IF语句和循环的结束,如果我们试图声明一个叫作“end”的变量:

DECLARE
   end VARCHAR2(10) := 'blip'; /* Will not work; "end" is reserved. */
BEGIN
   DBMS_OUTPUT.PUT_LINE (end);
END;
/

编译器则会返回以下错误信息:

PLS-00103: Encountered the symbol "END" when expecting one of the following:…

STANDARD程序包中的标识符

除了要避免与保留字重名,我们还应该避免使用(实际上,是会覆盖)那些Oracle公司在一个叫作STANDARD的特殊内置程序包中预定义的标识符。STANDARD是PL/SQL两个默认安装的内置程序包中的一个,在这个程序包中,Oracle定义了许多PL/SQL语言基础块,包含了如PLS_INTEGER这样的数据类型,如DUP_VAL_ON_INDEX这样的异常处理,如UPPER、REPLACE和TO_DATE这样的函数。

可能有许多PL/SQL开发者会觉得意外,但STANDARD(以及DBMS_STANDARD和其他默认程序包)中定义的标识符,都不是保留字。我们可以用相同的名字来定义自己的标识符,也可以编译通过。但是,这么做纯粹是自找麻烦,自挖陷阱。

我们会在第24章中对STANDARD程序包进行详细探讨。

如何避免使用保留字

给标识符取一个有效的名字不是难事,毕竟这些合法的字符有上万种组合,问题是:如果我们凑巧在程序里使用了一个保留字,我们怎样才会发现?首先,如果我们确实命名了一个保留字作为标识符,编译器会发出警示。如果我们的好奇心还想了解更多,我们可以在视图V$RESERVED_WORDS之上建一个查询,然后,试着对一个使用保留字做标识符的动态PL/SQL块进行编译。我以前这么干过,在本书的网站可以找到这个叫作reserved_words.sql的脚本,脚本运行后的输出文件叫作reserved.txt

运行结果相当有意思,下面是汇总:

Reserved Word Analysis Summary
Total count in V$RESERVED_WORDS = 1733
Total number of reserved words = 118
Total number of non-reserved words = 1615

换句话说,Oracle在这个视图中给出的大部分保留字都不是真正的保留字,也就是说,我们可以用它们给自定义标识符命名。

最终的建议是:避免使用任何Oracle在自己的技术中用到的名称。更好的方法是:使用命名规则,采用具有一致性的前缀和后缀,这可以最终保证我们不会与PL/SQL的真正保留字撞车。

标识符必须用至少一个空格或分隔符间隔开来。在进行格式处理时,我们可以增加额外的空格、行分隔符(开始新行和/或回车)、制表符等,而不会改变代码的含义。下面的两个代码块,其含义是等价的:

IF too_many_orders
THEN
   warn_user;
ELSIF no_orders_entered
THEN
   prompt_for_orders;
END IF;

IF too_many_orders THEN warn_user;
ELSIF no_orders_entered THEN prompt_for_orders;
END IF;

我们不能在一个词法单元内放入空格、回车或制表符,比如词法单元“不等于”(!=)。下面的语句会返回编译错误:

IF max_salary ! = min_salary THEN -- yields PLS-00103 compile error

原因在于,在代码中,“!”和“=”之间存在一个空格。

直接量就是不通过标识符表达,而是直接展示的值。下面就是我们在PL/SQL程序中会遇到的部分直接量:

数字

415, 21.6, 3.141592654f, 7D, NULL

字符串

‘This is my sentence’, ‘01-OCT-1986’, q‘hello!’, NULL

时间间隔

INTERVAL ‘25-6’ YEAR TO MONTH, INTERVAL ‘-18’ MONTH, NULL

布尔值

TRUE,FALSE,NULL

在直接量数值3.141592654f结尾处的f,表明这是一个符合IEEE754标准的32位浮点数值,Oracle公司从Oracle数据库10g第一版开始部分支持IEEE754标准。同样地,7D代表的是一个64位的浮点数。

这里需要对字符串q’hello!’做些解释。“!”是用户自定义的分隔符,这个概念也是在Oracle数据库10g里引入的。开头的“q”和包围的单引号告诉编译器,“!”是一个分隔符,整个字符串代表的就是一个单词hello。

INTERVAL数据类型让我们可以管理两个日期或时间戳之间的时间量。第一个时间间隔示例代表“25年零6个月之后”,第二个‘-18’代表“18个月之前”。

虽然数据库允许我们使用直接量来指定间隔值,但对DATE数据类型却不能这么做。请注意, '01-OCT-1986'是一个字符串而不是Oracle的DATE。尽管PL/SQL或SQL都可以隐式地将'01-OCT-1986'转换为Oracle内部日期格式,但我们一般都调用内置函数进行显示转换,示例如下:

TO_DATE('01-OCT-1986', 'DD-MON-YYYY')
TO_TIMESTAMP_TZ('01-OCT-1986 00:00:00 −6','DD-MON-YYYY HH24:MI:SS TZH')

两个表达式都返回October 1,1986,0时,0分,0秒。第一个表达式将字符串转换成DATE数据类型,而第二个表达式将字符串转换成TIMESTAMP WITH TIMEZONE数据类型。第二个表达式还包含了时区信息,-6代表的就是和GMT(UCT)之间的时差。

与标识符不同,PL/SQL中字符串是大小写敏感的。如我们所料,下面两个字符串是不同的:

'Steven'
'steven'

所以下面的条件表达式结果为FALSE:

IF 'Steven' = 'steven'

在Oracle数据库中,一个缺失的值是由关键字NULL来表示的。我们在前面提到,几乎所有数据类型的PL/SQL变量都可以存在null状态(唯一的例外就是关联数组类型,这种类型的实例绝不会为null)。对程序员来说,不管变量是什么数据类型都能正确地处理NULL有些困难,例如,空的字符串就需要特殊考虑。

Oracle SQL和PL/SQL中,一个空字符串通常和零个字符的字符串没有区别,后者表现为''(两个连在一起的单引号,之间没有任何字符)。例如,下面这个表达式,无论在SQL还PL/SQL中,都得到TRUE的结果:

'' IS NULL

在PL/SQL中,给一个类型为VARCHAR2(n)的变量赋值长度为0的字符串,也能得到NULL的结果:

DECLARE
   str VARCHAR2(1) := '';
BEGIN
   IF str IS NULL   -- will be TRUE

数据库在处理表中VARCHAR2类型的列时,也按照相同的逻辑。

现在我们来看看CHAR数据,这种数据类型有点怪异。如果我们在PL/SQL中创建一个CHAR(n)的变量,赋予其一个长度为0的字符串,数据库会用空格填满这个字符串,因此它就不为NULL。

DECLARE
   flag CHAR(2) := ''; -- try to assign zero-length string to CHAR(2)
BEGIN
   IF flag = ' '    ...    -- will be TRUE
   IF flag IS NULL ...    -- will be FALSE

奇怪的是,这种情况只在PL/SQL中存在。在数据库表中,当我们将0长度字符串插入到一个类型为CHAR(n)的列时,数据库不会用空格把列填满,所以其值为NULL!

这些示例说明,Oracle只是部分遵守了92到99版本的ANSI SQL标准,这个标准要求一个长度为零的字符串和NULL字符串必须要有所区别。Oracle认可了这个区别,承诺会在未来完全按照标准行事,但15年过去了,承诺还未兑现。

尽管NULL的表现总是显得它的默认数据类型是VARCHAR2,其实数据库会根据当前的操作需要的类型来对NULL进行隐式的数据转换。有时,我们也需进行显示转换,语法诸如TO_NUMBER(NULL)或CAST(NULL AS NUMBER)。

使用字符串直接量时,将不可避免地碰到一种讨厌的情况:我们需要把分隔符作为字符串的一部分。直到Oracle数据库10g为止,如果我们想让字符串中带有一个单引号,我们需要使用两个紧挨着的单引号。下面是一些例子。

直接量(默认分隔符)

实际值

'There''s no business like show business.'

There's no business like show business.

'"Hound of the Baskervilles"'

"Hound of the Baskervilles"

''''

'

'''hello'''

'hello'

''''''

''

这些示例表明,要想表示两个紧挨着的单引号,需要使用6个单引号。为了简化这个结构,Oracle数据库从10g版本开始,引入了用户定义分隔符的功能。用“q”做引导来标示自定义分隔符,然后用单引号把分隔表达式包围起来。下面的示例演示了其特性。

直接量(默认分隔符)

实际值

q' ( There's no business like show business.) '

There's no business like show business.

q' { "Hound of the Baskervilles" } '

"Hound of the Baskervilles"

q' [ ' ] '

'

q' !'hello' ! '

'hello'

q' |'' | '

''

就像示例所演示的,我们可以用简单分隔符诸如“!”和“|”,或者也可以使用“配对”的分隔符如左右括号、花括号和方括号。

最后要注意一点:与我们的预期一致,一个上引号字符在一个字符串直接量中没有任何特殊含义,把它当作一个字母或数字就可以了。

数字直接量可以是整数或实数(带有小数部分的数)。请注意,PL/SQL把154.00看作NUMBER类型的实数,虽然小数部分是零,其值实际上是整数。在数据库内部,整数和实数有不同的标示,在对二者进行数据转换时,有少量的系统开销。

我们也可以使用科学计数法表示一个数字直接量,使用字母E(大小写均可)标示把数字乘以10的n次方(如3.05E19,12e-5)。

从Oracle数据库10g开始,实数可以是Oracle NUMBER类型,也可以是IEEE754标准规定的浮点类型。浮点类型的直接量可以是BINARY(32位,以F结尾标示),也可以是BINARY DOUBLE(64位,以D结尾标示)。

在一些表达式中,我们可以使用下面已经命名的常量,它们已由IEEE标准进行了定义。

说  明

BINARY浮点(32位)

BINARY DOUBLE(64位)

“不是一个数字”(NaN),被零除或者一个无效的操作的结果

BINARY_FLOAT_NAN

BINARY_DOUBLE_NAN

正无穷

BINARY_FLOAT_INFINITY

BINARY_DOUBLE_INFINITY

能够表示的最大绝对数

BINARY_FLOAT_MAX_NORMAL

BINARY_DOUBLE_MAX_NORMAL

最小的常数,向下溢出

BINARY_FLOAT_MIN_NORMAL

BINARY_DOUBLE_MIN_NORMAL

比向下溢出小的最大整数

BINARY_FLOAT_MAX_SUBNORMAL

BINARY_DOUBLE_MAX_SUBNOR
MAL

能够表示的最小绝对数

BINARY_FLOAT_MIN_SUBNORMAL

BINARY_DOUBLE_MIN_SUBNOR
MAL

PL/SQL提供了两个表达布尔值的直接量:TRUE和FALSE。这两个值不是字符串,所以我们不应给它们加引号。可以用布尔直接量给布尔变量赋值,像下面这样:

DECLARE
   enough_money BOOLEAN; -- Declare a Boolean variable
BEGIN
   enough_money := FALSE; -- Assign it a value
END;

另一方面,在检查布尔表达式的值时也无需引用这些直接量,让表达式自己来展现即可,就像下面这个IF语句中的条件子句一样:

DECLARE
   enough_money BOOLEAN;
BEGIN
   IF enough_money
   THEN
      ...

一个布尔表达式、变量或常量也可以等于NULL,即既不是TRUE也不是FALSE。在第4章有更多这方面的讲述,尤其是其中“三值逻辑”的部分。

一个PL/SQL程序是由一系列的声明和语句组成的,这些声明和语句都是逻辑定义,而非物理定义,换句话说,每句不是根据物理行的换行作为结束,而是用分号(;)作为结束。实际上,为了提高可读性,一条语句经常会被分成多行。下面的示例中,IF语句编写占了4行,并且通过缩排来强调隐藏在语句后面的逻辑:

IF salary < min_salary (2003)
THEN
   salary := salary + salary * .25;
END IF;

这条IF语句中有2个分号,第一个分号表示的是在IF-END的IF结构中可执行语句的结束,第二个分号是整个IF语句的结束。这个语句也可以全部放在一行中,结果完全相同:

IF salary < min_salary (2003) THEN salary := salary + salary*.25; END IF;

即便是嵌入在另一个分号里面,每个逻辑判断、可执行语句也需要用分号结束,否则创建出的代码将无人能读。作者强烈建议,不要把IF语句的不同部分放到一行中,也不要在一行中编写超过一个语句或声明。

行内文档,也叫作注释,是一个良好代码的重要组成部分。尽管贯穿本书,从规范命名实践到程序模块化,对于如何写出能够自说明的程序提供了很多建议,不过单靠这些技术本身,仍不足以对一个复杂的程序做出足够清晰的说明,所以我们还需要辅以注释。

PL/SQL提供两种形式的注释:单行注释和多行注释块。

单行注释使用两个连字符开始(--),两个连字符中间不可存在空格或其他字符。在一个物理行中,这两个连字符以后的文本都被看作注释,直到本行结束,注释会被编译器自动忽略。如果某一行开始的字符就是两个连字符,则整行都会被当作注释对待。

请记住,两个连字符注释的是物理行其位置以后的内容,而不是一个逻辑的PL/SQL语句。在下面的IF语句中,我们用一个单行注释来解释布尔表达式的逻辑:

IF salary < min_salary (2003)  --函数返回当年的最低月薪
THEN
   salary := salary + salary * .25;
END IF;

尽管单行注释对于代码简短说明或忽略一行暂时不想执行的语句很有效,但对于很长的注释块来说,多行注释的方式会更好一些。

多行注释块以一个斜线+星号(/*)开始,以另一个星号+斜线(*/)结束。PL/SQL把这两组符号之间的内容全部当作注释对待,编译器在编译时也会对其中内容进行忽略。

下面是一个多行注释的示例,注释出现在过程块的头部。我们在左边使用了竖线,当眼睛移到程序的左边时,可以很容易地识别出注释部分:

PROCEDURE calc_revenue (company_id IN NUMBER) IS
/*
| Program: calc_revenue
| Author: Steven Feuerstein
| Change history:
|   10-JUN-2009 Incorporate new formulas
|   23-SEP-2008 - Program created
|*/
BEGIN
   ...
END;

我们也可以把多行注释用于代码测试过程。在下面的示例中,EXIT语句中的附加子句会被当作注释行进行忽略,从而将测试聚焦在a_delimiter函数:

EXIT WHEN a_delimiter (next_char)
/*
            OR
          (was_a_delimiter AND NOT a_delimiter (next_char))
*/
;

一个真正来源于希腊语的编程概念就是pragma,字面含义是“契约”,隐含的意思有“动作”。在许多编程语言中,pragma 通常是一行源代码,用于告诉编译器如何对程序进行编译。就像提供给编译器的一个选项,可以导致程序在运行时间上的不同表现,但它不会被直接翻译成字节代码。

PL/SQL的PRAGMA关键字语法如下:

PRAGMA instruction_to_compiler;

PL/SQL编译器能够接受出现在声明单元中任何地方的这种指令,但对大多数编译器而言,对指令出现的地方还是有着一些附加要求的。

PL/SQL提供如下几种pragma:

AUTONOMOUS_TRANSACTION

这个指令告诉PL/SQL运行引擎,当前程序块对数据库所做修改的提交或回滚,将不影响主事务或外层事务。详见第14章。

EXCEPTION_INIT

这个指令告诉编译器,把一个指定的错误代码与我们在声明块中定义的标识符关联起来。这个指令必须跟在异常声明的后面。详见第6章。

RESTRICT_REFERENCES

指令告诉编译器一个程序包的纯度级别(避免副作用)。详见第17章。

SERIALLY_REUSABLE

指令告诉PL/SQL运行引擎,程序包级别的数据在引用之间不应该保留。详见第18章。

下面的代码块演示了EXCEPTION_INIT的用法,用这个指令命名一个内置异常而且对应一个数字,如果没有PRAGMA指令,我们只能得到一个数字:

DECLARE
   no_such_sequence EXCEPTION;
   PRAGMA EXCEPTION_INIT (no_such_sequence, −2289);
BEGIN
   ...
EXCEPTION
   WHEN no_such_sequence
   THEN
      q$error_manager.raise_error ('Sequence not defined');
END;

PL/SQL标签是为我们的程序中特定部分命名的方法。从语法角度,标签的格式是:

<<identifier>>

其中的identifier是一个有效的PL/SQL标识符(以字母开头,最大长度30位,在前面已有所论述),无需结束符。标签直接放在要标注的内容的前面,要标注的内容必须是一个可执行语句——哪怕仅仅是一个NULL语句:

BEGIN
   ...
   <<the_spot>>
   NULL;

因为匿名块本身就是可执行的语句,所以一个标签可以为执行过程中的匿名块“命名”。示例如下:

<<insert_but_ignore_dups>>
BEGIN
   INSERT INTO catalog
   VALUES (...);
EXCEPTION
   WHEN DUP_VAL_ON_INDEX
   THEN
      NULL;
END insert_but_ignore_dups;

我们为一个程序块加标签的原因之一是提高代码的可读性。当我们给程序块命名时,就相当于给代码添加了说明文档,我们也可以澄清思路,说明代码块的作用,避免在过程中出错。

另一个加标签的原因是可以从当前、内嵌的代码块中,规范地引用一个来自外层块中的具有相同名称的元素。示例如下:

<<outerblock>>
DECLARE
   counter INTEGER := 0;
BEGIN
   ...
   DECLARE
      counter INTEGER := 1;
   BEGIN
      IF counter = outerblock.counter
      THEN
         ...
      END IF;
   END;
END;

如果没有块标签,我们就无法区分这两个counter变量。再重复说一次,更好的方法还是使用不同的变量名称。

标签的第三个作用是作为GOTO语句的跳转指示目标。这在第4章中会有详述。

尽管我所见过的或使用过的程序中很少需要使用标签,但这个特性还有一个最终用途,比前面三个作用加起来更重要:标签可以作为嵌套循环中EXIT语句的指示目标。示例如下:

BEGIN
    <<outer_loop>>
    LOOP
       LOOP
          EXIT outer_loop;
       END LOOP;
       some_statement;
    END LOOP;
END;

如果没有<<outer_loop>>标签,EXIT语句会退出内循环,转而执行some_statement。但这并不是我们希望的结果。所以,在这个示例中,标签提供的跳转指示功能比其他PL/SQL方法都来得更直接有效。


相关图书

Oracle从入门到精通
Oracle从入门到精通
Oracle 12c数据库应用与开发
Oracle 12c数据库应用与开发
Oracle PL/SQL必知必会
Oracle PL/SQL必知必会
Oracle性能优化与诊断案例精选
Oracle性能优化与诊断案例精选
Oracle数据库管理与维护实战
Oracle数据库管理与维护实战
Oracle SQL疑难解析
Oracle SQL疑难解析

相关文章

相关课程