互联网环境,数据是第一生产力,数据的存储至关重要。虽然去IOE说了很多年,但是传统行业还是有在用Oracle的。

环境

Oracle 10g安装和配置

Oracle 简介

  • Oracle 是殷墟出土的甲骨文(oracle bone inscriptions)的英文翻译的第一个单词
  • Oracle 公司是全球最大的信息管理软件及服务供应商,成立于1977年,总部位于美国加州Redwood shore
  • Oracle 公司因其复杂的关系数据库产品而闻名。Oracle的关系数据库是世界第一个支持SQL语言的数据库

image-20230927204710923

Oracle数据库是一种网络上的数据库, 它在网络上支持多用户, 支持服务器/客户机等部署(或配置)

服务器与客户机是软件概念, 它们与计算机硬件不存在一一对应的关系. 即: 同一台计算机既可以充当服务器又可以充当客户机, 或者, 一台计算机只充当服务器或只充当充当客户机

Oracle 10g服务器端的安装

Oracle 10g 数据库服务器企业版的安装要求

  • CPU: Pentium 1.6G Hz 以上
  • 内存: 512MB 以上
  • 可用硬盘空间: 
    •  系统盘: 500 MB 以上
    •  安装盘: 1.6G 以上

安装方法

image-20230927204715960

image-20230927204720187

image-20230927204724673

image-20230927204728766

Oracle数据库体系结构简介

  • 平常所说的 Oracle 或 Oracle 数据库指的是 Oracle 数据库管理系统. Oracle 数据库管理系统是管理数据库访问的计算机软件(Oracle database manager system). 它由 Oracle 数据库和 Oracle 实例(instance)构成.
  • Oracle 数据库: 一个相关的操作系统文件(即存储在计算机硬盘上的文件)集合,这些文件组织在一起, 成为一个逻辑整体, 即为 Oracle 数据库.
    • Oracle 用它来存储和管理相关的信息.Oracle数据库必须要与内存里实例合作,才能对外提供数据管理服务。
  • Oracle 实例: 位于物理内存里的数据结构,它由操作系统的多个后台进程和一个共享的内存池所组成,共享的内存池可以被所有进程访问.
    • Oracle 用它们来管理数据库访问.用户如果要存取数据库(也就是硬盘上的文件) 里的数据, 必须通过Oracle实例才能实现, 不能直接读取硬盘上的文件.
    • 实际上, Oracle 实例就是平常所说的数据库服务(service) .
  • 区别:实例可以操作数据库;在任何时刻一个实例只能与一个数据库关联,访问一个数据库;而同一个数据库可由多个实例访问(RAC)
    • 比如京东,一个京东账号只能登陆京东网站,而一个京东网站可以被多个京东账号登录访问,例子不是很恰当,大致理解下.

image-20230927204733646

image-20230927204738262

image-20230927204741831

image-20230927204746811

ps: oracle创始人埃里森纪念公司第一位程序员scott,而Scott家有只猫,叫tiger

image-20230927204751173

image-20230927204755799

Oracle 数据库的启动

image-20230927204802558

ps: 学习的时候,为了不让oracle一直开着耗内存,我们一般关闭oracle服务,用的时候开,而不是一直开着。一般我们只需要开listener监听和serviceORCL这两项(设置为手动),其他的可以禁用

1
2
3
4
OracleDBConsoleORCL是Oracle网页端管理工具的服务,访问地址一般为“http://127.0.0.1:1158/em/console/logon/logon”,如果不习惯用这个来管理数据库可以不用启动。
OracleJobSchedulerORCL是管理Oracle中计划任务的,一般不用启动。
OracleOraDb10g_home1iSQL*Plus是SQL Plus的服务,如果不习惯在命令行下面操作数据库,可以不用启动。
OracleServiceORCL,OracleOraDb10g_home1TNSListener都需要开启,前者是主服务,后者是监听服务。

image-20230927204806699

安装完可以用自带的sqlplus测试是否可以连接

image-20230927204811574

Oracle 10g客户机的安装

要从局域网内的一台计算机上访问另一台计算机上的 Oracle 服务. 需要在此计算机上安装能通过局域网访问另一台计算机上的 Oracle 服务的客户机.

image-20230927204816129

image-20230927204821526

image-20230927204826009

image-20230927204830221

image-20230927204835741

image-20230927204840812

image-20230927204845104

image-20230927204849672

image-20230927204854036

image-20230927204859708

image-20230927204903519

image-20230927204907317

image-20230927204911036

  • 若测试成功,则忽略此步。
  • 如果测试不成功,需要操作如下: 
    • 通过开始-服务端选项的“配置和移植工具”-net manager来配置监听文件,并重启服务-homeListener 
    • 通过开始-服务端选项的“配置和移植工具”-net configuration assitant来加载监听器和本地Net服务连接

image-20230927204914447

image-20230927204918675

image-20230927204926044

image-20230927204933750

image-20230927204938902

image-20230927204943340

用 SQL*PLUS 访问 Oracle 数据库

image-20230927204947254

image-20230927204952683

Oracle数据库的卸载

image-20230927204957008

image-20230927205002021

image-20230927205005986

image-20230927205009938

image-20230927205014123

image-20230927205018544

image-20230927205022849

  • 以上只是简单的将Oracle卸载掉了,还学要对注册表进行修改
  • 修改注册表,在开始-运行中执行regedit命令,进入注册表,对注册表中的键值进行修改
    • 将HKEY_CLASS_ROOT下所有以ORACLE或者ORAL开头的注册表项删除 
    • 将HKEY_LOCAL_MACHINE\SOFTWARE下ORACLE注册表项删除 
    • 将HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Service下的以Oracle开头的注册表项删除

     

  • 重新启动计算机 
  • 删除 c:\Program Files\Oracle目录

通过以上步骤才正真的完成了Oracle的卸载,此时可以安装新的Oracle

Oralce 11g安装


全局数据库名/口令: orcl/xxxxx

image-20230927205027327

注意点

1.安装之前修改配置文件

image-20230927205031892

2.使用pqsql连接的时候,如果报标识符错误,可以重新配置一下试试

image-20230927205040225

安装和登录oracle

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
1.安装oracle数据库,并通过sql plus或pl/sql dev登录到数据库,并能够实现基本的sql语句操作。
1)安装服务端
2)在我的电脑-管理-服务。将oracleServiceORCL和OracleHomeListener设置为手动启动,其他选项关闭。
3)安装客户端
4)通过开始-服务端选项的“配置和移植工具”-net manager来配置监听文件
5)重启一下服务里的OracleHomeListener
6)通过开始-服务端选项的“配置和移植工具”-net configuration assitant来加载监听器和本地Net服务连接。
7)通过sql plus或pl/sql dev来连接数据库
注:可以在“企业用户管理器”进行用户的添加、删除、具体用户的权限设置等操作。

2.通过scott用户登录orcl数据库。
1)登录企业用户管理器,选择orcl数据库,通过system用户或者DBA的身份访问orcl数据库
2)选择:安全性-用户,将scott用户解锁。
3)用pl/sql developer的scott用户登录orcl数据库。

3.自己创建一个数据库,数据库名命名为myorcl。

4.在已有的数据库下,添加用户,并赋予其相应的权限。

卸载Oracle 10g

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
如何卸载Oracle 10g
关键字: 如何卸载oracle 10g
软件环境:
1、Windows XP + Oracle 10g
2、Oracle安装路径为:d:\Oracle
实现方法:
1、开始->设置->控制面板->管理工具->服务停止所有Oracle服务;
2、开始->程序->Oracle – OraDb10g_home1>Oracle Installation Products-> Universal Installer 卸装所有Oracle产品,但Universal Installer本身不能被删除;
3、运行regedit,选择HKEY_LOCAL_MACHINE\SOFTWARE\ORACLE,按del键删除这个入口;
4、运行regedit,选择HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services,滚动这个列表,删除所有Oracle入口;
5、运行refedit,选择HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Eventlog\Application,删除所有Oracle入口;
6、开始->设置->控制面板->系统->高级->环境变量,删除环境变量CLASSPATH和PATH中有关Oracle的设定;
7、从桌面上、STARTUP(启动)组、程序菜单中,删除所有有关Oracle的组和图标;
8、删除c:\Program Files\Oracle目录;
9、重新启动计算机,重起后才能完全删除Oracle所在目录 ;
10、删除与Oracle有关的文件,选择Oracle所在的缺省目录C:\Oracle,删除这个入口目录及所有子目录,并从Windows XP目录(一般为d:\WINDOWS)下删除以下文件ORACLE.INI、oradim73.INI、oradim80.INI、oraodbc.ini等等;
11、WIN.INI文件中若有[ORACLE]的标记段,删除该段;
12、如有必要,删除所有Oracle相关的ODBC的DSN;
13、到事件查看器中,删除Oracle相关的日志 说明:如果有个别DLL文件无法删除的情况,则不用理会,重新启动,开始新的安装,o安装时,选择一个新的目录,则,安装完毕并重新启动后,原来的目录及文件就可以删除掉了。

oracle安装sid重复问题


Oracle卸载后再次安装,设置的SID相同出现“指定的SID在本机上已经存在。请指定一个不同的SID。”

SID简介

   SID也就是安全标识符(Security Identifiers),是标识用户、组和计算机帐户的唯一的号码。在第一次创建该帐户时,将给网络上的每一个帐户发布一个唯一的 SID。Windows 2000 中的内部进程将引用帐户的 SID 而不是帐户的用户或组名。如果创建帐户,再删除帐户,然后使用相同的用户名创建另一个帐户,则新帐户将不具有授权给前一个帐户的权力或权限,原因是该帐户 具有不同的 SID 号。安全标识符也被称为安全 ID 或 SID。

SID的作用

   用户通过验证后,登陆进程会给用户一个访问令牌,该令牌相当于用户访问系统资源的票证,当用户试图访问系统资源时,将访问令牌提供给 Windows NT,然后 Windows NT 检查用户试图访问对象上的访问控制列表。如果用户被允许访问该对象,Windows NT将会分配给用户适当的访问权限。

  访问令牌是用户在通过验证的时候有登陆进程所提供的,所以改变用户的权限需要注销后重新登陆,重新获取访问令牌。

SID号码的组成

  如果存在两个同样SID的用户,这两个帐户将被鉴别为同一个帐户,原理上如果帐户无限制增加的时候,会产生同样的SID,在通常的情况下SID是唯一的,他由计算机名、当前时间、当前用户态线程的CPU耗费时间的总和三个参数决定以保证它的唯一性。

  一个完整的SID包括:

  · 用户和组的安全描述

  · 48-bit的ID authority

  · 修订版本

  · 可变的验证值Variable sub-authority values

  例:S-1-5-21-31044058 8- 2 500 36847- 5 803 895 05-500

  我们来先分析这个重要的SID。第一项S表示该字符串是SID;第二项是SID的版本号,对于 2000来说,这个就是1;然后是标志符的颁发机构(identifier authority),对于2000内的帐户,颁发机构就是NT,值是5。然后表示一系列的子颁发机构,前面几项是标志域的,最后一个标志着域内的帐户和组。

SID的获得

  开始-运行-regedit32-HKEY_LOCAL_MACHINESAMSAMDomainsBuiltinAliasesMembers,找到本地的域的代码,展开后,得到的就是本地帐号的所有SID列表。

  其中很多值都是固定的,比如第一个000001F4(16进制),换算成十进制是500,说明是系统建立的内置管理员帐号administrator,000001F5换算成10进制是501,也就是GUEST帐号了,详细的参照后面的列表。

  这一项默认是system可以完全控制,这也就是为什么要获得这个需要一个System的Cmd的Shell的原因了,当然如果权限足够的话你可以把你要添加的帐号添加进去。

  或者使用Support Tools的Reg工具:

1
reg query "HKEY\_LOCAL\_MACHINESOFTWAREMicrosoftWindows NTCurrentVersionProfileList

  还有一种方法可以获得SID和用户名称的对应关系:

   1. Regedit32:
1
HKEY\_LOCAL\_MACHINESOFTWAREMicrosoftWindows NTCurrentVersion ProfileList
   2. 这个时候可以在左侧的窗口看到SID的值,可以在右侧的窗口中ProfileImagePath看到不同的SID关联的用户名,
1
2
3
比如
%SystemDrive%Documents and SettingsAdministrator.momo这个对应的就是本地机器的管理员SID
%SystemDrive%Documents and SettingsAdministrator.domain这个就是对应域的管理员的帐户

  另外微软的ResourceKit里面也提供了工具getsid,sysinternals的工具包里面也有Psgetsid,其实感觉原理都是读取注册表的值罢了,就是省了一些事情。

 

Oracle SID重复解决方案

Oracle卸载后再次安装,设置的SID相同出现“指定的SID在本机上已经存在。请指定一个不同的SID。”

实现方法:

1、 开始->设置->控制面板->管理工具->服务 停止所有Oracle服务。

2、 开始->程序->Oracle - OraHome81->Oracle Installation Products-> Universal Installer 卸装所有Oracle产品,但Universal Installer本身不能被删除

5、 运行regedit,选择HKEY_LOCAL_MACHINE\SOFTWARE\ORACLE,按del键删除这个入口。

6、 运行regedit,选择HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services,滚动这个列表,删除所有Oracle入口。

7、 运行regedit,HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Eventlog\Application,删除所有Oracle入口。

8、 开始->设置->控制面板->系统->高级->环境变量 删除环境变量CLASSPATH和PATH中有关Oracle的设定

9、 从桌面上、STARTUP(启动)组、程序菜单中,删除所有有关Oracle的组和图标

10、 删除\Program Files\Oracle目录

11、 重新启动计算机,重起后才能完全删除Oracle所在目录

12、 删除与Oracle有关的文件,选择Oracle所在的缺省目录C:\Oracle,删除这个入口目录及所有子目录,并从Windows 2000目录(一般为C:\WINNT)下删除以下文件ORACLE.INI、oradim73.INI、oradim80.INI、 oraodbc.ini等等。

13、 WIN.INI文件中若有[ORACLE]的标记段,删除该段

14、 如有必要,删除所有Oracle相关的ODBC的DSN

15、 到事件查看器中,删除Oracle相关的日志

说明:

其中,6、7两条为重点删除对象,通常删除时我们易删除5,但不易发现6、7. 

如果有个别DLL文件无法删除的情况,则不用理会,重新启动,开始新的安装,

安装时,选择一个新的目录,则,安装完毕并重新启动后,老的目录及文件就可以删除掉了。

全局数据库名/口令

全局数据库名/口令: orcl/Axxxx

  • sys/sys
  • system/system
  • scott/tigger

口令管理:ORACLEaxxxx

新口令:

SCOTT:SCOTTaxxxx

HR:HRaxxxx

安装ORACLE时,若没有为下列用户重设密码,则其默认密码如下:

用户名 / 密码                 登录身份              说明

sys/change_on_install       SYSDBA 或 SYSOPER        不能以 NORMAL 登录,可作为默认的系统管理员

system/manager              SYSDBA 或 NORMAL         不能以 SYSOPER 登录,可作为默认的系统管理员

sysman/oem_temp             sysman                   为 oms 的用户名

scott/tiger                 NORMAL             普通用户

aqadm /aqadm                SYSDBA 或 NORMAL         高级队列管理员

Dbsnmp/dbsnmp            SYSDBA 或 NORMAL         复制管理员

登录身份:指登录时的Role指定,oracle11g中分SYSDBA和default两种。

Oracle数据库管理工具

利用 Oracle 企业管理器连接数据库服务器

oracle_11G

Oracle 11g之后Enterprise Manager Console的程序转化成了web页面来进行数据库的管理,触发管理器的方式如下:

image-20230927205047511

image-20230927205051692

点击“用户”之后出现如下列表:

image-20230927205055642

一、创建用户

点击“创建”便可以新建用户了

image-20230927205100098

二、对象权限

点击 “对象权限” 之后出现如下情况

image-20230927205104730

image-20230927205108852

点击“选择对象类型”之后便出现下拉列表,点击“表”之后再点击“添加”就会出现如下视图:

image-20230927205113140

点击箭头所指的地方便可以出现如下视图:

image-20230927205116854

image-20230927205122190

勾选“DEPARTMENTS”之后点击“选择”便会出现如下视图:

image-20230927205127003

点击“确定”之后便会出现如下视图:

image-20230927205131456

三、系统权限

点击“系统权限”之后便会出现如下视图:

image-20230927205135790

点击“编辑列表”之后便会出现如下视图:

image-20230927205142946

点击“确定”之后系统权限就添加成功了。

再回到如下视图:

image-20230927205147548

点击“确定”之后用户就添加成功了,成功之后的列表如下所示:

image-20230927205152194

oracle_10G

image-20230927205532402

image-20230927205520644

image-20230927205515449

image-20230927205511589

image-20230927205506331

用户管理

image-20230927205501810

image-20230927205457022

创建用户账号

image-20230927205452637

image-20230927205448035

image-20230927205443628

查看用户账号

image-20230927205438586

Oracle 的(资源限制)概要文件

  • 为了控制系统资源的使用, 可以利用资源限制概要文件.
  • 资源限制概要文件是 Oracle 安全策略的重要组成部分, 利用资源限制概要文件可以对数据库用户进行基本的资源限制, 而且还可以对用户的口令进行管理.
  • 使用资源限制概要文件可以限制下列资源的使用
    • 每个会话或每个语句的 CPU 时间(以百分之一秒计)
    • 每个用户的并发数据库会话
    • 每个会话的最大链接事件和空闲时间(以分计)
    • 可供多线程服务器会话使用的最大的服务器内存.
  • 使用资源限制概要文件可以对每个指定此概要文件的用户账号进行一下设置
    • 允许用户连续输入错误口令的次数, 在此之后 Oracle 将锁定账户
    • 口令的过期时间(以天计)
    • 允许用户使用一个到期口令的天数, 这之后 Oracle 将锁定账号 
    • 是否检查一个账号口令的复杂性, 以防止账号使用明显的口令
  • 每个 Oracle 数据库都有一个默认的资源概要文件, 名为 DEFAULT
  • 当创建一个新的数据库用户且不对用户分配一个特定的概要文件时, Oracle 自动给用户分配数据库的 DEFAULT 概要文件. 默认时,数据库 DEFAULT 概要文件的所有资源限制设置为无限制的.

利用企业管理器查看概要文件

image-20230927205432759

创建概要文件

image-20230927205428204

模式(schema)

  • 模式: 组织相关数据库对象的一个逻辑概念, 与数据库对象的物理存储无关. 一个模式只能属于一个数据库用户, 而且模式的名称与用户的名称相同.
  • Oracle 数据库的每个用户都拥有唯一的模式. 默认情况下, 用户所创建的所有模式对象都保存在自己的模式中.在 Oracle 数据库中模式与用户账号为一一对应的关系
  • 如果要从一个模式中引用另一个模式中的对象, 可以使用 点表示法. 不同模式中的对象名可以重复.
1
atguigu要访问 scott 用户的 emp 表,    scott.emp

模式对象和非模式对象

  • 能包含在模式中的对象称为模式对象.
  • Oracle 数据库中有许多类型的对象, 但不是所有的对象都可以组织在模式中. 可以组织在模式中的对象有: 表, 索引, 触发器等.
  • 有一些不属于任何模式的数据库对象, 称为非模式对象. 如: 表空间, 用户账号, 角色, 概要文件等.

用户的默认表空间

  • 表空间是数据库的逻辑存储设备, 它把数据库信息组织成物理存储空间.
  • 表空间由数据文件组成.用户的各种模式对象(如表, 索引, 过程, 触发器等) 都是放在表空间中.
  • 对每个数据库用户, 都可以设置一个默认表空间. 当用户创建一个新的数据库对象(如表), 并且不明确地为此对象指定表空间时, Oracle 会把所创建的这个新数据库对象存放到用户默认的表空间中.
  • 如果不给用户指定默认表空间, 则用户的默认表空间为 USERS 表空间.

用户的临时表空间

  • 一般, SQL 语句在完成任务时需要临时工作空间. 例如:一个用来连接和排序大量的查询需要临时工作空间来存放结果. 除非另外指定, 一般情况下, 用户的临时表空间是 TEMP 表空间.
  • 若数据库中没有创建 TEMP 表空间, 则用户的临时表空间为 SYSTEM 表空间.
  • 因为 SYSTEM 表空间是用来保存数据库系统信息(数据库自身信息的内部系统表和视图 ---- 数据字典; 所有 PL/SQL 程序的源代码 ---- 包括函数, 触发器等)的. 如果用户大量使用此表空间存储自己的数据, 将会影响系统的执行效率. 因此一般不建议用户使用 SYSTEM 表空间

权限管理

  • 在为一个 Oracle 数据库系统创建用户之后, 这些用户既不能与数据库服务器连接, 也不能做任何事情, 除非他们具有执行特定数据库操作的权限.
  • Oracle 中的数据库访问权限类型共有两种:
    • 系统权限:
      • 一种功能很强的权限, 他向用户提供了执行某                
      • 一种或某一类型的数据库操作的能力.

       

    • 对象权限: 
      • 控制用户是否能在特定数据库对象(如表, 视图或存储过程) 上执行特定类型的操作.

常用的系统权限

image-20230927205422195

使用系统权限

  • 用户连接到数据库必须具备 create session 权限.
  • 如果用户具有 create any procedure 系统权限, 则能够创建, 修改, 删除或执行任何存储过程, 程序包和函数
  • 如果用户具有 create any table 系统权限, 则能够在自己的模式中创建, 修改, 删除或查询任何表
  • 开发人员一般需要 create table, create view 和 create type 系统权限.

权限管理:

查看用户的系统权限

image-20230927205416418

修改用户的系统权限

image-20230927205412099

image-20230927205407260

查看用户的对象权限

image-20230927205403123

常用的对象权限

image-20230927205358587

修改(授予)用户的对象权限

image-20230927205354535

利用角色进行权限管理

  • 数据库应用程序所需要的系统权限和对象权限很多. 为了使 ”安全管理” 成为比较容易的工作, 可以利用角色
  • 角色(role): 系统权限和对象权限的一个集合. 可以将角色授予 用户, 被授予角色的用户会自动拥有角色所具有的权限. 如果修改了角色所拥有的权限, 则被授予角色的用户的权限也会随之自动修改.

创建角色

image-20230927205349423

image-20230927205345329

修改角色

image-20230927205340238

将角色授予用户

image-20230927205335854

删除角色

image-20230927205330881

oracle表空间不足

1
ORA-01652: unable to extend temp segment by 128 in tablespace FLK_DATA_TMP

每个表空间都有一个或多个用于存储数据的数据文件。

数据文件的最大大小取决于数据库的块大小。我相信,默认情况下,每个数据文件最多可以保留 32GB。

image-20230927213055203

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 最大数据文件大小是多少。
11g r2,在默认安装中,它的块大小为 8,192(每个数据文件 32gb)
select value from v$parameter where name = 'db_block_size';

# 查看数据文件、它们关联的表空间以及您当前设置的最大文件大小(不能超过上述 32GB)
select bytes/1024/1024 as mb_size,
maxbytes/1024/1024 as maxsize_set,
x.*
from dba_data_files x

# 如果文件最大大小较低或未启用自动扩展,可以开启
alter database datafile 'path_to_your_filethat_file.DBF' autoextend on maxsize unlimited;

# 如果接近/到了32G 需要扩展一个表空间文件
alter tablespace FLK_DATA_TMP add datafile 'path_to_your_datafiles_foldername_of_df_you_want.dbf' size 10m autoextend on maxsize unlimited;

表空间的概念理解

表空间设计原则

1
2
3
4
5
6
7
8
为了方便dba对oracle数据库的日常管理,结合日常工作和一些书上的建议,汇总以下建议。

1、系统数据与应用数据必须存储于不同的表空间。
2、按照应用划分数据,不同应用的数据应存储于不同的表空间。
3、表和索引分离,需存储在不同的表空间,以便分布到不同的数据文件和硬盘上,并分别进行不同的物理存储参数优化。
4、相对静态的表和频繁变动的表分开存放在不同的表空间,以便分别进行不同的物理参数优化。
5、为中间表单独设计表空间,可以不考虑备份。
6、采用临时表空间组技术,提高大批量数据处理效率。

表空间设计理念

1
2
SQL Server数据库与Oracle数据库之间最大的区别要属表空间设计。
Oracle数据库开创性地提出了表空间的设计理念,这为Oracle数据库的高性能做出了不可磨灭的贡献。可以这么说,Oracle中很多优化都是基于表空间的设计理念而实现的。

典型应用一:控制用户所占用的表空间配额。

1
2
    在一些大型的数据库应用中,我们需要控制某个用户或者某一组用户其所占用的磁盘空间。这就好像在文件服务器中,需要为每个用户设置磁盘配额一样,以防止硬盘空间耗竭。所以,在数据库中,我们也需要限制用户所可以使用的磁盘空间大小。为了达到这个目的,我们就可以通过表空间来实现。
  我们可以在Oracle数据库中,建立不同的表空间,为其设置最大的存储容量,然后把用户归属于这个表空间。如此的话,这个用户的存储容量,就受到这个表空间大小的限制。

典型应用二:控制数据库所占用的磁盘空间。

1
    有时候,在Oracle数据库服务器中,可能运行的不止一个服务。除了数据库服务器外,可能还有邮件服务器等应用系统服务器。为此,就需要先对Oracle数据库的磁盘空间作个规划,否则,当多个应用程序服务所占用的磁盘空间都无限增加时,最后可能导致各个服务都因为硬盘空间的耗竭而停止。所以,在同一台服务器上使用多个应用程序服务,我们往往需要先给他们进行磁盘空间的规划和分配。各个服务都不能够超过我们分配给他的最大限额,或者超过后及时的提醒我们。只有这样,才能够避免因为磁盘空间的耗竭而导致各种应用服务的崩溃。

典型应用三:灵活放置表空间,提高数据库的输入输出性能。

1
2
    数据库管理员还可以将不同类型的数据放置到不同的表空间中,这样可以明显提高数据库输入输出性能,有利于数据的备份与恢复等管理工作。因为我们数据库管理员在备份或者恢复数据的时候,可以按表空间来备份数据。如在设计一个大型的分销系统后台数据库的时候,我们可以按省份建立表空间。与浙江省相关的数据文件放置在浙江省的表空间中,北京发生业务记录,则记录在北京这个表空间中。如此,当浙江省的业务数据出现错误的时候,则直接还原浙江省的表空间即可。很明显,这样设计,当某个表空间中的数据出现错误需要恢复的时候,可以避免对其他表空间的影响。
  另外,还可以对表空间进行独立备份。当数据库容量比较大的时候,若一下子对整个数据库进行备份,显然会占用比较多的时间。虽然说Oracle数据库支持热备份,但是在备份期间,会占用比较多的系统资源,从而造成数据库性能的下降。为此,当数据库容量比较大的时候,我们就需要进行设置多个表空间,然后规划各个表空间的备份时间,从而可以提高整个数据库的备份效率,降低备份对于数据库正常运行的影响。

典型应用四:大表的排序操作。

1
    我们都知道,当表中的记录比较多的时候,对他们进行查询,速度会比较慢。第一次查询成功后,若再对其进行第二次重新排序,仍然需要这么多的时间。为此,我们在数据库设计的时候,针对这种容量比较大的表对象,往往把它放在一个独立的表空间,以提高数据库的性能。

oracle创建远程连接

在Database links创建DBlink

image-20220703024731079

image-20220703025034661

如果想从flk访问tcmb的数据表,则可以通过创建db links. 需要ip 用户名口令

oracle系统级

首先查看系统日志,/var/log/message系列日志.查看到日志文件中最接近发生错误的时间的日志.

然后查看oracle警告日志,/u01/app/oracle/diag/rdbms/r5/r5/trace/alert_r5.log

查询最近执行的SQL语句

1
select t.sql_text,t.first_load_time from v$sqlarea where first_load_time is not null order by t.first_load_time desc

语法

01_SQL初步

SQL语句分为以下三种类型:
DML: Data Manipulation Language 数据操纵语言
DDL:Data Definition Language 数据定义语言
DCL:Data Control Language 数据控制语言

  • DML用于查询与修改数据记录,包括如下SQL语句:

INSERT:添加数据到数据库中
UPDATE:修改数据库中的数据
DELETE:删除数据库中的数据
SELECT:选择(查询)数据

SELECT是SQL语言的基础,最为重要。

  • DDL用于定义数据库的结构,比如创建、修改或删除数据库对象,包括如下SQL语句:

CREATE TABLE:创建数据库表
ALTER TABLE:更改表结构、添加、删除、修改列长度
DROP TABLE:删除表
CREATE INDEX:在表上建立索引
DROP INDEX:删除索引

  • DCL用来控制数据库的访问,包括如下SQL语句:

GRANT:授予访问权限
REVOKE:撤销访问权限
COMMIT:提交事务处理
ROLLBACK:事务处理回退
SAVEPOINT:设置保存点
LOCK:对数据库的特定部分进行锁定

01_基本SQL-SELECT语句

基本 SELECT 语句

image-20230927205551055

  • SELECT   标识 选择哪些列。
  • FROM     标识从哪个表中选择。

选择全部列

image-20230927205555073

选择特定的列

image-20230927205600126

  • SQL 语言大小写不敏感。
  • SQL 可以写在一行或者多行 
  • 关键字不能被缩写也不能分行 
  • 各子句一般要分行写。 
  • 使用缩进提高语句的可读性。

算术运算符

数字和日期使用的算术运算符。

image-20230927205603671

使用数学运算符

image-20230927205608180

image-20230927212812893

操作符优先级

image-20230927205612599

  • 乘除的优先级高于加减。
  • 同一优先级运算符从左向右执行。 
  • 括号内的运算先执行。

使用括号

image-20230927205617034

定义空值

  • 空值是无效的,未指定的,未知的或不可预知的值
  • 空值不是空格或者0。

image-20230927205620555

空值在数学运算中的使用

包含空值的数学表达式的值都为空值

image-20230927205627805

image-20230927205636685

列的别名

  • 重命名一个列。 
  • 便于计算。
  • 紧跟列名,也可以在列名和别名之间加入关键字‘AS’,别名使用双引号,以便在别名中包含空格或特殊的字符并区分大小写。

使用别名

image-20230927205641645

image-20230927205646034

image-20230927205650633

image-20230927205655264

连接符

  • 把列与列,列与字符连接在一起。
  • 用 ‘||’表示。
  • 可以用来‘合成’列。
1
2
类比java中的+号字符连接
System.out.println(123 + "hello" + 123) ;//123hello123

连接符应用举例

image-20230927205700251

image-20230927205705735

字符串

  • 字符串可以是 SELECT 列表中的一个字符,数字,日期。
  • 日期和字符只能在
  • 每当返回一行时,字符串被输出一次。

image-20230927205712080

重复行

默认情况下,查询会返回全部行,包括重复行

image-20230927205716587

image-20230927205720826

删除重复行

在 SELECT 子句中使用关键字 ‘DISTINCT’ 删除重复行。

image-20230927205726061

image-20230927205730239

SQL 和 SQL*Plus

image-20230927205734799

SQL 语句与  SQL*Plus 命令

image-20230927205739296

Structural query language        

使用SQL*Plus可以:

  • 描述表结构。 
  • 编辑 SQL 语句。 
  • 执行 SQL语句。 
  • 将 SQL 保存在文件中并将SQL语句执行结果保存在文件中。 
  • 在保存的文件中执行语句。 
  • 将文本文件装入 SQL*Plus编辑窗口。

显示表结构

使用 DESCRIBE 命令,表示表结构

image-20230927205744068

image-20230927205747973

02_过滤和排序数据

在查询中过滤行

过滤

使用WHERE 子句,将不满足条件的行过滤掉

image-20230927205752293

WHERE 子句紧随 FROM 子句

image-20230927205756401

字符和日期

  • 字符和日期要包含在单引号中
  • 字符大小写敏感,日期格式敏感。
  • 默认的日期格式是 DD-MON月-RR。

image-20230927205801609

image-20230927205805897

比较运算

赋值使用 := 符号

image-20230927205811875

image-20230927205816188

其它比较运算

image-20230927205823579

BETWEEN

使用 BETWEEN 运算来显示在一个区间内的值

image-20230927205827956

IN

使用 IN运算显示列表中的值

image-20230927205831915

LIKE

  • 使用 LIKE 运算选择类似的值
  • 选择条件可以包含字符或数字:
    • % 代表零个或多个字符(任意个字符)。
    • _ 代表一个字符。

image-20230927205836262

  • ‘%’和’_‘可以同时使用

image-20230927205840424

可以使用 ESCAPE 标识符 选择‘%’和 ‘_’ 符号。

ESCAPE

回避特殊符号的:使用转义符。例如:将[%]转为[\%]、[_]转为[\_],然后再加上[ESCAPE ‘\‘] 即可

image-20230927205845066

image-20230927205849419

NULL

使用 IS (NOT) NULL 判断空值。

image-20230927205853893

逻辑运算

image-20230927205858859

AND

AND 要求并的关系为真。

image-20230927205904337

OR

OR 要求或关系为真。

image-20230927205908286

NOT

image-20230927205912372

优先级

image-20230927205916186

可以使用括号改变优先级顺序

ORDER BY子句

  • 使用 ORDER BY 子句排序
    • ASC
    • DESC
  • ORDER BY 子句

image-20230927205920293

降序排序

image-20230927205923980

按别名排序

image-20230927205929523

多个列排序

按照ORDER BY 列表的顺序排序。

image-20230927205934936

可以使用不在SELECT 列表中的列排序。

image-20230927205939054

练习

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
1. 查询工资大于12000的员工姓名和工资
select last_name,salary from employees where salary > 12000

2. 查询员工号为176的员工的姓名和部门号
select last_name,department_id from employees where employee_id  = 176

3. 选择工资不在5000到12000的员工的姓名和工资
select last_name,salary from employees where salary >=5000 AND  salary <=12000

select last_name,salary from employees where salary between 5000  AND 12000;

4. 选择雇用时间在1998-02-01到1998-05-01之间的员工姓名,job_id和雇用时间
select last_name,job_id,hire_date from employees
where hire_date between '1-2月-1998' and '1-5月-1998'

select last_name,job_id,hire_date from employees
where to_char(hire_date,'yyyy-mm-dd') between '1998-02-01' and  '1998-05-01'

5. 选择在20或50号部门工作的员工姓名和部门号
select last_name,department_id from employees
where department_id in(20,50)

select last_name,department_id from employees
where department_id = 20 or department_id = 50

6. 选择在1994年雇用的员工的姓名和雇用时间
select last_name,hire_date from employees
where hire_date like '%94'

select last_name,hire_date from employees
where to_char(hire_date,'yyyy') = '1994'

7. 选择公司中没有管理者的员工姓名及job_id
select last_name,job_id from employees
where manager_id is null

8. 选择公司中有奖金的员工姓名,工资和奖金级别
select last_name,salary,commission_pct from employees where  commission_pct is not null

9. 选择员工姓名的第三个字母是a的员工姓名
select last_name
from employees where last_name like '__a%'

10.选择姓名中有字母a和e的员工姓名
select last_name
from employees where last_name like '%a%e%' or last_name like  '%e%a%'

select last_name
from employees where last_name like '%a%' and last_name like  '%e%'

03_单行函数

image-20230927205945132

image-20230927205950001

单行函数

  • 操作数据对象
  • 接受参数返回一个结果 
  • 只对一行进行变换 
  • 每行返回一个结果 
  • 可以转换数据类型 
  • 可以嵌套 
  • 参数可以是一列或一个值

image-20230927205957808

image-20230927205954139

image-20230927210002503

大小写控制函数: 这类函数改变字符的大小写。

image-20230927210008231

显示员工 Higgins的信息:

image-20230927210012499

image-20230927210017732

字符控制函数: 这类函数控制字符

image-20230927210021578

ps: 

substr是从第1位开始,取5个,数据库索引下标从1开始的.

image-20230927210025613

instr是返回字符的索引位置,不存在返回0

image-20230927210029817

replace是替换匹配的所有字符

image-20230927210035375

image-20230927210039921

image-20230927210045451

image-20230927210051471

image-20230927210057415

image-20230927210101957

image-20230927210108289

函数SYSDATE 返回: 

  • 日期 
  • 时间

日期的数学运算

  • 在日期上加上或减去一个数字结果仍为日期
  • 两个日期相减返回日期之间相差的天数。
    • 日期不允许做加法运算,无意义
  • 可以用数字除24来向日期中加上或减去天数

image-20230927210115122

image-20230927212750763

日期函数

image-20230927210119709

image-20230927210124483

image-20230927210128579

image-20230927210133898

隐式数据类型转换

Oracle 自动完成下列转换:

image-20230927210138834

varchar2隐式转换为number运算

image-20230927210153867

显式数据类型转换

image-20230927212839926

TO_CHAR函数对日期的转换

image-20230927210200295

格式:

  • 必须包含在单引号中而且大小写敏感。 
  • 可以包含任意的有效的日期格式。
  • 日期之间用逗号隔开。

image-20230927210204015

日期格式的元素

image-20230927210208021

时间格式

image-20230927210213519

使用双引号向日期中添加字符

image-20230927210217579

image-20230927210221557

TO_DATE 函数对字符的转换

  • 使用

image-20230927210228373

  • 使用

image-20230927210232221

练习:返回hire_date 为 ****/**/**的员工信息,使用显示日期表达

image-20230927210236439

image-20230927210241406

TO_CHAR函数对数字的转换

image-20230927210307309

下面是在TO_CHAR 函数中经常使用的几种格式:

image-20230927210311098

image-20230927210315073

image-20230927210320882

TO_NUMBER 函数对字符的转换

  • 使用

image-20230927210325749

  • 使用

image-20230927210330669

通用函数

这些函数适用于任何数据类型,同时也适用于空值:

  • NVL (expr1, expr2) 
  • NVL2 (expr1, expr2, expr3) 
  • NULLIF (expr1, expr2) 
  • COALESCE (expr1, expr2, …, exprn)

NVL 函数

将空值转换成一个已知的值:

  • 可以使用的数据类型有日期、字符、数字。 
  • 函数的一般形式: 
    • NVL(commission_pct,0) 
    • NVL(hire_date,’01-JAN-97’) 
    • NVL(job_id,’No Job Yet’)
1
2
3
4
5
6
练习1:求公司员工的年薪(含commission_pct)
select employee_id,last_name,salary*12*(1+nvl(commission_pct,0))  "annual sal" from employees
练习2:输出last_name,department_id,当department_id为null时,显示‘没有部门’。
select last_name,nvl(to_char(department_id,'999999'),'没有部门') from employees 
select last_name,nvl(to_char(department_id),'没有部门') from employees  
# nvl 类型不匹配的时候,要用到转换函数,这里department_id是number类型的,和'没有部门'不是同种类型,所以to_char转换

image-20230927210335979

使用NVL函数

image-20230927210339599

使用 NVL2 函数

image-20230927210348189

1
2
练习:查询员工的奖金率,若为空,返回0.01,若不为空,返回实际奖金率+0.015
select  employee_id,last_name,commission_pct,nvl2(commission_pct,commission_pct+0.015,0.01) from employees

使用 NULLIF 函数

image-20230927210353558

使用 COALESCE 函数

  • COALESCE 与 NVL 相比的优点在于 COALESCE 可以同时处理交替的多个值。
  • 如果第一个表达式为空,则返回下一个表达式,对其他的参数进行COALESCE 。

image-20230927210358524

条件表达式

  • 在 SQL 语句中使用IF-THEN-ELSE 逻辑
  • 使用两种方法: 
    • CASE 表达式 
    • DECODE 函数

CASE 表达式

在需要使用 IF-THEN-ELSE 逻辑时:

image-20230927210402703

1
2
3
4
5
6
7
8
9
练习:查询部门号为 10, 20, 30 的员工信息, 若部门号为 10, 则打印其工资的 1.1 倍, 20 号部门, 则打印其工资的 1.2 倍, 30 号部门打印其工资的 1.3 倍数
select employee_id,last_name,department_id,
case department_id
  when 10 then salary * 1.1
  when 20 then salary * 1.2
  else salary * 1.3
end salary
from employees
where department_id in (10,20,30)

image-20230927210406933

DECODE 函数

在需要使用 IF-THEN-ELSE 逻辑时:

image-20230927210411832

image-20230927210416782

image-20230927210424395

image-20230927210428463

image-20230927210433564

练习

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
1. 显示系统时间(注:日期+时间)
    select to_char(sysdate,'yyyy-mm-dd hh:mi:ss')
    from dual
2. 查询员工号,姓名,工资,以及工资提高百分之20%后的结果(new salary)
    select employee_id,last_name,salary,salary*1.2  "new salary"
    from employees
3. 将员工的姓名按首字母排序,并写出姓名的长度(length)
    select last_name,length(last_name)
    from employees
    order by last_name asc
4. 查询各员工的姓名,并显示出各员工在公司工作的月份数(worked_month)。
    select  last_name,hire_date,round(months_between(sysdate,hire_date),1) workded_month
    from employees
5. 查询员工的姓名,以及在公司工作的月份数(worked_month),并按月份数降序排列
    Select  last_name,hire_date,round(months_between(sysdate,hire_date),1) workded_month
    from employees
    order by workded_month desc
6. 做一个查询,产生下面的结果
<last_name> earns <salary> monthly but wants <salary*3>

image-20230927210439102

1
2
3
4
5
6
7
8
9
10
11
select last_name || ' earns '||  to_char(salary,'$999999')||' monthly,but wants  '||to_char(3*salary,'$999999') "Dream Salary"
from employees
7. 使用decode函数,按照下面的条件:

job                grade
AD_PRES            A
ST_MAN             B
IT_PROG            C
SA_REP             D
ST_CLERK           E
产生下面的结果

image-20230927210443313

1
2
3
4
5
6
7
8
9
10
select last_name "Last_name",job_id  "Job_id",decode(job_id,'AD_PRES','A','ST_MAN','B',  'IT_PROG','C', 'SA_REP','D', 'ST_CLERK','E') "Grade"
from employees
8. 将第7题的查询用case函数再写一遍。
    select last_name "Last_name",job_id  "Job_id",case job_id when 'AD_PRES'then 'A'
    when 'ST_MAN' then 'B'
    when 'IT_PROG' then 'C'
    when 'SA_REP' then 'D'
    when 'ST_CLERK' then'E' end  "Grade"
    from employees

04_多表查询

从多个表中获取数

image-20230927210447804

1
select last_name, department_namefrom employees, departments

image-20230927210454496

演示笛卡尔集的错误情况:

1
2
3
4
5
select count(employee_id) from employees;
假设输出107行 
select count(department_id)from departments; 
假设输出27行 
select 107*27 from dual;

笛卡尔集

  • 笛卡尔集会在下面条件下产生:
    • 省略连接条件
    • 连接条件无效
    • 所有表中的所有行互相连接
  • 为了避免笛卡尔集, 可以在 WHERE 加入

image-20230927210458953

Oracle 连接

使用连接在多个表中查询数据

image-20230927210504745

  • 在 WHERE 子句中写入连接条件。
  • 在表中有相同列时,在列名之前加上表名前缀

等值连接

image-20230927210508557

image-20230927210513545

多个连接条件与 AND 操作符

image-20230927210517896

区分重复的列名

  • 使用表名前缀在多个表中区分相同的列。
  • 在不同表中具有相同列名的列可以用

表的别名

  • 使用别名可以简化查询。
  • 使用表名前缀可以提高执行效率。

image-20230927210522734

连接多个表

image-20230927210528554

连接 n个表,至少需要 n-1个连接条件。 例如:连接三个表,至少需要两个连接条件。

1
练习:查询出公司员工的 last_name, department_name, city

非等值连接

image-20230927210533502

image-20230927210538083

外连接

image-20230927210542913

内连接和外连接

  • 内连接: 合并具有同一列的两个以上的表的行, 结果集中不包含一个表与另一个表不匹配的行
  • 外连接: 两个表在连接过程中除了返回满足连接条件的行以外还返回左(或右)表中不满足条件的行 ,这种连接称为左(或右) 外连接。没有匹配的行时, 结果表中相应的列为空(NULL). 外连接的 WHERE 子句条件类似于内部连接, 但连接条件中没有匹配行的表的列后面要加外连接运算符, 即用圆括号括起来的加号(+).

外连接语法

  • 使用外连接可以查询不满足连接条件的数据。
  • 外连接的符号是 (+)。

image-20230927210547732

外连接

image-20230927210553009

自连接

image-20230927210558099

1
2
3
4
练习:查询出 last_name 为 ‘Chen’ 的员工的 manager 的信息
select  emp.last_name,manager.last_name,manager.salary,manager.email
from employees emp,employees manager
where emp.manager_id=manager.employee_id and  lower(emp.last_name)='chen', 

题目:查询employees表,返回“Xxx  works for Xxx”

image-20230927210602915

使用SQL: 1999 语法连接

使用连接从多个表中查询数据:

image-20230927210609616

叉  集(了解)

  • 使用
  • 叉集和笛卡尔集是相同的。

image-20230927210614036

自然连接

  • NATURAL JOIN 子句,会以两个表中具有相同名字的列为条件创建等值连接。(相当于说where a.xx = b.xx and a.yy = b.yy)
  • 在表中查询满足等值条件的数据。
  • 如果只是列名相同而数据类型不同,则会产生错误。

返回的是,两个表中具有相同名字的列的“且、交集”,而非“或,并集”。即:比如employee类和department类都有department_id和manager_id,返回二者都相同的结果。

image-20230927210618721

使用 USING 子句创建连接

  • 在NATURAL JOIN 子句创建等值连接时,可以使用 USING 子句指定等值连接中需要用到的列。(不然默认是有几个列名相等的就连接几个)
  • 使用 USING 可以在有多个列满足条件时进行选择。 
  • 不要给选中的列中加上表名前缀或别名。 
  • JOIN 和 USING 子句经常同时使用。

image-20230927210624309

image-20230927210630180

使用ON 子句创建连接(常用)

  • 自然连接中是以具有相同名字的列为连接条件的。
  • 可以使用 ON 子句指定额外的连接条件
  • 这个连接条件是与其它条件分开的。 
  • ON 子句使语句具有更高的易读性

image-20230927210635334

使用 ON 子句创建多表连接

image-20230927210639492

内连接和外连接

  • 在SQL: 1999中,内连接只返回满足连接条件的数据
  • 两个表在连接过程中除了返回满足连接条件的行以外还返回左(或右)表中不满足条件的行,这种连接称为左(或右) 外连接。 
  • 两个表在连接过程中除了返回满足连接条件的行以外还返回两个表中不满足条件的行 ,这种连接称为满 外连接。

左外连接

image-20230927210646383

右外连接

image-20230927210650970

满外连接

image-20230927210656229

练习

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
1. 显示所有员工的姓名,部门号和部门名称。
方法一:
    select last_name,e.department_id,department_name
    from employees e,departments d
    where e.department_id = d.department_id(+)
方法二:
select last_name,e.department_id,department_name
from employees e left outer join departments d
on e.department_id = d.department_id

2. 查询90号部门员工的job_id和90号部门的location_id
    select distinct job_id,location_id
    from employees e left outer join departments d
    on e.department_id = d.department_id
    where d.department_id = 90

3. 选择所有有奖金的员工的last_name , department_name , location_id , city
    select last_name,department_name,d.location_id,city
    from employees e join departments d
    on e.department_id = d.department_id
    join locations l
    on d.location_id = l.location_id
    where e.commission_pct is not null

4. 选择city在Toronto工作的员工的last_name , job_id , department_id , department_name
    select last_name , job_id , e.department_id , department_name
    from employees e ,departments d,locations l
    where e.department_id = d.department_id and l.city =  'Toronto' and d.location_id = l.location_id

5.选择指定员工的姓名,员工号,以及他的管理者的姓名和员工号,结果类似于下面的格式

image-20230927210701850

1
2
3
    select e1.last_name "employees",e1.employee_id "Emp#",e2.last_name"Manger",e2.employee_id "Mgr#"
    from employees e1,employees e2
    where e1.manager_id = e2.employee_id(+)

总结:

SQL语句的多表查询方式:

例如:按照department_id查询employees(员工表)和departments(部门表)

的信息。

方式一(通用型):SELECT … FROM … WHERE

SELECT e.last_name,e.department_id,d.department_name

FROM employees e,departments d

where e.department_id = d.department_id

方式二:SELECT … FROM … NATURAL JOIN …

有局限性:会自动连接两个表中相同的列(可能有多个:department_id和manager_id)

SELECT last_name,department_id,department_name

FROM employees

NATURAL JOIN departments

方式三:SELECT … JOIN … USING …

有局限性:好于方式二,但若多表的连接列列名不同,此法不合适

SELECT last_name,department_id,department_name

FROM employees

JOIN departments

USING(department_id)

方式四:SELECT … FROM … JOIN … ON …

常用方式,较方式一,更易实现外联接(左、右、满)

SELECT last_name,e.department_id,department_name

FROM employees e

JOIN departments d

ON e.department_id = d.department_id

--内连接

    1)

    --等值连接

    --不等值连接

    2)

    --非自连接

    --自连接

--外连接

    --左外连接、右外连接、满外连接

05_分组函数

什么是分组函数? 分组函数作用于一组数据,并对一组数据返回一个值。

image-20230927210723435

组函数类型

  • AVG
  • COUNT
  • MAX
  • MIN
  • STDDEV
  • SUM

组函数语法

image-20230927210727492

AVG(平均值)和 SUM (合计)函数

可以对数值型数据使用AVG 和 SUM 函数

image-20230927210731969

MIN(最小值)和 MAX(最大值)函数

可以对任意数据类型的数据使用 MIN 和 MAX 函数

image-20230927210736470

COUNT(计数)函数

  • COUNT(*) 返回表中记录总数,适用于任意数据类型。

image-20230927210740992

  • COUNT(expr) 返回expr不为空的记录总数。

image-20230927210745514

组函数与空值

组函数忽略空值。

image-20230927210750892

1
2
3
例如:
select avg(commission_pct),sum(commission_pct)/107, sum(commission_pct)/count(commission_pct) From employees; 
查看结果的不同

在组函数中使用NVL函数

NVL函数使分组函数无法忽略空值。

image-20230927210756393

DISTINCT 关键字

COUNT(DISTINCT expr)返回expr非空且不重复的记录总数

image-20230927210800618

分组数据

image-20230927210804281

GROUP BY 子句语法

可以使用GROUP BY子句将表中的数据分成若干组

image-20230927210808814

明确:WHERE一定放在FROM后面

在SELECT 列表中所有未包含在组函数中的列都应该包含在 GROUP BY 子句中。

image-20230927210813519

包含在 GROUP BY 子句中的列不必包含在SELECT 列表中

image-20230927210818675

使用多个列分组

image-20230927210823149

在GROUP BY子句中包含多个列

image-20230927210827588

非法使用组函数

所有包含于SELECT 列表中,而未包含于组函数中的列都必须包含于 GROUP BY 子句中。

image-20230927210832536

GROUP BY 子句中缺少列

  • 不能在 WHERE 子句中使用组函数。
  • 可以在 HAVING 子句中使用组函数。

image-20230927210836939

WHERE 子句中不能使用组函数

过滤分组

image-20230927210843695

HAVING 子句

使用 HAVING 过滤分组:

  1. 行已经被分组。
  2. 使用了组函数。 
  3. 满足HAVING 子句中条件的分组将被显示。

image-20230927210847830

image-20230927210852594

嵌套组函数

显示各部门平均工资的最大值

image-20230927210856774

总  结

通过本章学习,您已经学会: 

  • 使用组函数:avg(),sum(),max(),min(),count() 
  • 在查询中使用 GROUP BY 子句。 
  • 在查询中使用 HAVING 子句。

image-20230927210900142

练习

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
1. 组函数处理多行返回一行吗?


2. 组函数不计算空值吗?


3. where子句可否使用组函数进行过滤?

不可以,用having替代
4. 查询公司员工工资的最大值,最小值,平均值,总和
    select max(salary),min(salary),avg(salary),sum(salary)
    from employees
5. 查询各job_id的员工工资的最大值,最小值,平均值,总和
    select  job_id,max(salary),min(salary),avg(salary),sum(salary)
    from employees
    group by job_id
6. 选择具有各个job_id的员工人数
    select job_id,count(employee_id)
    from employees
    group by job_id
7. 查询员工最高工资和最低工资的差距(DIFFERENCE)
    select max(salary),min(salary),max(salary)-min(salary)  "DIFFERENCE"
    from employees
8. 查询各个管理者手下员工的最低工资,其中最低工资不能低于6000,没有管理者的员工不计算在内
    select manager_id,min(salary)
    from employees
    where manager_id is not null
    group by manager_id
    having min(salary) >= 6000
9. 查询所有部门的名字,location_id,员工数量和工资平均值
    select  department_name,location_id,count(employee_id),avg(salary)
    from employees e right outer join departments d
    on e.department_id = d.department_id
    group by department_name,location_id
10. 查询公司在1995-1998年之间,每年雇用的人数,结果类似下面的格式

image-20230927210907532

1
2
3
4
5
6
7
select count(*) "total",
       count(decode(to_char(hire_date,'yyyy'),'1995',1,null)) "1995",
       count(decode(to_char(hire_date,'yyyy'),'1996',1,null)) "1996",
       count(decode(to_char(hire_date,'yyyy'),'1997',1,null)) "1997",
       count(decode(to_char(hire_date,'yyyy'),'1998',1,null)) "1998"
from employees
where to_char(hire_date,'yyyy') in ('1995','1996','1997','1998')

06_子查询

使用子查询解决问题

image-20230927210911435

子查询语法

image-20230927210915218

  • 子查询 (内查询) 在主查询之前一次执行完成。
  • 子查询的结果被主查询(外查询)使用 。

image-20230927210919467

image-20230927210924043

  • 子查询要包含在括号内。 
  • 将子查询放在比较条件的右侧
  • 单行操作符对应单行子查询,多行操作符对应多行子查询。

子查询类型

image-20230927210932007

单行子查询

  • 只返回一行。
  • 使用单行比较操作符。

image-20230927210937794

1
题目:返回job_id与141号员工相同,salary比143号员工多的员工姓名,job_id 和工资

image-20230927210941913

1
题目:返回公司工资最少的员工的last_name,job_id和salary

image-20230927210947122

子查询中的 HAVING 子句

  • 首先执行子查询。
  • 向主查询中的HAVING 子句返回结果
1
题目:查询最低工资大于50号部门最低工资的部门id和其最低工资

image-20230927210952180

非法使用子查询

image-20230927210956819

子查询中的空值问题

image-20230927211001736

多行子查询

  • 返回多行。
  • 使用多行比较操作符。

image-20230927211006680

在多行子查询中使用 ANY 操作符

1
题目:返回其它部门中比job_id为‘IT_PROG’部门任一工资低的员工的员工号、姓名、job_id 以及salary

image-20230927211011761

在多行子查询中使用 ALL 操作符

1
题目:返回其它部门中比job_id为‘IT_PROG’部门所有工资都低的员工的员工号、姓名、job_id 以及salary

image-20230927211017500

多行子查询中的空值问题

image-20230927211024224

总结

在查询时基于未知的值时,应使用子查询。

image-20230927211027696

练习

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
1. 查询和Zlotkey相同部门的员工姓名和雇用日期
    select last_name,hire_date
    from employees
    where department_id = (
                          select department_id
                          from employees
                          where last_name = 'Zlotkey'
                          )
    and last_name <> 'Zlotkey'
2. 查询工资比公司平均工资高的员工的员工号,姓名和工资。
    select last_name,employee_id,salary
    from employees
    where salary > (select avg(salary)
                   from employees)
3. 查询各部门中工资比本部门平均工资高的员工的员工号, 姓名和工资
    select employee_id,last_name,salary
    from employees e1
    where salary > (
                   select avg(salary)
                   from employees e2
                   where e1.department_id = e2.department_id
                   group by department_id
                   )
4. 查询和姓名中包含字母u的员工在相同部门的员工的员工号和姓名
    select employee_id,last_name
    from employees
    where department_id in (
                           select department_id
                           from employees
                           where last_name like '%u%'
                            )
    and last_name not like '%u%'

5. 查询在部门的location_id为1700的部门工作的员工的员工号
select employee_id
from employees
where department_id in (
                       select department_id
                       from departments
                       where location_id = 1700
                       )
6.查询管理者是King的员工姓名和工资
select last_name,salary
from employees
where manager_id in (
                   select employee_id
                   from employees
                   where last_name = 'King'
                   )

07_创建和管理表

常见的数据库对象

image-20230927211032452

Oracle 数据库中的表

  • 用户定义的表:
    • 用户自己创建并维护的一组表 
    • 包含了用户所需的信息 
      • 如:SELECT * FROM user_tables;查看用户创建的表

     

  • 数据字典: 
    • 由 Oracle Server 自动创建的一组表 
    • 包含数据库信息

查询数据字典

image-20230927211037279

命名规则

表名和列名:

  • 必须以字母开头
  • 必须在 1–30 个字符之间 
  • 必须只能包含 A–Z, a–z, 0–9, _, $, 和 #
  • 必须不能和用户定义的其他对象重名 
  • 必须不能是Oracle 的保留字

CREATE TABLE 语句

  • 必须具备:
    • CREATE TABLE权限 
    • 存储空间

image-20230927211042033

  • 必须指定:
    • 表名 
    • 列名, 数据类型, 尺寸

创建表

语法

image-20230927211046715

确认

image-20230927211050597

数据类型

image-20230927211054588

使用子查询创建表

  • 使用 AS subquery 选项,将创建表和插入数据结合起来

image-20230927211058728

  • 指定的列和子查询中的列要一一对应
  • 通过列名和默认值定义列

使用子查询创建表

  • 使用 AS subquery 选项,将创建表和插入数据结合起来

image-20230927211103973

  • 指定的列和子查询中的列要一一对应
  • 通过列名和默认值定义列

复制现有的表:

1
2
create table emp1 as select * from employees;
create table emp2 as select * from employees where 1=2; --创建的emp2是空表。

image-20230927211109880

ALTER TABLE 语句

使用 ALTER TABLE 语句可以:

  • 追加新的列 
  • 修改现有的列 
    • 为新追加的列定义默认值

     

  • 删除一个列 
  • 重命名表的一个列名

使用 ALTER TABLE 语句追加, 修改, 或删除列的语法

image-20230927211115170

ps: 如果有数据的时候,modify修改数据类型不能成功,修改默认值对后续数据生效。

追加一个新列

image-20230927211120490

  • 使用 ADD 子句追加一个新列

image-20230927211125224

  • 新列是表中的最后一列

image-20230927211129079

修改一个列

  • 可以修改列的数据类型, 尺寸和默认值

image-20230927211133588

  • 对默认值的修改只影响今后对表的修改

删除一个列

  • 使用 DROP COLUMN 子句删除不再需要的列.

image-20230927211137066

重命名一个列

使用 RENAME COLUMN [table_name] TO子句重命名列

image-20230927211141374

删除表

  • 数据和结构都被删除
  • 所有正在运行的相关事务被提交 
  • 所有相关索引被删除 
  • DROP TABLE 语句不能回滚

image-20230927211144986

清空表

TRUNCATE TABLE 语句:

  • 删除表中所有的数据 
  • 释放表的存储空间

image-20230927211149390

  • TRUNCATE语句
  • 可以使用 DELETE 语句删除数据,可以回滚 对比:

image-20230927211153927

改变对象的名称

  • 执行RENAME语句改变表, 视图, 序列, 或同义词的名称

 

image-20230927211158216

  • 必须是对象的拥有者

总   结

image-20230927211202605

练习

1
1. 创建表dept1

image-20230927211207640

1
2
3
4
5
6
7
8
9
    create table dept1(
    id number(7),
    name varchar2(25)
    )
2. 将表departments中的数据插入新表dept2中
    create table dept2
    as
    select * from departments
3. 创建表emp5

image-20230927211211689

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
    create table emp5(
    id number(7),
    first_name varchar2(25),
    last_name varchar2(25),
    dept_id number(7)
    )
4. 将列Last_name的长度增加到50
    alter table emp5
    modify (last_name varchar2(50))
5. 根据表employees创建employees2
    create table employees2
    as
    select * from employees
6. 删除表emp5

    drop table emp5;
7. 将表employees2重命名为emp5

    rename employees2 to emp5
8. 在表dept和emp5中添加新列test_column,并检查所作的操作

    alter table dept
    add(test_column number(10));
    desc dept;
9. 在表dept和emp5中将列test_column设置成不可用,之后删除
    alter table emp5
    set unused column test_column

    alter table emp5
    drop unused columns
10. 直接删除表emp5中的列 dept_id

    Alter table emp5
    drop column dept_id

08_数据处理

  • 使用 DML 语句 
  • 向表中插入数据 
  • 更新表中数据 
  • 从表中删除数据 
  • 控制事务

数据操纵语言

  • DML(Data Manipulation Language – 数据操纵语言) 可以在下列条件下执行:
    • 向表中插入数据
    • 修改现存数据
    • 删除现存数据
  • 事务是由完成若干项工作的DML语句组成的

插入数据

image-20230927211217094

INSERT 语句语法

  • 使用 INSERT 语句向表中插入数据。

 

image-20230927211221510

  • 使用这种语法一次只能向表中插入

插入数据

  • 为每一列添加一个新值。
  • 按列的默认顺序列出各个列的值。 
  • 在 INSERT 子句中随意列出列名和他们的值。 
  • 字符和日期型数据应包含在单引号中。

image-20230927211225405

向表中插入空值

  • 隐式方式: 在列名表中省略该列的值

image-20230927211229752

  • 显示方式: 在VALUES 子句中指定空值

image-20230927211234326

插入指定的值

SYSDATE 记录当前系统的日期和时间

image-20230927211238070

  • 加入新员工

image-20230927211241972

  • 检查插入的数据

image-20230927211250989

创建脚本

  • 在SQL 语句中使用 & 变量指定列值。
  • & 变量放在VALUES子句中。

image-20230927211255151

从其它表中拷贝数据

  • 在 INSERT 语句中加入子查询。

 

image-20230927211259797

  • 不必书写 VALUES 子句。
  • 子查询中的值列表应与 INSERT 子句中的列名对应

更新数据

image-20230927211303717

UPDATE 语句语法

  • 使用 UPDATE 语句更新数据。

image-20230927211309899

 

  • 可以一次更新多条数据。

更新数据

  • 使用 WHERE 子句指定需要更新的数据。

 

image-20230927211315376

  • 如果省略 WHERE 子句,则表中的所有数据都将被更新

image-20230927211319326

在 UPDATE 语句中使用子查询

1
题目:更新 114号员工的工作和工资使其与205号员工相同。

image-20230927211324921

在 UPDATE 中使用子查询,使更新基于另一个表中的数据。

1
题目:调整与employee_id 为200的员工job_id相同的员工的department_id为employee_id为100的员工的department_id。

image-20230927211345802

更新中的数据完整性错误

image-20230927211349123

不存在 55 号部门

1
另例:update employees set manager_id = 299 where employee_id = 203;

删除数据

image-20230927211352856

DELETE 语句

使用 DELETE 语句从表中删除数据。

image-20230927211357344

删除数据

  • 使用 WHERE 子句删除指定的记录。

 

image-20230927211401499

  • 如果省略 WHERE 子句,则表中的全部数据将被删除

image-20230927211404839

在 DELETE 中使用子查询

在 DELETE 中使用子查询,使删除基于另一个表中的数据。

1
题目:从emp1表中删除dept1部门名称中含Public字符的部门id

image-20230927211408991

删除中的数据完整性错误

image-20230927211413867

数据库事务

  • 事务:一组逻辑操作单元,使数据从一种状态变换到另一种状态。
  • 数据库事务由以下的部分组成: 
    • 一个或多个DML 语句 
    • 一个 DDL(Data Definition Language – 数据定义语言) 语句 
    • 一个 DCL(Data Control Language – 数据控制语言) 语句
  • 以第一个 DML 语句的执行作为开始 
  • 以下面的其中之一作为结束:
    • COMMIT 或 ROLLBACK 语句 
    • DDL 语句(自动提交)
    • 用户会话正常结束 
    • 系统异常终止

COMMIT和ROLLBACK语句的优点

使用COMMIT 和 ROLLBACK语句,我们可以: 

  • 确保数据完整性。 
  • 数据改变被提交之前预览。 
  • 将逻辑上相关的操作分组。

控制事务

image-20230927211419522

回滚到保留点

  • 使用 SAVEPOINT 语句在当前事务中创建保存点。
  • 使用 ROLLBACK TO SAVEPOINT 语句回滚到创建的保存点。

image-20230927211423735

事务进程

  • 自动提交在以下情况中执行:
    • DDL 语句。 
    • DCL 语句。 
    • 不使用 COMMIT 或 ROLLBACK 语句提交或回滚,正常结束会话。

     

  • 会话异常结束或系统异常会导致自动回滚。

提交或回滚前的数据状态

  • 改变前的数据状态是可以恢复的
  • 执行 DML 操作的用户可以通过 SELECT 语句查询之前的修正 
  • 其他用户不能看到当前用户所做的改变,直到当前用户结束事务。 
  • DML语句所涉及到的行被锁定, 其他用户不能操作

提交后的数据状态

  • 数据的改变已经被保存到数据库中。
  • 改变前的数据已经丢失。 
  • 所有用户可以看到结果。 
  • 锁被释放,其他用户可以操作涉及到的数据
  • 所有保存点被释放。

提交数据

  • 改变数据

 

image-20230927211429037

  • 提交改变

image-20230927211433367

数据回滚后的状态

使用 ROLLBACK 语句可使数据变化失效:

  • 数据改变被取消。 
  • 修改前的数据状态被恢复。 
  • 锁被释放。

image-20230927211441244

总  结

通过本章学习, 您应学会如何使用DML语句改变数据和事务控制

image-20230927211444974

练习

1
2
3
4
5
6
7
8
9
10
11
1. 运行以下脚本创建表my_employees

Create table my_employee (  id         number(3),
                            first_name varchar2(10),
                            Last_name  varchar2(10),
                            User_id    varchar2(10),
                            Salary     number(5));
2. 显示表my_employees的结构

DESC my_employees;
3. 向表中插入下列数据

image-20230927211450043

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
INSERT INTO my_employee
VALUES(1,’patel’,’Palph’,’Rpatel’895);
4. 提交

COMMIT;
5. 将3号员工的last_name修改为“drelxer”

UPDATE my_employees
SET last_name = ‘drelxer’
WHERE id = 3;
6. 将所有工资少于900的员工的工资修改为1000

UPDATE my_employees
SET salary = 1000
WHERE salary< 900
7. 检查所作的修正

SELECT * FROM my_employees
WHERE salary < 900
8. 提交

COMMIT;
9. 删除所有数据

DELETE FROM my_employees;
10. 检查所作的修正

SELECT * FROM my_employees;
11. 回滚

ROLLBACK;
12. 清空表my_employees

TRUNCATE TABLE my_employees

09_约束

什么是约束

约束是表级的强制规定

  • 有以下五种约束: 
    • NOT NULL 
    • UNIQUE 
    • PRIMARY KEY 
    • FOREIGN KEY 
    • CHECK
  • 如果不指定约束名 ,Oracle server 自动按照 SYS_Cn 的格式指定约束名
  • 创建和修改约束:
    • 建表的同时 
    • 建表之后
  • 可以在表级或列级定义约束
  • 可以通过数据字典视图查看约束

表级约束和列级约束

  • 作用范围:    
    1. 列级约束只能作用在一个列上
    2. 表级约束可以作用在多个列上(当然表级约束也可以作用在一个列上)
  • 定义方式:列约束必须跟在列的定义后面,表约束不与列一起,而是单独定义。 
  • 非空(not null) 约束只能定义在列上

定义约束

image-20230927211455239

image-20230927211459154

  • 列级

image-20230927211503323

  • 表级

image-20230927211507073

NOT NULL 约束

保证列值不能为空:

image-20230927211511587

只能定义在列级:

image-20230927211515630

可以在PL/SQLDEV的My objects-Tables-employees-Check constraints中查看

UNIQUE 约束

image-20230927211520587

可以定义在表级或列级:

image-20230927211524699

PRIMARY KEY 约束

image-20230927211529368

可以定义在表级或列级:

image-20230927211533578

FOREIGN KEY 约束

image-20230927211537685

可以定义在表级或列级:

image-20230927211541414

FOREIGN KEY 约束的关键字

  • FOREIGN KEY: 在表级指定子表中的列
  • REFERENCES: 标示在父表中的列 
  • ON DELETE CASCADE(级联删除): 当父表中的列被删除时,子表中相对应的列也被删除
  • ON DELETE SET NULL(级联置空): 子表中相应的列置空

image-20230927211546299

CHECK 约束

定义每一行必须满足的条件

image-20230927211552038

image-20230927211555488

添加约束的语法

使用 ALTER TABLE 语句:

  • 添加或删除约束,但是不能修改约束
  • 有效化或无效化约束 
  • 添加 NOT NULL 约束要使用 MODIFY 语句

image-20230927211600136

以create table emp as select * from employees;为例,添加和删除约束

Alter table emp modify(empname varchar2(50) not null);

添加约束

添加约束举例

image-20230927211605622

删除约束

从表 EMPLOYEES 中删除约束

image-20230927211610295

无效化约束

  • 在ALTER TABLE 语句中使用 DISABLE 子句将约束无效化。

image-20230927211614015

激活约束

  • ENABLE 子句可将当前无效的约束激活

image-20230927211619114

  • 当定义或激活UNIQUE 或 PRIMARY KEY 约束时系统会自动创建UNIQUE 或 PRIMARY KEY索引

查询约束

  • 查询数据字典视图 USER_CONSTRAINTS

image-20230927211622821

查询定义约束的列

查询数据字典视图 USER_CONS_COLUMNS

image-20230927211627312

练习

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
1. 向表emp2的id列中添加PRIMARY KEY约束(my_emp_id_pk)

ALTER table emp2
ADD constraint my_emp_id_pk primary key(id);
2. 向表dept2的id列中添加PRIMARY KEY约束(my_dept_id_pk)

ALTER table dept2
ADD constraint my_dept_id_pk primary key(id)
3. 向表emp2中添加列dept_id,并在其中定义FOREIGN KEY约束,与之相关联的列是dept2表中的id列。

ALTER table emp2
ADD (dept_id number(10) constraint emp2_dept_id_fk references  dept2(id));
准备工作:
      create table emp2 as select employee_id id, last_name name, salary  from employees
      create table dept2 as select department_id id, department_name  dept_name from departments

10_视图

image-20230927211632766

视  图

表EMPLOYEES

image-20230927211636702

  • 视图是一种虚表。 
  • 视图建立在已有表的基础上, 视图赖以建立的这些表称为基表。
  • 向视图提供数据内容的语句为 SELECT 语句, 可以将视图理解为存储起来的 SELECT 语句.
  • 视图向用户提供基表数据的另一种表现形式

为什么使用视图

  • 控制数据访问
  • 简化查询 
  • 避免重复访问相同的数据

简单视图和复杂视图

image-20230927211640690

创建视图

  • 在 CREATE VIEW 语句中嵌入子查询

image-20230927211644953

  • 子查询可以是复杂的 SELECT 语句

image-20230927211650536

  • 创建视图举例

image-20230927211655216

  • 描述视图结构

image-20230927211659349

  • 创建视图时在子查询中给列定义

image-20230927211703207

  • 在选择视图中的列时应使用别名

查询视图

image-20230927211708817

image-20230927211712268

修改视图

  • 使用CREATE OR REPLACE VIEW 子句修改视图

image-20230927211717449

  • CREATE VIEW 子句中各列的别名应和子查询中各列相对应

创建复杂视图

复杂视图举例

image-20230927211722002

视图中使用DML的规定

可以在简单视图中执行 DML 操作

当视图定义中包含以下元素之一时不能使用delete: 

  • 组函数 
  • GROUP BY 子句 
  • DISTINCT 关键字 
  • ROWNUM 伪列

image-20230927211726024

当视图定义中包含以下元素之一时不能使用update:

  • 组函数 
  • GROUP BY子句 
  • DISTINCT 关键字 
  • ROWNUM 伪列 
  • 列的定义为表达式

当视图定义中包含以下元素之一时不能使insert:

  • 组函数 
  • GROUP BY 子句 
  • DISTINCT 关键字 
  • ROWNUM 伪列 
  • 列的定义为表达式 
  • 表中非空的列在视图定义中未包括

屏蔽 DML 操作

  • 可以使用 WITH READ ONLY 选项屏蔽对视图的DML 操作
  • 任何 DML 操作都会返回一个Oracle server 错误

image-20230927211729957

删除视图

删除视图只是删除视图的定义,并不会删除基表的数据

image-20230927211733923

Top-N 分析

  • Top-N 分析查询一个列中最大或最小的 n 个值:
    • 销售量最高的十种产品是什么?
    • 销售量最差的十种产品是什么?

     

  • 最大和最小的值的集合是 Top-N 分析所关心的

查询最大的几个值的 Top-N 分析:

image-20230927211737615

注意: 对 ROWNUM 只能使用 < 或 <=, 而用 =, >, >= 都将不能返回任何数据。

1
2
练习1:查询员工表中,工资前10名的员工信息。
练习2:查询员工表中,工资排名在10-20之间的员工信息。

查询工资最高的三名员工:

image-20230927211741315

1
2
3
4
5
6
select *from( 
    select rownum rn,employee_id,salary from( 
        select employee_id,salary,last_name from employees order by salary desc 
    ) 
)
where rn <=50 and rn >40

练习

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
1. 向表emp2的id列中添加PRIMARY KEY约束(my_emp_id_pk)

ALTER table emp2
ADD constraint my_emp_id_pk primary key(id);
2. 向表dept2的id列中添加PRIMARY KEY约束(my_dept_id_pk)

ALTER table dept2
ADD constraint my_dept_id_pk primary key(id)
3. 向表emp2中添加列dept_id,并在其中定义FOREIGN KEY约束,与之相关联的列是dept2表中的id列。

ALTER table emp2
ADD (dept_id number(10) constraint emp2_dept_id_fk references  dept2(id));
准备工作:
      create table emp2 as select employee_id id, last_name name, salary  from employees
      create table dept2 as select department_id id, department_name  dept_name from departments

11_其它数据库对象

image-20230927213107578

什么是序列?

序列: 可供多个用户用来产生唯一数值的数据库对象

  • 自动提供唯一的数值 
  • 共享对象 
  • 主要用于提供主键值
  • 将序列值装入内存可以提高访问效率

CREATE SEQUENCE 语句

定义序列

image-20230927211748772

创建序列

  • 创建序列 DEPT_DEPTID_SEQ为表 DEPARTMENTS 提供主键
  • 不使用 CYCLE 选项

image-20230927211752495

1
2
3
4
5
Create sequence seq;
Select seq.nextval from dual; 
Insert into emp values(seq.nextval,’c’); 

其中create table emp as select employee_id,last_name name from employees where 1=2;

查询序列

  • 查询数据字典视图 USER_SEQUENCES 获取序列定义信息

image-20230927211757265

  • 如果指定NOCACHE 选项,则列LAST_NUMBER 显示序列中下一个有效的值

NEXTVAL 和 CURRVAL 伪列

  • NEXTVAL 返回序列中下一个有效的值,任何用户都可以引用
  • CURRVAL 中存放序列的当前值 
  • NEXTVAL 应在 CURRVAL 之前指定,否则会报CURRVAL 尚未在此会话中定义的错误。

序列应用举例

image-20230927211801061

  • 序列 DEPT_DEPTID_SEQ 的当前值

image-20230927211805258

使用序列

  • 将序列值装入内存可提高访问效率
  • 序列在下列情况下出现裂缝:
    • 回滚
    • 系统异常
    • 多个表同时使用同一序列
  • 如果不将序列的值装入内存(NOCACHE), 可使用表 USER_SEQUENCES 查看序列当前的有效值

修改序列

修改序列的增量, 最大值, 最小值, 循环选项, 或是否装入内存

image-20230927211809023

修改序列的注意事项

  • 必须是序列的拥有者或对序列有 ALTER 权限
  • 只有将来的序列值会被改变 
  • 改变序列的初始值只能通过删除序列之后重建序列的方法实现

删除序列

  • 使用 DROP SEQUENCE 语句删除序列
  • 删除之后,序列不能再次被引用

image-20230927211813079

索  引

索引:

  • 一种独立于表的模式对象, 可以存储在与表不同的磁盘或表空间中 
  • 索引被删除或损坏, 不会对表产生影响, 其影响的只是查询的速度
  • 索引一旦建立, Oracle 管理系统会对其进行自动维护, 而且由 Oracle 管理系统决定何时使用索引。用户不用在查询语句中指定使用哪个索引 
  • 在删除一个表时,所有基于该表的索引会自动被删除 
  • 通过指针加速 Oracle 服务器的查询速度
  • 通过快速定位数据的方法,减少磁盘 I/O

创建索引

  • 自动创建:
  • 手动创建:
  • 在一个或多个列上创建索引

 

image-20230927211818089

  •  在表 EMPLOYEES的列 LAST_NAME 上创建索引

image-20230927211823250

什么时候创建索引

以下情况可以创建索引:

  • 列中数据值分布范围很广 
  • 列经常在 WHERE 子句或连接条件中出现 
  • 表经常被访问而且数据量很大 ,访问的数据大概占数据总量的2%到4%

什么时候不要创建索引

下列情况不要创建索引:

  • 表很小 
  • 列不经常作为连接条件或出现在WHERE子句中 
  • 查询的数据大于2%到4% 
  • 表经常更新
1
2
Desc emp;
Create index name_index on emp(name);
  • 索引不需要用,只是说我们在用name进行查询的时候,速度会更快。当然查的速度快了,插入的速度就会慢。因为插入数据的同时,还需要维护一个索引。

查询索引

  • 可以使用数据字典视图 USER_INDEXES 和 USER_IND_COLUMNS 查看索引的信息

image-20230927211828121

删除索引

  • 使用DROP INDEX 命令删除索引

image-20230927211831713

  • 删除索引UPPER_LAST_NAME_IDX 
  • 只有索引的拥有者或拥有DROP ANY INDEX 权限的用户才可以删除索引 删除操作是不可回滚的

image-20230927211836789

同义词-synonym

使用同义词访问相同的对象:

  • 方便访问其它用户的对象 
  • 缩短对象名字的长度

image-20230927211841054

1
CREATE SYNONYM e FOR employees;select * from e;

创建和删除同义词

  • 为视图DEPT_SUM_VU 创建同义词

 

image-20230927211844501

  • 删除同义词

image-20230927211848620

练习

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
1. 向表emp2的id列中添加PRIMARY KEY约束(my_emp_id_pk)

ALTER table emp2
ADD constraint my_emp_id_pk primary key(id);
2. 向表dept2的id列中添加PRIMARY KEY约束(my_dept_id_pk)

ALTER table dept2
ADD constraint my_dept_id_pk primary key(id)
3. 向表emp2中添加列dept_id,并在其中定义FOREIGN KEY约束,与之相关联的列是dept2表中的id列。

ALTER table emp2
ADD (dept_id number(10) constraint emp2_dept_id_fk references  dept2(id));
准备工作:
      create table emp2 as select employee_id id, last_name name, salary  from employees
      create table dept2 as select department_id id, department_name  dept_name from departments

12_控制用户权限

通过本章学习,您将可以:

  • 创建用户 
  • 创建角色 
  • 使用GRANT 和 REVOKE 语句赋予和回收权限 
  • 创建数据库联接

权 限

  • 数据库安全性:
    • 系统安全性 
    • 数据安全性

     

  • 系统权限: 对于数据库的权限 
  • 对象权限: 操作数据库对象的权限

系统权限

  • 超过一百多种有效的权限
  • 数据库管理员具有高级权限以完成管理任务,例如: 
    • 创建新用户 
    • 删除用户 
    • 删除表 
    • 备份表

创建用户

DBA 使用 CREATE USER 语句创建用户

image-20230927211854226

用户的系统权限

用户创建之后, DBA 会赋予用户一些系统权限

image-20230927211857774

以应用程序开发者为例, 一般具有下列系统权限:

  • CREATE SESSION(创建会话)
  • CREATE TABLE(创建表) 
  • CREATE SEQUENCE(创建序列) 
  • CREATE VIEW(创建视图)
  • CREATE PROCEDURE(创建过程)

赋予系统权限

DBA 可以赋予用户特定的权限

image-20230927211901338

创建用户表空间

用户拥有create table权限之外,还需要分配相应的表空间才可开辟存储空间用于创建的表

image-20230927211905651

角  色

image-20230927211910223

创建角色并赋予权限

  • 创建角色

 

image-20230927211913924

  • 为角色赋予权限

 

image-20230927211917848

  • 将角色赋予用户

image-20230927211922321

修改密码

  • DBA 可以创建用户和修改密码
  • 用户本人可以使用 ALTER USER 语句修改密码

image-20230927211926502

对象权限

image-20230927211930380

对象权限

  • 不同的对象具有不同的对象权限
  • 对象的拥有者拥有所有权限 
  • 对象的拥有者可以向外分配权限

image-20230927211934743

分配对象权限

  • 分配表 EMPLOYEES 的查询权限

 

image-20230927211938741

  •  分配表中各个列的更新权限

image-20230927211942488

WITH GRANT OPTION和PUBLIC关键字

  • WITH GRANT OPTION 使用户同样具有分配权限的权利

image-20230927211946114

  • 向数据库中所有用户分配权限

image-20230927211949816

查询权限分配情况

image-20230927211953349

收回对象权限

  • 使用 REVOKE 语句收回权限
  • 使用 WITH GRANT OPTION 子句所分配的权限同样被收回

image-20230927211956906

收回对象权限举例

image-20230927212000536

总  结

通过本章学习,您已经可以使用 DCL 控制数据库权限,创建数据库联接:

image-20230927212004095

练习

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
1. 如果用户能够登陆到数据库,至少需要哪种权限?是系统权限还是对象权限

     CREATE SESSION 系统权限
2. 创建表需要哪种权限?

CREATE TABLE
3. 将表departments的查询权限分配给用户system

GRANT select
ON departments
TO system
4. 从system处收回刚才赋予的权限。

REVOKE select
ON departments
FROM system
5. 创建角色dvp,并将如下权限赋予该角色

    * CREATE PROCEDURE
    * CREATE SESSION
    *
CREATE TABLE
    * CREATE SEQUENCE
    * CREATE VIEW

1)CREATE ROLE dvp;
2)GRANT CREATE PROCEDURE,CREATE SESSION,CREATE TABLE,CREATE SEQUENCE,CREATE  VIEW
TO dvp;

13_SET 运算符

13_SET 运算符

目  标

通过本章学习,您将可以:

  • 描述 SET 操作符 
  • 将多个查询用 SET 操作符连接组成一个新的查询 
    • UNION/UNION ALL 
    • INTERSECT 
    • MINUS

     

  • 排序:ORDER BY

SET 操作符

image-20230927212010149

UNION 操作符

UNION 操作符返回两个查询的结果集的并集

image-20230927212014479

UNION 操作符举例

image-20230927212018895

UNION ALL 操作符

UNION ALL 操作符返回两个查询的结果集的并集。对于两个结果集的重复部分,不去重。

image-20230927212023494

UNION ALL 操作符举例

image-20230927212028258

INTERSECT 操作符

INTERSECT 操作符返回两个结果集的交集

image-20230927212032938

INTERSECT 操作符举例

image-20230927212037225

MINUS 操作符

image-20230927212040970

MINUS操作符:返回两个结果集的差集

MINUS 操作符举例

image-20230927212045490

使用 SET 操作符注意事项

  • 在SELECT 列表中的列名和表达式在数量和数据类型上要相对应
  • 括号可以改变执行的顺序
  • ORDER BY 子句: 
    • 只能在语句的最后出现 
    • 可以使用第一个查询中的列名, 别名或相对位置

SET 操作符

  • 除 UNION ALL之外,系统会自动将重复的记录删除
  • 系统将第一个查询的列名显示在输出中 
  • 除 UNION ALL之外,系统自动按照第一个查询中的第一个列的升序排列

匹配各SELECT 语句举例

image-20230927212050183

image-20230927212056201

使用相对位置排序举例

image-20230927212101092

连接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
1. 查询部门的部门号,其中不包括job_id是”ST_CLERK”的部门号

/*
select department_id
from departments
where department_id not in (
                           select distinct department_id
                           from employees
                           where job_id = 'ST_CLERK'
                           )
*/
select department_id
from departments
minus
select department_id
from employees
where job_id = 'ST_CLERK'
2. 查询10,50,20号部门的job_id,department_id并且department_id按10,50,20的顺序排列

1)column a_dummy noprint;
2)
SELECT job_id,department_id,1 a_dummy
from employees
where department_id = 10
union
SELECT job_id,department_id,2
from employees
where department_id = 50
union
SELECT job_id,department_id,3
from employees
where department_id = 20
order by 3 asc
> 后面用decode就行了

3. 查询所有员工的last_name ,department_id 和department_name

select last_name,department_id,to_char(null)
from employees
union
select to_char(null),department_id,department_name
from departments

14_高级子查询

通过本章学习,您将可以:

  • 书写多列子查询 
  • 在 FROM 子句中使用子查询 
  • 在SQL中使用单列子查询 
  • 书写相关子查询 
  • 使用 EXISTS 和 NOT EXISTS 操作符 
  • 使用子查询更新和删除数据 
  • 使用 WITH 子句

子查询:子查询是嵌套在 SQL 语句中的另一个SELECT 语句

image-20230927212108589

image-20230927212921551

  • 子查询 (内查询) 在主查询执行之前执行
  • 主查询(外查询)使用子查询的结果
1
问题:查询工资大于149号员工工资的员工的信息

image-20230927212112638

image-20230927212803576

一、多列子查询

主查询与子查询返回的多个列进行比较

image-20230927212116703

列比较

多列子查询中的比较分为两种: 

  • 成对比较

 

1
问题:查询与141号或174号员工的manager_id和department_id相同的其他员工的employee_id, manager_id, department_id  

image-20230927212127452

  • 不成对比较

image-20230927212130864

二、在 FROM 子句中使用子查询

1
问题:返回比本部门平均工资高的员工的last_name, department_id, salary及平均工资

方法一

image-20230927212134255

方法二

image-20230927212137686

三、单列子查询表达式

  • 单列子查询表达式是在一行中只返回一列的子查询
  • Oracle8i 只在下列情况下可以使用, 例如: 
    • SELECT 语句 (FROM 和 WHERE 子句) 
    • INSERT 语句中的VALUES列表中

     

  • Oracle9i中单列子查询表达式可在下列情况下使用: 
    • DECODE  和 CASE 
    • SELECT 中除 GROUP BY 子句以外的所有子句中

在 CASE 表达式中使用单列子查询

1
问题:显式员工的employee_id,last_name和location。其中,若员工department_id与location_id为1800的department_id相同,则location为’Canada’,其余则为’USA’。

image-20230927212142288

在 ORDER BY 子句中使用单列子查询

1
问题:查询员工的employee_id,last_name,要求按照员工的department_name排序

image-20230927212148174

四、相关子查询

相关子查询按照一行接一行的顺序执行,主查询的每一行都执行一次子查询

image-20230927212151951

相关子查询

image-20230927212156732

子查询中使用主查询中的列

1
问题:查询员工中工资大于本部门平均工资的员工的last_name,salary和其department_id

image-20230927212200238

1
问题:若employees表中employee_id与job_history表中employee_id相同的数目不小于2,输出这些相同id的员工的employee_id,last_name和其job_id

image-20230927212204191

五、EXISTS 操作符

  • EXISTS 操作符检查在子查询中是否存在满足条件的行
  • 如果在子查询中存在满足条件的行: 
    • 不在子查询中继续查找 
    • 条件返回 TRUE

     

  • 如果在子查询中不存在满足条件的行: 
    • 条件返回 FALSE 
    • 继续在子查询中查找

EXISTS 操作符应用举例

1
问题:查询公司管理者的employee_id,last_name,job_id,department_id信息

image-20230927212207910

NOT EXISTS 操作符应用举例

1
问题:查询departments表中,不存在于employees表中的部门的department_id和department_name

image-20230927212212493

六、相关更新

使用相关子查询依据一个表中的数据更新另一个表的数据

image-20230927212216073

相关更新应用举例

image-20230927212220220

相关删除

image-20230927212223718

使用相关子查询依据一个表中的数据删除另一个表的数据

1
问题:删除表employees中,其与emp_history表皆有的数据

image-20230927212227187

七、WITH 子句

  • 使用 WITH 子句, 可以避免在 SELECT 语句中重复书写相同的语句块
  • WITH 子句将该子句中的语句块执行一次并存储到用户的临时表空间中 
  • 使用 WITH 子句可以提高查询效率

WITH 子句应用举例

1
问题:查询公司中各部门的总工资大于公司中各部门的平均总工资的部门信息

image-20230927212230606

练习

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
1. 查询员工的last_name, department_id, salary.其中员工的salary,department_id与有奖金的任何一个员工的salary,department_id相同即可
    1. select last_name, department_id, salary
    2. from employees
    3. where (salary,department_id) in (
    4.                                  select  salary,department_id
    5.                                  from employees
    6.                                  where commission_pct is  not null
    7.                                 )

2. 选择工资大于所有JOB_ID = 'SA_MAN'的员工的工资的员工的last_name, job_id, salary

select last_name, job_id, salary
from employees
where salary > all(
                  select salary
                  from employees
                  where job_id = 'SA_MAN'
                  )
3. 选择所有没有管理者的员工的last_name

select last_name
from employees e1
where not exists (
              select 'A'
              from employees e2
              where e1.manager_id = e2.employee_id
              )

Mybatis JdbcType与Oracle、MySql数据类型对应列表

Mybatis JdbcType与Oracle、MySql数据类型对应列表

Mybatis JdbcType Oracle MySql
JdbcType ARRAY
JdbcType BIGINT BIGINT
JdbcType BINARY
JdbcType BIT BIT
JdbcType BLOB BLOB BLOB
JdbcType BOOLEAN
JdbcType CHAR CHAR CHAR
JdbcType CLOB CLOB TEXT
JdbcType CURSOR
JdbcType DATE DATE DATE
JdbcType DECIMAL DECIMAL DECIMAL
JdbcType DOUBLE NUMBER DOUBLE
JdbcType FLOAT FLOAT FLOAT
JdbcType INTEGER INTEGER INTEGER
JdbcType LONGVARBINARY
JdbcType LONGVARCHAR LONG VARCHAR
JdbcType NCHAR NCHAR
JdbcType NCLOB NCLOB
JdbcType NULL
JdbcType NUMERIC NUMERIC/NUMBER NUMERIC/
JdbcType NVARCHAR
JdbcType OTHER
JdbcType REAL REAL REAL
JdbcType SMALLINT SMALLINT SMALLINT
JdbcType STRUCT
JdbcType TIME TIME
JdbcType TIMESTAMP TIMESTAMP TIMESTAMP/DATETIME
JdbcType TINYINT TINYINT
JdbcType UNDEFINED
JdbcType VARBINARY
JdbcType VARCHAR VARCHAR VARCHAR

注意到, MyBatis的JdbcType中部分没有对应到Oracle和Mysql的数据类型中(或许由于自己遗漏),不过不用担心,后续大家碰到再具体分析;同时上述对应关系不一定是一一对应,请大家了解。

大家主要掌握基本的数字、时间、字符串就足以应对日常开发了。 

  • org.apache.ibatis.type.JdbcType可以查看多有的类型
    • 都是大写!

Oracle的nvl函数和nvl2函数

一、基本语法

介绍一下oracle的nvl函数和nvl2函数。

  • nvl函数

nvl函数基本语法为nvl(E1,E2),意思是E1为null就返回E2,不为null就返回E1。

  • nvl2函数

nvl2函数的是nvl函数的拓展,基本语法为nvl2(E1,E2,E3),意思是E1为null,就返回E3,不为null就返回E2。

二、业务场景

nvl()函数比较常用的是这样的nvl(E1,0),意思是E1参数查询到为null的情况,就返回0,不为null就返回E1,常用于非空校验。

nvl2()函数也讲一个业务场景。

今天用列转行函数vm_concat查询的时候,遇到一个问题,我用vm_concat查询,假如b参数为空的情况就会出现“a()”的参数,我想做的是b参数为空的情况,直接返回“a”参数,b参数不为空的情况才返回“a(b)”类型的数据,比如可以是用户名a(账号b)这样显示。原来SQL是这样的。

1
select to_char(nvl2(b,vm_concat(a||'('||b||')'), '') from A group by id

改写SQL,通过nvl2函数实现改写:

1
2
3
select to_char(wm_concat(nvl2(b,
                             a || '(' || b || ')',
                             a))) as 返回参数

oracle的decode函数

最近发现oracle数据库中一个自带的函数decode。

查看了一下用法,简直了。真是太好用了,看来多看API,真的可以短时间让我们有很大的提升。(虽然我也是读代码的过程中看到的~~)

    DECODE是Oracle公司独家提供的功能(ps:informix数据库也提供该函数),它是一个功能很强的函数。它虽然不是SQL的标准,但对于性能非常有用。到目前,其他的数据库供应商还不能提供类似DECODE的功能,甚至有的数据库的供应商批评Oracle的SQL不标准。实际上,这种批评有些片面或不够水平。就象有些马车制造商抱怨亨利·福特的“马车”不标准一样。(来自百度百科)

    一种吃不到葡萄说葡萄酸的感觉。但是为何说这个函数很强大呢,因为你在数据库中写了很多行的If函数,可能用了decode函数后,一行就可以解决。写完后,代码简洁明了,绝对上升了好几个档次。

说一下具体的用法,你真的会“卧槽,这么强”

decode(条件,值1,返回值1,值2,返回值2,…值n,返回值n,缺省值),这行代码就是decode全部的精髓以及用法。

1
2
3
4
5
6
IF 条件=值1 THEN  RETURN(返回值1)
ELS IF 条件=值2 THEN  RETURN(返回值2)
......
ELSIF 条件=值n THEN  RETURN(返回值n)
ELSE  RETURN(缺省值)
END IF

decode(字段或字段的运算,值1,值2,值3)

    这个函数运行的结果是,当字段或字段的运算的值等于值1时,该函数返回值2,否则返回值3

    当然值1,值2,值3也可以是表达式,这个函数使得某些sql语句简单了许多

你要统计students表中,男生女生的数量:

  • 传统写法如下:
1
2
3
select count(*) from students where 性别 = 男;
select count(*) from students where 性别 = 女;
然后使用Union连接,得到最终的统计结果。
  • 现在你只需要这样来写:
1
select decode(性别,男,1,0),decode(性别,女,1,0) from students

应用到order by 排序中:

​ 在表中,有一列为学科,分别为语文,数学,英语,自然,美术等等,你要将表中信息按照学科分类。

    可以使用decode这样来写:

1
2
3
select * from 学科 order by decode(学科, '语文', 1, '数学', 2, , '外语',3...)
相当于指定排序的顺序,这样就会按语,数,英的顺序排序.
如果没有匹配到的数据,就跟在后面按默认排序。

上面两个例子,可以说明使用decode可以使我们sql代码简洁明了,可读性更强。

oracle为什么没有主键?

image-20230927212240103

一般来说,是否需要主键需要考虑:

  • 你的表是否需要关联
  • 你的表是否集群,是的要也要主键[todo:为什么集群要有主键?]
  • 如果你的表设计为不需要主键,那么需要考虑为什么会允许保留重复记录!

日志表等出于性能方面的考虑,它们没有任何索引。

或者是批量加载大量数据的临时表。

或者是主键关联的中间表

说实话,以上还没有足够强有力的理由说明必须要有主键(或者我还是不太理解)。

总之,有主键是一个好的习惯

mark待补充

附件

参考链接:

oracle 开发命名规范

我相信大家不管是做管理或者是做开发也好,都要看代码是吧。作为一个开发人员,代码能不能让人看懂,的确是一个很考验开发人员编程的功底。好像每次看到别人写的代码都会觉得:bull shit! What the fuck is that?

    所以,一个开发人员,不但代码也要写得好,并且也要有很好的风格,这样才能把编程的艺术发挥的淋漓尽致。

    下面记录下学习到的Oracle开发所需要了解的命名规范(从命名开始)

  • 表和字段命名规范

    UserPrivilege骆驼命名法,这是一种相当有美感的方法,适合那些英文比较好的(其实我觉得英文不好更需要用,这样用得多了英文也就好了,顶多用的时候多备一个翻译词典)

    tbl_user_privilege下划线命名法,这是一种比较古老的命名方法,好像是从C开始有的吧,适合做开发(不过我个人比较喜欢骆驼命名法,更加直观)

注意:不建议使用数据库关键字和保留字(关键字和保留字:select * from v$reserved_words where reserved=’Y’)

  •  其它对象命名规范
对象名 前缀 范例
表(table) tbl_  / t_ t_user_info  /  tbl_user_info
视图(view) v_  /  v v_user_info  /  vuserinfo
序列(sequence) seq_ seq_user_info
簇(cluster) c_ c_user_info
解发器(tigger) trg_ trg_user_info
存储过程(procedure) sp_  /  p_ sp_user_info  /  p_
函数(function) f_  /  fn_ fn_user_info  /  fn_user_info
物化视图(materialized view) mv_ mv_user_info
包和包体(package & package body) pkg_ pkg_user_info
类和类体(type & type body) typ_ typ_user_info
主键(primary key) pk_ pk_user_info_fieldname
外键(foreign key) fk_ fk_user_info
唯一索引(unique index) uk_ uk_user_info_fieldname
普通索引(normal index) idx_ idx_user_info_fieldname
位图索引(bitmap index) bk_ bk_user_info_fieldname
同义词(synonym) 依据所分配的表所属模块

plsql程序设计

PL/SQL程序设计简介

PL/SQL是一种高级数据库程序设计语言,该语言专门用于在各种环境下对ORACLE数据库进行访问。由于该语言集成于数据库服务器中所以PL/SQL代码可以对数据进行快速高效的处理。除此之外,可以在ORACLE数据库的某些客户端工具中,使用PL/SQL语言也是该语言的一个特点。本章的主要内容是讨论引入PL/SQL语言的必要性和该语言的主要特点,以及了解PL/SQL语言的重要性和数据库版本问题。还要介绍一些贯穿全书的更详细的高级概念,并在本章的最后就我们在本书案例中使用的数据库表的若干约定做一说明。

什么是 PL/SQL?

PL/SQL 是 Procedure Language & Structured Query Language 的缩写。ORACLE 的 SQL 是支持ANSI(American national Standards Institute)和 ISO92 (International Standards Organization)标准的产品。PL/SQL是对** SQL 语言存储过程语言的扩展。从 ORACLE6 以后,ORACLE 的 RDBMS 附带了 PL/SQL。它现在已经成为一种过程处理语言,简称 PL/SQL。目前的 PL/SQL 包括两部分,一部分是数据库引擎部分;另一部分是可嵌入到许多产品(如 C 语言,JAVA 语言等)工具中的独立引擎。可以将这两部分称为:数据库 PL/SQL 和工具PL/SQL。两者的编程非常相似。都具有编程结构、语法和逻辑机制。工具 PL/SQL 另外还增加了用于支持工具(如 ORACLE Forms)的句法,如:在窗体上设置按钮等。本章主要介绍数据库 PL/SQL 内容。

PL/SQL 的好处

有利于客户/服务器环境应用的运行

对于客户/服务器环境来说,真正的瓶颈是网络上。无论网络多快,只要客户端与服务器进行大量的数据交换。应用运行的效率自然就回受到影响。如果使用 PL/SQL 进行编程,将这种具有大量数据处理的应用放在服务器端来执行。自然就省去了数据在网上的传输时间。

适合于客户环境

PL/SQL 由于分为数据库 PL/SQL 部分和工具 PL/SQL。对于客户端来说,PL/SQL 可以嵌套到相应的工具中,客户端程序可以执行本地包含 PL/SQL 部分,也可以向服务发 SQL 命令或激活服务器端的 PL/SQL 程序运行。

PL/SQL 可用的 SQL 语句

PL/SQL ORACLE 系统的核心语言,现在 ORACLE 的许多部件都是由 PL/SQL 写成。在 PL/SQL 中可以使用的 SQL 语句有:

1
INSERT,UPDATE,DELETE,SELECT … INTO,COMMIT,ROLLBACK,SAVEPOINT。

提示:在 PL/SQL 中只能用 SQL 语句中的 DML 部分,不能用 DDL 部分,如果要在 PL/SQL 中使用 DDL(CREATE table )的话,只能以动态的方式来使用。

  • ORACLE 的 PL/SQL 组件在对 PL/SQL 程序进行解释时,同时对在其所使用的表名、列名及数据类型进行检查。
  • PL/SQL 可以在 SQL*PLUS 中使用。
  • PL/SQL 可以在高级语言中使用。
  • PL/SQL 可以 在 ORACLE 的 开发工具中使用。
  • 其它开发工具也可以调用 PL/SQL 编写的过程和函数,如 Power Builder 等都可以调用服务器端的PL/SQL 过程。

运行 PL/SQL 程序

PL/SQL 程序的运行是通过 ORACLE 中的一个引擎来进行的。这个引擎可能在 ORACLE 的服务器端,也可能在 ORACLE 应用开发的客户端。引擎执行 PL/SQL 中的过程性语句,然后将 SQL 语句发送给数据库服务器来执行。再将结果返回给执行端。

PL/SQL块结构和组成元

PL/SQL

PL/SQL 程序由三个块组成,即声明部分、执行部分、异常处理部分

PL/SQL 块的结构如下:

1
2
3
4
5
6
7
DECLARE
/* 声明部分: 在此声明 PL/SQL 用到的变量,类型及游标,以及局部的存储过程和函数 */
BEGIN
/* 执行部分: 过程及 SQL 语句 , 即程序的主要部分 */
EXCEPTION
/* 执行异常部分: 错误处理 */
END;

其中 执行部分是必须的。

PL/SQL 块可以分为三类

  1. 无名块:动态构造,只能执行一次。
  2. 子程序:存储在数据库中的存储过程、函数及包等。当在数据库上建立好后可以在其它程序中调用它
  3. 触发器:当数据库发生操作时,会触发一些事件,从而自动执行相应的程序。

PL/SQL 结构

  • PL/SQL块中可以包含子块
  • 子块可以位于PL/SQL 中的任何部分
  • 子块也即PL/SQL 中的一条命令

标识符

PL/SQL 程序设计中的标识符定义与 SQL 的标识符定义的要求相同。要求和限制有:

  • 标识符名不能超过
  • 第一个字符必须为字母;
  • 不分大小写;
  • 不能用-(减号);
  • 不能是SQL 保留字

提示: 一般不要把变量名声明与表中字段名完全一样,如果这样可能得到不正确的结果.

  • 例如:下面的例子将会删除所有的纪录,而不是 KING 的记录;
1
2
3
4
5
DECLARE
    Ename varchar2(20) := ’KING’;
BEGIN
    DELETE FROM emp WHERE ename=ename;
END;

变量命名在 PL/SQL 中有特别的讲究,建议在系统的设计阶段就要求所有编程人员共同遵守一定的要求,使得整个系统的文档在规范上达到要求。下面是建议的命名方法

image-20230927212248475

PL/SQL 变量类型

在前面的介绍中,有系统的数据类型,也可以自定义数据类型。下表是 ORACLE 类型和 PL/SQL 中的变量类型的合法使用列表:

变量类型

在 ORACLE8i 中可以使用的变量类型有:

image-20230927212253050

复合类型

ORACLE 在 PL/SQL 中除了提供象前面介绍的各种类型外,还提供一种称为复合类型的类型---记录和表.

记录类型

记录类型是把逻辑相关的数据作为一个单元存储起来,称作 PL/SQL RECORD 的域(FIELD),其作用是存放互不相同但逻辑相关的信息**。

定义记录类型语法如下:

1
2
3
4
5
TYPE record_type IS RECORD(
    Field1 type1 [NOT NULL] [:= exp1],
    Field2 type2 [NOT NULL] [:= exp2],
    . . . . . .
    Fieldn typen [NOT NULL] [:= expn ]) ;

1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
declare
  -- 声明一个记录类型
  type emp_record is record(
    v_sal employees.salary%type,
    v_email employees.email%type,
    v_hire_date employees.hire_date%type
  );
  -- 定义一个记录类型的成员变量
  v_emp_record emp_record;
begin
  -- sql 语句的操作: select .. into ... from ... where ...
  select salary,email,hire_date into v_emp_record from employees  where employee_id=100;
  -- 打印
   dbms_output.put_line(v_emp_record.v_sal||','||v_emp_record.v_email||','||v_emp_record.v_hire_date);
end;

提示:

1) DBMS_OUTPUT.PUT_LINE 过程的功能类似于 Java 中的 System.out.println() 直接将输出结果送到 标准输出中.

2) 在使用上述过程之前必须将 SQL * PLUS 的环境参数 SERVEROUTPUT 设置为 ON, 否则将看不到输出结果: set serveroutput on

可以用 SELECT 语句对记录变量进行赋值,只要保证记录字段与查询结果列表中的字段相配即可。 

使用%TYPE

定义一个变量,其数据类型与已经定义的某个数据变量的类型相同,或者与数据库表的某个列的数据类型相同,这时可以使用**%TYPE**。

使用%TYPE 特性的优点在于:

  • 所引用的数据库列的数据类型可以不必知道;
  • 所引用的数据库列的数据类型可以实时改变。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
declare
  -- 声明的变量,类型,游标
  v_sal employees.salary%type;
  v_email employees.email%type;
  v_hire_date employees.hire_date%type;
begin
  -- 程序的执行部分,类似于java里的main()方法
  dbms_output.put_line('helloworld');
  -- sql 语句的操作: select .. into ... from ... where ...
  select salary,email,hire_date into v_sal,v_email,v_hire_date  from employees where employee_id=100;
  -- 打印
  dbms_output.put_line(v_sal||','||v_email||','||v_hire_date);
--exception
  -- 针对begin块中出现的异常,提供处理的机制
  -- when ... then ...
  -- when ... then ...  
end;
使用%ROWTYPE

PL/SQL 提供**%ROWTYPE** 操作符, 返回一个记录类型, 其数据类型和数据库表的数据结构相一致

使用%ROWTYPE 特性的优点在于:

  • 所引用的数据库中列的个数和数据类型可以不必知道;
  • 所引用的数据库中列的个数和数据类型可以实时改变。

3:

1
2
3
4
5
6
7
8
declare  
  v_emp employees%rowtype;
begin  
  select * into v_emp from employees where employee_id = 200;
  dbms_output.put_line(v_emp.v_sal
                ||','||v_emp.v_email
                ||','||v_emp.v_hire_date);
end;

PL/SQL 表(嵌套表)

PL/SQL 程序可使用嵌套表类型创建具有一个或多个列和无限行的变量, 这很像数据库中的表. 声明嵌

套表类型的一般语法如下:

1
2
TYPE type_name IS TABLE OF
{datatype | {variable | table.column} % type | table%rowtype};

image-20230927212259378

例 4:

image-20230927212307444

1
2
3
4
5
6
7
8
9
10
11
12
declare  
  type dep_table_type is table of departments%rowtype;
  my_dep_table dep_table_type := dep_table_type();
begin  
  my_dep_table.extend(5);
  for i in 1 .. 5 loop
    select * into my_dep_table(i) from departments
    where department_id = 200 + 10 + i;
  end loop;
  dbms_output.put_line(my_dep_table.count());
  dbms_output.put_line(my_dep_table(1).department_id);
end;

说明:

  1. 在使用嵌套表之前必须先使用该集合的构造器初始化它. PL/SQL 自动提供一个带有相同名字的构造器作为集合类型.

  2. 嵌套表可以有任意数量的行. 表的大小在必要时可动态地增加或减少: extend(x) 方法添加 x 个空元素到集合末尾; trim(x) 方法为去掉集合末尾的 x 个元素.

运算符和表达式(数据定义)

关系运算符

image-20230927212312200

一般运算符

image-20230927212318992

逻辑运算符

image-20230927212325459

变量赋值

在 PL/SQL 编程中,变量赋值是一个值得注意的地方,它的语法如下:

1
variable := expression;

variable 是一个 PL/SQL 变量, expression 是一个 PL/SQL 表达式.

字符及数字运算特点

空值加数字仍是空值:NULL + <数字> = NULL

空值加(连接)字符,结果为字符NULL || <字符串> = <字符串>

BOOLEAN 赋值

布尔值只有 TRUE, FALSE 及 NULL 三个值。

数据库赋值

数据库赋值是通过 SELECT语句来完成的,每次执行 SELECT语句就赋值一次,一般要求被赋值的变量与SELECT中的列名要一一对应。如:

例 9:

1
2
3
4
5
6
7
8
9
DECLARE
    emp_id emp.empno%TYPE :=7788;
    emp_name emp.ename%TYPE;
    wages emp.sal%TYPE;
BEGIN
    SELECT ename, NVL(sal,0) + NVL(comm,0) INTO emp_name, wages
    FROM emp WHERE empno = emp_id;
    DBMS_OUTPUT.PUT_LINE(emp_name||’----‘||to_char(wages));
END;

提示:不能将SELECT语句中的列赋值给布尔变量。

可转换的类型赋值

  • CHAR

    使用 TO_NUMBER 函数来完成字符到数字的转换,如:v_total := TO_NUMBER('100.0') + sal;

  • NUMBER

    使用 TO_CHAR 函数可以实现数字到字符的转换,如:v_comm := TO_CHAR('123.45') || '元';

  • 字符转换为日期:

    使用 TO_DATE 函数可以实现 字符到日期的转换,如:v_date := TO_DATE('2001.07.03','yyyy.mm.dd');

  • 日期转换为字符

    使用 TO_CHAR 函数可以实现日期到字符的转换,如:v_to_day := TO_CHAR(SYSDATE, 'yyyy.mm.dd hh24:mi:ss') ;

变量作用范围及可见性

在 PL/SQL 编程中,如果在变量的定义上没有做到统一的话,可能会隐藏一些危险的错误,这样的原因

主要是变量的作用范围所致。与其它高级语言类似,PL/SQL 的变量作用范围特点是:

  • 变量的作用范围是在你所引用的程序单元(块、子程序、包)内。即从声明变量开始到该块的结束。
  • 一个变量(标识)只能在你所引用的块内是可见的。
  • 当一个变量超出了作用范围,
  • 在子块中重新定义该变量后,它的作用仅在该块内。

注释

在PL/SQL里,可以使用两种符号来写注释,即:

  • 使用双- ( 减号) 加注释

    PL/SQL允许用 – 来写注释,它的作用范围是只能在一行有效。如:V_Sal NUMBER(12,2); -- 工资变量。

  • 使用/* */ 来加一行或多行注释,如
1
2
3
/**********************************/
/* 文件名: department_salary.sql */
/*********************************/

提示:被解释存放在数据库中的 PL/SQL 程序,一般系统自动将程序头部的注释去掉。只有在 PROCEDURE之后的注释才被保留;另外程序中的空行也自动被去掉。

简单例子

简单数据插入例子

11:

1
2
3
4
5
6
7
8
9
10
11
/* 本例子仅是一个简单的插入,不是实际应用。 */
DECLARE
    v_ename VARCHAR2(20) := ‘Bill’;
    v_sal NUMBER(7,2) :=1234.56;
    v_deptno NUMBER(2) := 10;
    v_empno NUMBER(4) := 8888;
BEGIN
    INSERT INTO emp ( empno, ename, JOB, sal, deptno , hiredate ) 
    VALUES ( v_empno, v_ename, ‘Manager’, v_sal, v_deptno,TO_DATE(’1954.06.09’,’yyyy.mm.dd’) );
    COMMIT;
END;

简单数据删除例子

例 12:

1
2
3
4
5
6
7
/* 本例子仅是一个简单的删除例子,不是实际应用。 */
DECLARE
    v_empno number(4) := 8888;
BEGIN
    DELETE FROM emp WHERE empno=v_empno;
    COMMIT;
END;

PL/SQL 流程控制语句

介绍 PL/SQL 的流程控制语句, 包括如下三类:

  • 控制语句: IF 语句
  • 循环语句: LOOP 语句, EXIT 语句
  • 顺序语句: GOTO 语句, NULL 语句

条件语句

1
2
3
IF <布尔表达式> THEN
    PL/SQL 和 SQL 语句;
END IF;
1
2
3
4
5
IF <布尔表达式> THEN
    PL/SQL 和 SQL 语句;
ELSE
    其它语句;
END IF;
1
2
3
4
5
6
7
8
9
IF <布尔表达式> THEN
    PL/SQL 和 SQL 语句;
ELSIF < 其它布尔表达式> THEN
    其它语句;
ELSIF < 其它布尔表达式> THEN
    其它语句;
ELSE
    其它语句;
END IF;

提示: ELSIF 不能写成 ELSEIF

1:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
DECLARE
    v_empno emp.empno%TYPE;
    V_salary emp.sal%TYPE;
    V_comment VARCHAR2(35);
BEGIN
    SELECT sal INTO v_salary FROM emp WHERE empno=v_empno;
    IF v_salary<1500 THEN
        V_comment:= 'Fairly less';
    ELSIF v_salary <3000 THEN
        V_comment:= 'A little more';
    ELSE
        V_comment:= 'Lots of salary';
    END IF;
    DBMS_OUTPUT.PUT_LINE(V_comment);
END;

CASE 表达式

1
2
3
4
5
6
CASE selector
WHEN expression1 THEN result1
WHEN expression2 THEN result2
WHEN expressionN THEN resultN
[ ELSE resultN+1]
END;

例 2:

1
2
3
4
5
6
7
8
9
10
11
12
13
DECLARE
    V_grade char(1) ;
    V_appraisal VARCHAR2(20);
BEGIN
    V_appraisal :=
        CASE v_grade
            WHEN 'A' THEN 'Excellent'
            WHEN 'B' THEN 'Very Good'
            WHEN 'C' THEN 'Good'
            ELSE 'No such grade'
        END;
    DBMS_OUTPUT.PUT_LINE('Grade:'||v_grade||'Appraisal: '|| v_appraisal);
END;

循环

1. 简单循环

1
2
3
4
LOOP
要执行的语句;
EXIT WHEN <条件语句> ; /*条件满足,退出循环语句*/
END LOOP;

3.

1
2
3
4
5
6
7
8
9
DECLARE
    int NUMBER(2) :=0;
BEGIN
    LOOP
        int := int + 1;
        DBMS_OUTPUT.PUT_LINE('int 的当前值为:'||int);
        EXIT WHEN int =10;
    END LOOP;
END;

2. WHILE 循环(相较 1,推荐使用** 2)

1
2
3
WHILE <布尔表达式> LOOP
    要执行的语句;
END LOOP;

例 4.

1
2
3
4
5
6
7
8
DECLARE
    x NUMBER :=1;
BEGIN
    WHILE x<=10 LOOP
        DBMS_OUTPUT.PUT_LINE('X 的当前值为:'||x);
        x:= x+1;
    END LOOP;
END;

3. 数字式循环

1
2
3
FOR 循环计数器 IN [ REVERSE ] 下限 .. 上限 LOOP
    要执行的语句;
END LOOP;

每循环一次,循环变量自动加 1;使用关键字 REVERSE,循环变量自动减 1。跟在 IN REVERSE 后面的数字必须是从小到大的顺序,而且必须是整数,不能是变量或表达式。可以使用 EXIT 退出循环。

5.

1
2
3
4
5
BEGIN
    FOR int in 1..10 LOOP
        DBMS_OUTPUT.PUT_LINE('int 的当前值为: '||int);
    END LOOP;
END;

6.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
CREATE TABLE temp_table(num_col NUMBER);
DECLARE
    V_counter NUMBER := 10;
BEGIN
    INSERT INTO temp_table(num_col) VALUES (v_counter );
    FOR v_counter IN 20 .. 25 LOOP
        INSERT INTO temp_table (num_col ) VALUES ( v_counter );
    END LOOP;
    INSERT INTO temp_table(num_col) VALUES (v_counter );
    FOR v_counter IN REVERSE 20 .. 25 LOOP
        INSERT INTO temp_table (num_col ) VALUES ( v_counter );
    END LOOP;
END ;
DROP TABLE temp_table;

标号和 GOTO

PL/SQL 中 GOTO 语句是无条件跳转到指定的标号去的意思。语法如下:

1
2
3
GOTO label;
. . . . . .
<<label>> /*标号是用<< >>括起来的标识符 */

例 7:

1
2
3
4
5
6
7
8
9
10
11
12
13
DECLARE
    V_counter NUMBER := 1;
BEGIN
    LOOP
        DBMS_OUTPUT.PUT_LINE('V_counter 的当前值为:'||V_counter);
        V_counter := v_counter + 1;
        IF v_counter > 10 THEN
            GOTO l_ENDofLOOP;
        END IF;
    END LOOP;
    <<l_ENDofLOOP>>
    DBMS_OUTPUT.PUT_LINE('V_counter 的当前值为:'||V_counter);
END ;

NULL 语句

在 PL/SQL 程序中,可以用 null 语句来说明“不用做任何事情”的意思,相当于一个占位符,可以使某些语句变得有意义,提高程序的可读性。如:

1
2
3
4
5
6
7
8
9
10
11
DECLARE
    ...
BEGIN
    ...
    IF v_num IS NULL THEN
        GOTO print1;
    END IF;
    ...
    <<print1>>
    NULL; -- 不需要处理任何数据。
END;

游标的使用

在 PL/SQL 程序中,对于处理多行记录的事务经常使用游标来实现。

游标概念

为了处理 SQL 语句,ORACLE 必须分配一片叫上下文( context area )的区域来处理所必需的信息,其中包括要处理的行的数目,一个指向语句被分析以后的表示形式的指针以及查询的活动集(active set)。

游标是一个指向上下文的句柄( handle)或指针。通过游标,PL/SQL 可以控制上下文区和处理语句时上

下文区会发生些什么事情。

对于不同的 SQL 语句,游标的使用情况不同:

image-20230927212333683

处理显式游标

1. 显式游标处理

显式游标处理需四个 PL/SQL 步骤:

  • 定义游标

    格式:

1
CURSOR cursor_name[(parameter[, parameter]…)] IS select_statement;

    游标参数只能为输入参数,其格式为:

1
parameter_name [IN] datatype [{:= | DEFAULT} expression]

​ 在指定数据类型时,不能使用长度约束**。如 NUMBER(4)、CHAR(10) 等都是错误的。

  • 打开游标

    格式:

1
OPEN cursor_name[([parameter =>] value[, [parameter =>] value]…)];

    在向游标传递参数时,可以使用与函数参数相同的传值方法,即位置表示法和名称表示 法。PL/SQL程序不能用 OPEN 语句重复打开一个游标。

  • 提取游标数据

    格式:

1
FETCH cursor_name INTO {variable_list | record_variable };
  • 对该记录进行处理;
  • 继续处理,直到活动集合中没有记录;
  • 关闭游标

    格式:

1
CLOSE cursor_name;

注:定义的游标不能有 INTO 子句

1. 查询前 10 名员工的信息。

image-20230927212338558

例 2. 游标参数的传递方法。

image-20230927212342546

image-20230927212350662

**2.**游标属性

1
2
3
4
%FOUND 布尔型属性,当最近一次读记录时成功返回,则值为 TRUE;
%NOTFOUND 布尔型属性,与%FOUND 相反;
%ISOPEN 布尔型属性,当游标已打开时返回 TRUE;
%ROWCOUNT 数字型属性,返回已从游标中读取的记录数。

3:给工资低于 3000 的员工工资调为 3000。

image-20230927212356015

3. 游标的 FOR 循环

PL/SQL 语言提供了游标 FOR 循环语句,自动执行游标的 OPENFETCHCLOSE 语句和循环语句的功能;

当进入循环时,游标 FOR 循环语句自动打开游标,并提取第一行游标数据,当程序处理完当前所提取的数据而进入下一次循环时,游标 FOR 循环语句自动提取下一行数据供程序处理,当提取完结果集合中的所有数据行后结束循环,并自动关闭游标

格式:

1
2
3
FOR index_variable IN cursor_name[value[, value]…] LOOP
-- 游标数据处理代码
END LOOP;

其中:

index_variable 为游标 FOR 循环语句隐含声明的索引变量,该变量为记录变量,其结构与游标查询语句返回的结构集合的结构相同。在程序中可以通过引用该索引记录变量元素来读取所提取的游标数据,index_variable 中各元素的名称与游标查询语句选择列表中所制定的列名相同。如果在游标查询语句的选择列表中存在计算列,则必须为这些计算列指定别名后才能通过游标 FOR 循环语句中的索引变量来访问这些列数据。

注:不要在程序中对游标进行人工操作;不要在程序中定义用于控制 FOR 循环的记录。

4

image-20230927212400567

5:当所声明的游标带有参数时,通过游标 FOR 循环语句为游标传递参数。

image-20230927212410149

image-20230927212415353

6:PL/SQL 还允许在游标 FOR 循环语句中使用子查询来实现游标的功能。

image-20230927212419398

处理隐式游标

显式游标主要是用于对查询语句的处理,尤其是在查询结果为多条记录的情况下;而对于非查询语句,如修改、删除操作,则由 ORACLE 系统自动地为这些操作设置游标并创建其工作区,这些由系统隐含创建的游标称为隐式游标隐式游标的名字为** SQL,这是由 ORACLE 系统定义的。对于隐式游标的操作,如定义、打开、取值及关闭操作,都由 ORACLE 系统自动地完成,无需用户进行处理。用户只能通过隐式游标的相关属性,来完成相应的操作**。在隐式游标的工作区中,所存放的数据是与用户自定义的显示游标无关的、最新处理的一条 SQL 语句所包含的数据。

格式调用为: SQL%

隐式游标属性

1
2
3
4
5
6
7
SQL%FOUND 布尔型属性,当最近一次读记录时成功返回,则值为 TRUE;

SQL%NOTFOUND 布尔型属性,与%FOUND 相反;

SQL%ROWCOUNT 数字型属性, 返回已从游标中读取得记录数;

SQL%ISOPEN 布尔型属性, 取值总是 FALSE。SQL 命令执行完毕立即关闭隐式游标。

7: 更新指定员工信息,如果该员工没有找到,则打印”查无此人”信息。

image-20230927212425026

关于 NO_DATA_FOUND %NOTFOUND 的区别

1
SELECT … INTO 语句触发 NO_DATA_FOUND;

当一个显式游标的 WHERE 子句未找到时触发%NOTFOUND;

当 UPDATE 或 DELETE 语句的 WHERE 子句未找到时触发 SQL%NOTFOUND;在提取循环中要用 %NOTFOUND或%FOUND 来确定循环的退出条件,不要用 NO_DATA_FOUND.

游标修改和删除操作

游标修改和删除操作是指在游标定位下,修改或删除表中指定的数据行。这时,要求游标查询语句中必须使用 FOR UPDATE 选项,以便在打开游标时锁定游标结果集合在表中对应数据行的所有列和部分列。为了对正在处理(查询)的行不被另外的用户改动,ORACLE 提供一个 FOR UPDATE 子句来对所选择的行进行锁住。该需求迫使 ORACLE 锁定游标结果集合的行,可以防止其他事务处理更新或删除相同的行,直到您的事务处理提交或回退为止。

1
语法:SELECT . . . FROM … FOR UPDATE [OF column[, column]…] [NOWAIT]

如果另一个会话已对活动集中的行加了锁,那么 SELECT FOR UPDATE 操作一直等待到其它的会话释放这些锁后才继续自己的操作,对于这种情况,当加上 NOWAIT 子句时,如果这些行真的被另一个会话锁定,则 OPEN 立即返回并给出:ORA-0054 :resource busy and acquire with nowait specified.

如果使用 FOR UPDATE 声明游标,则可在 DELETE UPDATE 语句中使用 WHERE CURRENT OFcursor_name 子句,修改或删除游标结果集合当前行对应的数据库表中的数据行

8:从 EMPLOYEES 表中查询某部门的员工情况,将其工资最低定为 3000;

image-20230927212429505

异常错误处理

一个优秀的程序都应该能够正确处理各种出错情况,并尽可能从错误中恢复。ORACLE 提供异常情况(EXCEPTION)和异常处理(EXCEPTION HANDLER)来实现错误处理。

异常处理概念

异常情况处理(EXCEPTION)是用来处理正常执行过程中未预料的事件,程序块的异常处理预定义的错误自定义错误,由于 PL/SQL 程序块一旦产生异常而没有指出如何处理时,程序就会自动终止整个程序运行.有三种类型的异常错误:

1. 预定义 **( Predefined )**错误

ORACLE 预定义的异常情况大约有 24 个。对这种异常情况的处理,无需在程序中定义, ORACLE 自动将其引发**。

2. 非预定义 ( Predefined )错误

即其他标准的 ORACLE 错误。对这种异常情况的处理,需要用户在程序中定义,然后由 ORACLE 自动将其引发。

3. 用户定义(User_define) 错误

程序执行过程中,出现编程人员认为的非正常情况。对这种异常情况的处理,需要用户在程序中定义,然后显式地在程序中将其引发**。

异常处理部分一般放在 PL/SQL 程序体的后半部,结构为:

1
2
3
4
5
6
7
8
EXCEPTION

WHEN first_exception THEN

WHEN second_exception THEN

WHEN OTHERS THEN
END;

异常处理可以按任意次序排列,但 OTHERS 必须放在最后.

预定义的异常处理

预定义说明的部分 ORACLE 异常错误

image-20230927212433905

image-20230927212900636

对这种异常情况的处理,只需在 PL/SQL 块的异常处理部分,直接引用相应的异常情况名,并对其完成相应的异常错误处理即可。

1:更新指定员工工资,如工资小于 300,则加 100;对 NO_DATA_FOUND 异常, TOO_MANY_ROWS 进行处理.

image-20230927212438053

非预定义的异常处理

对于这类异常情况的处理,首先必须对非定义的 ORACLE 错误进行定义。步骤如下:

  1. PL/SQL 块的定义部分定义异常情况
1
<异常情况> EXCEPTION;

2. 将其定义好的异常情况,与标准的 ORACLE 错误联系起来,使用 PRAGMA EXCEPTION_INIT 语句:

1
PRAGMA EXCEPTION_INIT(<异常情况>, <错误代码>);

3. PL/SQL 块的异常情况处理部分对异常情况做出相应的处理。

2 删除指定部门的记录信息,以确保该部门没有员工。

image-20230927212442630

用户自定义的异常处理

当与一个异常错误相关的错误出现时,就会隐含触发该异常错误。用户定义的异常错误是通过显式使用** RAISE 语句来触发。当引发一个异常错误时,控制就转向到 EXCEPTION 块异常错误部分,执行错误处理代码。

对于这类异常情况的处理,步骤如下:

1. PL/SQL 块的定义部分定义异常情况

1
<异常情况> EXCEPTION;

2. **RAISE <**异常情况**>**;

3. PL/SQL 块的异常情况处理部分对异常情况做出相应的处理

3:更新指定员工工资,增加 100;若该员工不存在则抛出用户自定义异常: no_result

image-20230927212448085

PL/SQL 中使用 SQLCODE, SQLERRM

SQLCODE 返回错误代码数字

SQLERRM 返回错误信息.

image-20230927212452640

5. 将 ORACLE 错误代码及其信息存入错误代码表

1
2
3
4
5
6
7
8
9
10
11
CREATE TABLE errors (errnum NUMBER(4), errmsg VARCHAR2(100));
DECLARE
    err_msg VARCHAR2(100);
BEGIN
    /* 得到所有 ORACLE 错误信息 */
    FOR err_num IN -100 .. 0 LOOP
        err_msg := SQLERRM(err_num);
        INSERT INTO errors VALUES(err_num, err_msg);
    END LOOP;
END;
DROP TABLE errors;

6. 查询 ORACLE 错误代码;

1
2
3
4
5
6
7
8
9
10
11
BEGIN
    INSERT INTO emp(empno, ename, hiredate, deptno)
    VALUES(2222, ‘Jerry’, SYSDATE, 20);
    DBMS_OUTPUT.PUT_LINE('插入数据记录成功!');
    INSERT INTO emp(empno, ename, hiredate, deptno)
    VALUES(2222, ‘Jerry’, SYSDATE, 20);
    DBMS_OUTPUT.PUT_LINE('插入数据记录成功!');
EXCEPTION
    WHEN OTHERS THEN
    DBMS_OUTPUT.PUT_LINE(SQLCODE||’---‘||SQLERRM);
END;

存储函数和过程

引言

1. ORACLE 提供可以把 PL/SQL 程序存储在数据库中,并可以在任何地方来运行它。这样就叫存储过程或函数。过程和函数统称为 PL/SQL 子程序,他们是被命名的 PL/SQL 块,均存储在数据库中,并通过输入、输出参数或输入/输出参数与其调用者交换信息。过程和函数的唯一区别是函数总向调用者返回数据,而过程则不返回数据

创建函数

**1.**建立内嵌函数

语法如下:

1
2
3
4
5
6
7
8
9
10
11
12
CREATE [OR REPLACE] FUNCTION function_name
[ (argment [ { IN | IN OUT }] Type,
argment [ { IN | OUT | IN OUT } ] Type ]
[ AUTHID DEFINER | CURRENT_USER ]
RETURN return_type
{ IS | AS }
<类型.变量的说明>
BEGIN
FUNCTION_body
EXCEPTION
其它语句
END;

说明:

1) OR REPLACE 为可选. 有了它, 可以或者创建一个新函数或者替换相同名字的函数, 而不会出现冲突

2) 函数名后面是一个可选的参数列表, 其中包含 IN, OUT 或 IN OUT 标记. 参数之间用逗号隔开. IN 参数标记表示传递给函数的值在该函数执行中不改变**; OUT 标记表示一个值在函数中进行计算并通过该参数传递给调用语句; **IN OUT 标记表示传递给函数的值可以变化并传递给调用语句. 若省略标记, 则参数隐含为 IN

3) 因为函数需要返回一个值, 所以 RETURN 包含返回结果的数据类型.

1. 不带参数的函数

image-20230927212457259

执行该函数

image-20230927212500904

2. 获取某部门的工资总和:

image-20230927212504982

**2.**内嵌函数的调用

函数声明时所定义的参数称为形式参数,应用程序调用时为函数传递的参数称为实际参数。应用程序在调用函数时,可以使用以下三种方法向函数传递参数:

第一种参数传递格式称为位置表示法,格式为:

1
argument_value1[,argument_value2 …]

3:计算某部门的工资总和:

image-20230927212508429

第二种参数传递格式称为名称表示法,格式为:

1
argument => parameter [,…]

其中:argument 为形式参数,它必须与函数定义时所声明的形式参数名称相同。Parameter 为实际参数。

在这种格式中,形势参数与实际参数成对出现,相互间关系唯一确定,所以参数的顺序可以任意排列。

4:计算某部门的工资总和:

image-20230927212511535

第三种参数传递格式称为混合表示法

即在调用一个函数时,同时使用位置表示法和名称表示法为函数传递参数。采用这种参数传递方法时,使用位置表示法所传递的参数必须放在名称表示法所传递的参数前面。也就是说,无论函数具有多少个参数,只要其中有一个参数使用名称表示法,其后所有的参数都必须使用名称表示法。

5

image-20230927212517053

无论采用哪一种参数传递方法,实际参数和形式参数之间的数据传递只有两种方法:传址法和传值法。所谓传址法是指在调用函数时,将实际参数的地址指针传递给形式参数使形式参数和实际参数指向内存中的同一区域,从而实现参数数据的传递。这种方法又称作参照法,即形式参数参照实际参数数据。输入参数均采用传址法传递数据

传值法是指将实际参数的数据拷贝到形式参数,而不是传递实际参数的地址。默认时,输出参数和输入**/输出参数均采用传值法。在函数调用时,ORACLE 将实际参数数据拷贝到输入/输出参数,而当函数正常运行退出时,又将输出形式参数和输入/输出形式参数数据拷贝到实际参数变量中。

**3.**参数默认值

在CREATE OR REPLACE FUNCTION 语句中声明函数参数时可以使用DEFAULT关键字为输入参数指定默认值**。

例 6:

image-20230927212523196

具有默认值的函数创建后,在函数调用时,如果没有为具有默认值的参数提供实际参数值,函数将使用该参数的默认值。但当调用者为默认参数提供实际参数时,函数将使用实际参数值。在创建函数时,能为输入参数设置默认值,而不能为输入**/**输出参数设置默认值。

image-20230927212526883

自定义函数哪怕只是返回单一值,但是被调用多次时会非常慢,可以增加函数索引

return integer deterministic is

存储过程

创建过程

建立存储过程

在 ORACLE SERVER 上建立存储过程,可以被多个应用程序调用,可以向存储过程传递参数,也可以向存储过程传回参数.

创建过程语法:

1
2
3
4
5
6
7
8
9
10
11
CREATE [OR REPLACE] PROCEDURE Procedure_name
[ (argment [ { IN | IN OUT }] Type,
argment [ { IN | OUT | IN OUT } ] Type ]
[ AUTHID DEFINER | CURRENT_USER ]
{ IS | AS }
<类型.变量的说明>
BEGIN
<执行部分>
EXCEPTION
<可选的异常错误处理程序>
END;

7.删除指定员工记录;

image-20230927212532527

8.插入员工记录;

image-20230927212538812

调用存储过程

ORACLE 使用 EXECUTE 语句来实现对存储过程的调用:

1
EXEC[UTE] Procedure_name( parameter1, parameter2…);

9:**查询指定员工记录;

image-20230927212542945

调用方法:

image-20230927212547214

例 10.计算指定部门的工资总和,并统计其中的职工数量。

image-20230927212551213

调用方法:

image-20230927212555221

AUTHID

在创建存储过程时, 可使用 AUTHID CURRENT_USER 或 AUTHID DEFINER 选项,以表明在执行该过程时Oracle 使用的权限.

1) 如果使用 AUTHID CURRENT_USER 选项创建一个过程, 则 Oracle 用调用该过程的用户权限执行该过程**. 为了成功执行该过程, 调用必须具有访问该存储过程体中引用的所有数据库对象**所必须的权限

2) 如果用默认的 AUTHID DEFINER 选项创建过程, 则 Oracle 使用过程所有者的特权执行该过程.**为了成功执行该过程, **过程的所有必须具有访问该存储过程体中引用的所有数据库对象所须的权限. 想要简化应用程序用户的特权管理, 在创建存储过程时, 一般选择 AUTHID DEFINER选项 –-- 这样就不必授权给需要调用的此过程的所有用户了.

开发存储过程步骤

开发存储过程、函数、包及触发器的步骤如下:

使用文字编辑处理软件编辑存储过程源码

使用文字编辑处理软件编辑存储过程源码,,需将源码存为文本格式。

SQLPLUS 或用调试工具将存储过程程序进行解释

在 SQLPLUS 或用调试工具将存储过程程序进行解释;

在 SQL>下调试,可用 START 或 GET 等 ORACLE 命令来启动解释。如:

1
SQL>START c:\stat1.sql
调试源码直到正确

我们不能保证所写的存储过程达到一次就正确。所以这里的调式是每个程序员必须进行的工作之一。

在 SQLPLUS 下来调式主要用的方法是:

  • 使用SHOW ERROR命令来提示源码的错误位置
  • 使用user_errors数据字典来查看各存储过程的错误位置。
授权执行权给相关的用户或角色

如果调式正确的存储过程没有进行授权,那就只有建立者本人才可以运行。所以作为应用系统的一部分的存储过程也必须进行授权才能达到要求。在 SQLPLUS 下可以用 GRANT 命令来进行存储过程的运行授权。

1
GRANT EXECUTE ON dbms_job TO PUBLIC WITH GRANT OPTION
与过程相关数据字典

USER_SOURCE, ALL_SOURCE, DBA_SOURCE, USER_ERRORS

相关的权限:

1
2
CREATE ANY PROCEDURE
DROP ANY PROCEDURE

在 SQLPLUS 中,可以用 DESCRIBE 命令查看过程的名字及其参数表

1
DROP PROCEDURE [user.]Procudure_name;

删除过程和函数

1.删除过程

可以使用 DROP PROCEDURE 命令对不需要的过程进行删除,语法如下:

DROP PROCEDURE [user.]Procudure_name;

2.删除函数

可以使用 DROP FUNCTION 命令对不需要的函数进行删除,语法如下:

1
DROP FUNCTION [user.]Function_name;

包的创建和应用

引言

包是一组相关过程、函数、变量、常量和游标等 PL/SQL 程序设计元素的组合,它具有面向对象程序设计语言的特点,是对这些 PL/SQL 程序设计元素的封装。包类似于 C++和 JAVA语言中的类,其中变量相当于类中的成员变量,过程和函数相当于类方法。把相关的模块归类成为包,可使开发人员利用面向对象的方法进行存储过程的开发,从而提高系统性能。
与类相同,包中的程序元素也分为公用元素和私用元素两种,这两种元素的区别是他们允许访问的程序范围不同,即它们的作用域不同。公用元素不仅可以被包中的函数、过程所调用,也可以被包外的 PL/SQL程序访问,而私有元素只能被包内的函数和过程序所访问。

在 PL/SQL 程序设计中,使用包不仅可以使程序设计模块化,对外隐藏包内所使用的信息(通过使用私用变量),而且可以提高程序的执行效率。因为,当程序首次调用包内函数或过程时,ORACLE 将整个包调入内存,当再次访问包内元素时,ORACLE 直接从内存中读取,而不需要进行磁盘 I/O 操作,从而使程序执行效率得到提高。

一个包由两个分开的部分组成:

  • 包定义(PACKAGE):包定义部分声明包内数据类型、变量、常量、游标、子程序和异常错误处理等元素,这些元素为包的公有元素。

  • 包主体(PACKAGE BODY):包主体则是包定义部分的具体实现,它定义了包定义部分所声明的游标和子程序,在包主体中还可以声明包的私有元素。

包定义和包主体分开编译,并作为两部分分开的对象存放在数据库字典中,详见数据字典 user_source,all_source, dba_source.

包的定义

包定义的语法如下:

创建包定义:

1
2
3
4
5
6
7
8
CREATE [OR REPLACE] PACKAGE package_name
[AUTHID {CURRENT_USER | DEFINER}]
{IS | AS}
[公有数据类型定义[公有数据类型定义]…]
[公有游标声明[公有游标声明]…]
[公有变量、常量声明[公有变量、常量声明]…]
[公有子程序声明[公有子程序声明]…]
END [package_name];

其中:AUTHID CURRENT_USER和AUTHID DEFINER选项说明应用程序在调用函数时所使用的权限模式,它们与CREATE FUNCTION语句中invoker_right_clause子句的作用相同。

创建包主体:

1
2
3
4
5
6
7
8
9
10
CREATE [OR REPLACE] PACKAGE BODY package_name
{IS | AS}
[私有数据类型定义[私有数据类型定义]…]
[私有变量、常量声明[私有变量、常量声明]…]
[私有子程序声明和定义[私有子程序声明和定义]…]
[公有游标定义[公有游标定义]…]
[公有子程序定义[公有子程序定义]…]
BEGIN
PL/SQL 语句
END [package_name];

其中:在包主体定义公有程序时,它们必须与包定义中所声明子程序的格式完全一致

包的开发步骤

与开发存储过程类似,包的开发需要几个步骤:

1. 将每个存储过程调式正确;

2. 用文本编辑软件将各个存储过程和函数集成在一起;

3. 按照包的定义要求将集成的文本的前面加上包定义;

4. 按照包的定义要求将集成的文本的前面加上包主体;

5. 使用 SQLPLUS 或开发工具进行调式。

包定义的说明

**1:**创建的包为 demo_pack, 该包中包含一个记录变量 DeptRec、两个函数和一个过程。

1
2
3
4
5
6
7
8
9
10
CREATE OR REPLACE PACKAGE demo_pack
    IS
    DeptRec dept%ROWTYPE;
    FUNCTION add_dept(
        dept_no NUMBER, dept_name VARCHAR2, location VARCHAR2)
        RETURN NUMBER;
    FUNCTION remove_dept(dept_no NUMBER)
        RETURN NUMBER;
    PROCEDURE query_dept(dept_no IN NUMBER);
END demo_pack;

包主体的创建方法,它实现上面所声明的包定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
CREATE OR REPLACE PACKAGE BODY demo_pack
    IS
FUNCTION add_dept
    (dept_no NUMBER, dept_name VARCHAR2, location VARCHAR2)
    RETURN NUMBER
    IS
    empno_remaining EXCEPTION;
    PRAGMA EXCEPTION_INIT(empno_remaining, -1);
    /* -1 是违反唯一约束条件的错误代码 */
BEGIN
    INSERT INTO dept VALUES(dept_no, dept_name, location);
    IF SQL%FOUND THEN
        RETURN 1;
    END IF;
EXCEPTION
    WHEN empno_remaining THEN
        RETURN 0;
    WHEN OTHERS THEN
        RETURN -1;
END add_dept;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
FUNCTION remove_dept(dept_no NUMBER)
    RETURN NUMBER
    IS
BEGIN
    DELETE FROM dept WHERE deptno=dept_no;
    IF SQL%FOUND THEN
        RETURN 1;
    ELSE
        RETURN 0;
    END IF;
EXCEPTION
    WHEN OTHERS THEN
        RETURN -1;
END remove_dept;
1
2
3
4
5
6
7
8
9
10
11
12
13
PROCEDURE query_dept
    (dept_no IN NUMBER)
    IS
BEGIN
    SELECT * INTO DeptRec FROM dept WHERE deptno=dept_no;
EXCEPTION
    WHEN NO_DATA_FOUND THEN
        DBMS_OUTPUT.PUT_LINE('数据库中没有编码为'||dept_no||'的部门');
    WHEN TOO_MANY_ROWS THEN
        DBMS_OUTPUT.PUT_LINE('程序运行错误!请使用游标');
    WHEN OTHERS THEN
        DBMS_OUTPUT.PUT_LINE(SQLCODE||’----‘||SQLERRM);
END query_dept;
1
2
3
BEGIN
    Null;
END demo_pack;

对包内共有元素的调用格式为:包名.元素名称

调用 demo_pack 包内函数对 dept 表进行插入、查询和修改操作,并通过 demo_pack 包中的记录变量 DeptRec

显示所查询到的数据库信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
DECLARE
    Var NUMBER;
BEGIN
    Var := demo_pack.add_dept(90,’Administration’, ‘Beijing’);
    IF var =-1 THEN
        DBMS_OUTPUT.PUT_LINE(SQLCODE||’----‘||SQLERRM);
    ELSIF var =0 THEN
        DBMS_OUTPUT.PUT_LINE(‘该部门记录已经存在!’);
    ELSE
        DBMS_OUTPUT.PUT_LINE(‘添加记录成功!’);
        Demo_pack.query_dept(90);
        DBMS_OUTPUT.PUT_LINE(demo_pack.DeptRec.deptno||’---‘||
            demo_pack.DeptRec.dname||’---‘||demo_pack.DeptRec.loc);
        var := demo_pack.remove_dept(90);
        IF var =-1 THEN
            DBMS_OUTPUT.PUT_LINE(SQLCODE||’----‘||SQLERRM);
        ELSIF var=0 THEN
            DBMS_OUTPUT.PUT_LINE(‘该部门记录不存在!’);
        ELSE
            DBMS_OUTPUT.PUT_LINE(‘删除记录成功!’);
        END IF;
    END IF;
END;

2: 创建包 emp_package

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
CREATE OR REPLACE PACKAGE emp_package
    IS
    TYPE emp_table_type IS TABLE OF emp%ROWTYPE
        INDEX BY BINARY_INTEGER;
    PROCEDURE read_emp_table (p_emp_table OUT emp_table_type);
END emp_package;
CREATE OR REPLACE PACKAGE BODY emp_package
    IS
    PROCEDURE read_emp_table (p_emp_table OUT emp_table_type)
    IS
    I BINARY_INTEGER := 0;
    BEGIN
        FOR emp_record IN ( SELECT * FROM emp ) LOOP
            P_emp_table(i) := emp_record;
            I := I + 1;
        END LOOP;
    END read_emp_table;
END emp_package;
1
2
3
4
5
6
7
8
DECLARE
    E_table emp_package.emp_table_type;
BEGIN
    Emp_package.read_emp_table(e_table);
    FOR I IN e_table.FIRST .. e_table.LAST LOOP
        DBMS_OUTPUT.PUT_LINE(e_table(i).empno||’ ‘||e_table(i).ename);
    END LOOP;
END;

3: 创建包 emp_mgmt:

1
2
3
4
5
6
7
8
9
CREATE SEQUENCE empseq
    START WITH 1000
    INCREMENT BY 1
    ORDER NOCYCLE;

CREATE SEQUENCE deptseq
    START WITH 50
    INCREMENT BY 10
    ORDER NOCYCLE;
1
2
3
4
5
6
7
8
9
10
11
12
CREATE OR REPLACE PACKAGE emp_mgmt
    AS
    FUNCTION hire(ename VARCHAR2, job VARCHAR2, mgr NUMBER, sal NUMBER,
        comm NUMBER, deptno NUMBER)
        RETURN NUMBER;
    FUNCTION create_dept(dname VARCHAR2, loc VARCHAR2)
        RETURN NUMBER;
    PROCEDURE remove_emp(empno NUMBER);
    PROCEDURE remove_dept(deptno NUMBER);
    PROCEDURE increase_sal(empno NUMBER, sal_incr NUMBER);
    PROCEDURE increase_comm(empno NUMBER, comm_incr NUMBER);
END emp_mgmt;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
CREATE OR REPLACE PACKAGE BODY emp_mgmt
    AS
    tot_emps NUMBER;
    tot_depts NUMBER;
    no_sal EXCEPTION;
    no_comm EXCEPTION;
FUNCTION hire(ename VARCHAR2, job VARCHAR2, mgr NUMBER,
    sal NUMBER, comm NUMBER, deptno NUMBER)
    RETURN NUMBER IS
    new_empno NUMBER(4);
BEGIN
    SELECT empseq.NEXTVAL INTO new_empno FROM dual;
    INSERT INTO emp
    VALUES (new_empno, ename, job, mgr, sysdate, sal, comm, deptno);
    tot_emps:=tot_emps+1;
    RETURN(new_empno);
EXCEPTION
    WHEN OTHERS THEN
        DBMS_OUTPUT.PUT_LINE('发生其它错误!');
END hire;
1
2
3
4
5
6
7
8
9
10
11
12
FUNCTION create_dept(dname VARCHAR2, loc VARCHAR2)
    RETURN NUMBER IS
    new_deptno NUMBER(4);
BEGIN
    SELECT deptseq.NEXTVAL INTO new_deptno FROM dual;
    INSERT INTO dept VALUES (new_deptno, dname, loc);
    Tot_depts:=tot_depts;
    RETURN(new_deptno);
EXCEPTION
    WHEN OTHERS THEN
        DBMS_OUTPUT.PUT_LINE('发生其它错误!');
END create_dept;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
PROCEDURE remove_emp(empno NUMBER) IS
    No_result EXCEPTION;
BEGIN
    DELETE FROM emp WHERE emp.empno=remove_emp.empno;
    IF SQL%NOTFOUND THEN
        RAISE no_result;
    END IF;
    tot_emps:=tot_emps-1;
EXCEPTION
    WHEN no_result THEN
        DBMS_OUTPUT.PUT_LINE('你需要的数据不存在!');
    WHEN OTHERS THEN
        DBMS_OUTPUT.PUT_LINE('发生其它错误!');
END remove_emp;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
PROCEDURE remove_dept(deptno NUMBER) IS
    No_result EXCEPTION;
    e_deptno_remaining EXCEPTION;
    PRAGMA EXCEPTION_INIT(e_deptno_remaining, -2292);
    /* -2292 是违反一致性约束的错误代码 */
BEGIN
    DELETE FROM dept WHERE dept.deptno=remove_dept.deptno;
    IF SQL%NOTFOUND THEN
        RAISE no_result;
    END IF;
    Tot_depts:=tot_depts-1;
EXCEPTION
    WHEN no_result THEN
        DBMS_OUTPUT.PUT_LINE('你需要的数据不存在!');
    WHEN e_deptno_remaining THEN
        DBMS_OUTPUT.PUT_LINE('违反数据完整性约束!');
    WHEN OTHERS THEN
        DBMS_OUTPUT.PUT_LINE('发生其它错误!');
END remove_dept;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
PROCEDURE increase_sal(empno NUMBER, sal_incr NUMBER) IS
    curr_sal NUMBER(7, 2);
BEGIN
    SELECT sal INTO curr_sal FROM emp WHERE emp.empno=increase_sal.empno;
    IF curr_sal IS NULL THEN
        RAISE no_sal;
    ELSE
        UPDATE emp SET sal=sal+increase_sal.sal_incr
        WHERE emp.empno=increase_sal.empno;
    END IF;
EXCEPTION
    WHEN NO_DATA_FOUND THEN
        DBMS_OUTPUT.PUT_LINE('你需要的数据不存在!');
    WHEN no_sal THEN
        DBMS_OUTPUT.PUT_LINE('此员工的工资不存在!');
    WHEN OTHERS THEN
        DBMS_OUTPUT.PUT_LINE('发生其它错误!');
END increase_sal;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
PROCEDURE increase_comm(empno NUMBER, comm_incr NUMBER) IS
    curr_comm NUMBER(7,2);
BEGIN
    SELECT comm INTO curr_comm
    FROM emp WHERE emp.empno=increase_comm.empno;
    IF curr_comm IS NULL THEN
        RAISE no_comm;
    ELSE
        UPDATE emp SET comm=comm+increase_comm.comm_incr
        WHERE emp.empno=increase_comm.empno;
    END IF;
EXCEPTION
    WHEN NO_DATA_FOUND THEN
        DBMS_OUTPUT.PUT_LINE('你需要的数据不存在!');
    WHEN no_comm THEN
        DBMS_OUTPUT.PUT_LINE('此员工的奖金不存在!');
    WHEN OTHERS THEN
        DBMS_OUTPUT.PUT_LINE('发生其它错误!');
END increase_comm;

END EMP_MGMT;

4:利用游标变量创建包 Curvarpack。由于游标变量指是一个指针,其状态是不确定的,因此它不能随同包存储在数据库中,既不能在 PL/SQL 包中声明游标变量。但在包中可以创建游标变量参照类型,并可向包中的子程序传递游标变量参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
CREATE OR REPLACE PACKAGE CurVarPack AS
    TYPE DeptCurType IS REF CURSOR RETURN dept%ROWTYPE; --强类型定义
    TYPE CurType IS REF CURSOR;-- 弱类型定义

    PROCEDURE OpenDeptVar(
        Cv IN OUT DeptCurType,
        Choice INTEGER DEFAULT 0,
        Dept_no NUMBER DEFAULT 50,
        Dept_name VARCHAR DEFAULT ‘%’);
    END;

    CREATE OR REPLACE PACKAGE BODY CurVarPack AS
        PROCEDURE OpenDeptvar(
            Cv IN OUT DeptCurType,
            Choice INTEGER DEFAULT 0,
            Dept_no NUMBER DEFAULT 50,
            Dept_name VARCHAR DEFAULT ‘%’)
    IS
    BEGIN
        IF choice =1 THEN
            OPEN cv FOR SELECT * FROM dept WHERE deptno <= dept_no;
        ELSIF choice = 2 THEN
            OPEN cv FOR SELECT * FROM dept WHERE dname LIKE dept_name;
        ELSE
            OPEN cv FOR SELECT * FROM dept;
        END IF;
    END OpenDeptvar;
END CurVarPack;

--定义一个过程
CREATE OR REPLACE PROCEDURE OpenCurType(
    Cv IN OUT CurVarPack.CurType,
    Tab CHAR)
    AS
BEGIN
--由于 CurVarPack.CurType 采用弱类型定义
--所以可以使用它定义的游标变量打开不同类型的查询语句
    IF tab = ‘D’ THEN
        OPEN cv FOR SELECT * FROM dept;
    ELSE
        OPEN cv FOR SELECT * FROM emp;
    END IF;
END OpenCurType;

--定义一个应用
DECLARE
    DeptRec Dept%ROWTYPE;
    EmpRec Emp%ROWTYPE;
    Cv1 Curvarpack.deptcurtype;
    Cv2 Curvarpack.curtype;
BEGIN
    DBMS_OUTPUT.PUT_LINE(’游标变量强类型定义应用’);
    Curvarpack.OpenDeptVar(cv1, 1, 30);
    FETCH cv1 INTO DeptRec;
    WHILE cv1%FOUND LOOP
        DBMS_OUTPUT.PUT_LINE(DeptRec.deptno||’:’||DeptRec.dname);
    FETCH cv1 INTO DeptRec;
    END LOOP;
    CLOSE cv1;
    DBMS_OUTPUT.PUT_LINE(’游标变量弱类型定义应用’);
    CurVarPack.OpenDeptvar(cv2, 2, dept_name => ‘A%’);
    FETCH cv2 INTO DeptRec;
    WHILE cv2%FOUND LOOP
        DBMS_OUTPUT.PUT_LINE(DeptRec.deptno||’:’||DeptRec.dname);
    FETCH cv2 INTO DeptRec;
    END LOOP;
    DBMS_OUTPUT.PUT_LINE(’游标变量弱类型定义应用—dept 表’);
    OpenCurtype(cv2, ‘D’);
    FETCH cv2 INTO DeptRec;
    WHILE cv2%FOUND LOOP
        DBMS_OUTPUT.PUT_LINE(deptrec.deptno||’:’||deptrec.dname);
    FETCH cv2 INTO deptrec;
    END LOOP;
    DBMS_OUTPUT.PUT_LINE(’游标变量弱类型定义应用—emp 表’);
    OpenCurtype(cv2, ‘E’);
    FETCH cv2 INTO EmpRec;
    WHILE cv2%FOUND LOOP
        DBMS_OUTPUT.PUT_LINE(emprec.empno||’:’||emprec.ename);
    FETCH cv2 INTO emprec;
    END LOOP;
    CLOSE cv2;
END;

子程序重载

PL/SQL 允许对包内子程序和本地子程序进行重载。所谓重载时指两个或多个子程序有相同的名称,但拥有不同的参数变量、参数顺序或参数数据类型。

5

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
CREATE OR REPLACE PACKAGE demo_pack1
IS
DeptRec dept%ROWTYPE;
V_sqlcode NUMBER;
V_sqlerr VARCHAR2(2048);
FUNCTION query_dept(dept_no IN NUMBER)
RETURN INTEGER;
FUNCTION query_dept(dept_no IN VARCHAR2)
RETURN INTEGER;
END demo_pack1;
CREATE OR REPLACE PACKAGE BODY demo_pack1
IS
FUNCTION check_dept(dept_no NUMBER)
RETURN INTEGER
IS
Flag INTEGER;
BEGIN
SELECT COUNT(*) INTO flag FROM dept WHERE deptno=dept_no;
IF flag > 0 THEN
RETURN 1;
ELSE
RETURN 0;
END IF;
END check_dept;
FUNCTION check_dept(dept_no VARCHAR2)
RETURN INTEGER
IS
Flag INTEGER;
BEGIN
SELECT COUNT(*) INTO flag FROM dept WHERE deptno=dept_no;
IF flag > 0 THEN
RETURN 1;
ELSE
RETURN 0;
END IF;
END check_dept;
FUNCTION query_dept(dept_no IN NUMBER)
RETURN INTEGER
IS
BEGIN
IF check_dept(dept_no) =1 THEN
SELECT * INTO DeptRec FROM dept WHERE deptno=dept_no;
RETURN 1;
ELSE
RETURN 0;
END IF;
END query_dept;
FUNCTION query_dept(dept_no IN VARCHAR2)
RETURN INTEGER
IS
BEGIN
IF check_dept(dept_no) =1 THEN
SELECT * INTO DeptRec FROM dept WHERE deptno=dept_no;
RETURN 1;
ELSE
RETURN 0;
END IF;
END query_dept;
END demo_pack1;

删除

可以使用 DROP PACKAGE 命令对不需要的包进行删除,语法如下:

1
2
3
4
5
DROP PACKAGE [BODY] [user.]package_name;
DROP PACKAGE demo_pack;
DROP PACKAGE demo_pack1;
DROP PACKAGE emp_mgmt;
DROP PACKAGE emp_package;

包的管理

1
DBA_SOURCE, USER_SOURCE, USER_ERRORS, DBA-OBJECTS

触发器

触发器是许多关系数据库系统都提供的一项技术。在 ORACLE 系统里,触发器类似过程和函数,都有声明,执行和异常处理过程的** PL/SQL

触发器类型

触发器在数据库里以独立的对象存储,它与存储过程不同的是,存储过程通过其它程序来启动运行或直接启动运行,而触发器是由一个事件来启动运行即触发器是当某个事件发生时自动地隐式运行。并且,触发器不能接收参数。所以运行触发器就叫触发或点火(firingORACLE 事件指的是对数据库的表进行的INSERTUPDATE** DELETE 操作或对视图进行类似的操作。ORACLE 将触发器的功能扩展到了触发ORACLE,如数据库的启动与关闭等。

DML 触发器

ORACLE 可以在 DML 语句进行触发,可以在 DML 操作前或操作后进行触发,并且可以对每个行或语句操作上进行触发**。

替代触发器

由于在 ORACLE 里,不能直接对由两个以上的表建立的视图进行操作。所以给出了替代触发器。

系统触发器

它可以在 ORACLE 数据库系统的事件中进行触发,如 ORACLE 系统的启动与关闭等。

触发器组成:

  • 触发事件:即在何种情况下触发TRIGGER;例如:INSERT, UPDATE, DELETE。
  • 触发时间:即该TRIGGER是在触发事件发生之前(BEFORE)还是之后(AFTER)触发,也就是触发事件和
  • 触发器本身:即该TRIGGER被触发之后的目的和意图,正是触发器本身要做的事情。 例如:PL/SQL块。
  • 触发频率:说明触发器内定义的动作被执行的次数。即语句级(STATEMENT)触发器和行级(ROW)触发器

创建触发器

创建触发器的一般语法是:

1
2
3
4
5
6
7
CREATE [OR REPLACE] TRIGGER trigger_name
{BEFORE | AFTER }
{INSERT | DELETE | UPDATE [OF column [, column …]]}
ON [schema.] table_name
[FOR EACH ROW ]
[WHEN condition]
trigger_body;

其中:

BEFORE 和 AFTER 指出触发器的触发时序分别为前触发和后触发方式,前触发是在执行触发事件之前触发当前所创建的触发器,后触发是在执行触发事件之后触发当前所创建的触发器。

FOR EACH ROW 选项说明触发器为行触发器。行触发器和语句触发器的区别表现在:行触发器要求当一个 DML 语句操做影响数据库中的多行数据时,对于其中的每个数据行,只要它们符合触发约束条件,均激活一次触发器;而语句触发器将整个语句操作作为触发事件,当它符合约束条件时,激活一次触发器。省略** FOR EACH ROW 选项时BEFOREAFTER 触发器为语句触发器,而 INSTEAD OF 触发器则为行触发器。

WHEN 子句说明触发约束条件。Condition 为一个逻辑表达时,其中必须包含相关名称,而不能包含查询语句,也不能调用 PL/SQL 函数。WHEN 子句指定的触发约束条件只能用在 BEFORE 和 AFTER 行触发器中,不能用在 INSTEAD OF 行触发器和其它类型的触发器中。

当一个基表被修改( INSERT, UPDATE, DELETE)时要执行的存储过程,执行时根据其所依附的基表改动而自动触发,因此与应用程序无关,用数据库触发器可以保证数据的一致性和完整性。

每张表最多可建立 12 种类型的触发器,它们是:

1
2
3
4
5
6
7
8
9
10
11
12
BEFORE INSERT
BEFORE INSERT FOR EACH ROW
AFTER INSERT
AFTER INSERT FOR EACH ROW
BEFORE UPDATE
BEFORE UPDATE FOR EACH ROW
AFTER UPDATE
AFTER UPDATE FOR EACH ROW
BEFORE DELETE
BEFORE DELETE FOR EACH ROW
AFTER DELETE
AFTER DELETE FOR EACH ROW

触发器触发次序

  1. 执行 BEFORE 语句级触发器;

  2. 对与受语句影响的每一行:

  • 执行BEFORE行级触发器
  • 执行DML语句
  • 执行AFTER行级触发器
  1. 执行 AFTER 语句级触发器

创建 DML 触发器

触发器名可以和表或过程有相同的名字,但在一个模式中触发器名不能相同。

触发器的限制

  • CREATE TRIGGER语句文本的字符长度不能超过32KB;
  • 触发器体内的SELECT语句只能为SELECT … INTO …结构,或者为定义游标所使用的SELECT语句
  • 触发器中不能使用数据库事务控制语句COMMIT; ROLLBACK, SVAEPOINT语句;
  • 由触发器所调用的过程或函数也不能使用数据库事务控制语句;

问题:当触发器被触发时,要使用被插入、更新或删除的记录中的列值,有时要使用操作前、 后列的值.

实现: :NEW 修饰符访问操作完成后列的值

:OLD 修饰符访问操作完成前列的值

image-20230927212829153

1: 建立一个触发器, 当职工表 emp 表被删除一条记录时,把被删除记录写到职工表删除日志表中去。

image-20230927212610951

image-20230927212615044

创建替代(INSTEAD OF)触发器

创建触发器的一般语法是:

1
2
3
4
5
6
7
CREATE [OR REPLACE] TRIGGER trigger_name
INSTEAD OF
{INSERT | DELETE | UPDATE [OF column [, column …]]}
ON [schema.] view_name
[FOR EACH ROW ]
[WHEN condition]
trigger_body;

其中:

BEFORE 和 AFTER 指出触发器的触发时序分别为前触发和后触发方式,前触发是在执行触发事件之前触发当前所创建的触发器,后触发是在执行触发事件之后触发当前所创建的触发器。

INSTEAD OF 选项使 ORACLE 激活触发器,而不执行触发事件。只能对视图和对象视图建立 INSTEAD OF触发器**,而不能对表、模式和数据库建立 INSTEAD OF 触发器。

FOR EACH ROW 选项说明触发器为行触发器。行触发器和语句触发器的区别表现在:行触发器要求当一个 DML 语句操做影响数据库中的多行数据时,对于其中的每个数据行,只要它们符合触发约束条件,均激活一次触发器;而语句触发器将整个语句操作作为触发事件,当它符合约束条件时,激活一次触发器。当省略 FOR EACH ROW 选项时,BEFORE 和 AFTER 触发器为语句触发器,而 INSTEAD OF 触发器则为行触发器。

WHEN 子句说明触发约束条件。Condition 为一个逻辑表达时,其中必须包含相关名称,而不能包含查询语句,也不能调用 PL/SQL 函数。WHEN 子句指定的触发约束条件只能用在 BEFORE 和 AFTER 行触发器中,不能用在 INSTEAD OF 行触发器和其它类型的触发器中。

INSTEAD_OF 用于对视图的 DML 触发,由于视图有可能是由多个表进行联结(join)而成,因而并非是所有的联结都是可更新的。但可以按照所需的方式执行更新,例如下面情况:

1
2
3
CREATE OR REPLACE VIEW emp_view AS
SELECT deptno, count(*) total_employeer, sum(sal) total_salary
FROM emp GROUP BY deptno;

在此视图中直接删除是非法:

1
2
3
4
5
SQL>DELETE FROM emp_view WHERE deptno=10;
DELETE FROM emp_view WHERE deptno=10
*
ERROR 位于第 1 行:
ORA-01732: 此视图的数据操纵操作非法

但是可以创建 INSTEAD_OF 触发器来为 DELETE 操作执行所需的处理,即删除 EMP 表中所有基准行:

1
2
3
4
5
6
7
8
CREATE OR REPLACE TRIGGER emp_view_delete
INSTEAD OF DELETE ON emp_view FOR EACH ROW
BEGIN
DELETE FROM emp WHERE deptno= :old.deptno;
END emp_view_delete;
DELETE FROM emp_view WHERE deptno=10;
DROP TRIGGER emp_view_delete;
DROP VIEW emp_view;

创建系统事件触发器

ORACLE 提供的系统事件触发器可以在 DDL 或数据库系统上被触发。DDL 指的是数据定义语言,如CREATE 、ALTER 及 DROP 等。而数据库系统事件包括数据库服务器的启动或关闭,用户的登录与退出、数据库服务错误等。创建系统触发器的语法如下:

创建触发器的一般语法是:**

1
2
3
4
5
6
CREATE OR REPLACE TRIGGER [sachema.] trigger_name
{BEFORE|AFTER}
{ddl_event_list | database_event_list}
ON { DATABASE | [schema.] SCHEMA }
[WHEN_clause]
trigger_body;

其中: ddl_event_list:一个或多个 DDL 事件,事件间用 OR 分开;

database_event_list:一个或多个数据库事件,事件间用 OR 分开;

系统事件触发器既可以建立在一个模式上,又可以建立在整个数据库上。当建立在模式(SCHEMA)之上时,只有模式所指定用户的 DDL 操作和它们所导致的错误才激活触发器, 默认时为当前用户模式。当建立在数据库(DATABASE)之上时,该数据库所有用户的 DDL 操作和他们所导致的错误,以及数据库的启动和关闭均可激活触发器。要在数据库之上建立触发器时,要求用户具有 ADMINISTER DATABASE TRIGGER 权限。

下面给出系统触发器的种类和事件出现的时机(前或后):

image-20230927212620200

系统触发器事件属性

image-20230927212625625

除 DML 语句的列属性外,其余事件属性值可通过调用 ORACLE 定义的事件属性函数来读取。

image-20230927212630135

使用触发器谓词

ORACLE 提供三个参数 INSERTING, UPDATING, DELETING 用于判断触发了哪些操作。

image-20230927212633807

重新编译触发器

如果在触发器内调用其它函数或过程,当这些函数或过程被删除或修改后,触发器的状态将被标识为无效。当 DML 语句激活一个无效触发器时,ORACLE 将重新编译触发器代码,如果编译时发现错误,这将导致 DML 语句执行失败。

在 PL/SQL 程序中可以调用 ALTER TRIGGER 语句重新编译已经创建的触发器,格式为:

ALTER TRIGGER [schema.] trigger_name COMPILE [ DEBUG]

其中:DEBUG 选项要器编译器生成 PL/SQL 程序条使其所使用的调试代码。

删除和使能触发器

  • 删除触发器
1
DROP TRIGGER trigger_name;

当删除其他用户模式中的触发器名称,需要具有 DROP ANY TRIGGER 系统权限,当删除建立在数据库上的触发器时,用户需要具有 ADMINISTER DATABASE TRIGGER 系统权限。

此外,当删除表或视图时,建立在这些对象上的触发器也随之删除

  • 触发器的状态

数据库 TRIGGER 的状态:

有效状态(ENABLE):当触发事件发生时,处于有效状态的数据库触发器 TRIGGER 将被触发。

无效状态(DISABLE):当触发事件发生时,处于无效状态的数据库触发器 TRIGGER 将不会被触发,此时就跟没有这个数据库触发器(TRIGGER) 一样。

数据库 TRIGGER 的这两种状态可以互相转换。格式为:

1
ALTER TIGGER trigger_name [DISABLE | ENABLE ];

例:ALTER TRIGGER emp_view_delete DISABLE;

ALTER TRIGGER 语句一次只能改变一个触发器的状态,而 ALTER TABLE 语句则一次能够改变与指定表相关的所有触发器的使用状态。格式为:

1
ALTER TABLE [schema.]table_name {ENABLE|DISABLE} ALL TRIGGERS;

例:使表 EMP 上的所有 TRIGGER 失效:

ALTER TABLE emp DISABLE ALL TRIGGERS;

SQL练习

准备工作

员工表结构

image-20230927212638510

练习oracle语句前置步骤

1
2
3
4
5
6
7
将3个sql拷贝到d盘下 -> pqsql develop -> new -> command window -> 顺序执行以下命令
@d:/del_data.sql;
@d:/hr_cre.sql;
@d:/hr_popul.sql;

=> 不用管视图不存在/权限不足等提示
=> 验证: select * from employees; -> 如果有107条记录,则初始化脚本成功

SQL练习

基本SQL-SELECT语句

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/*************************************************************************************************/
                                   第 1 章  基本SQL-SELECT语句
/*************************************************************************************************/
1. 对于日期型数据, 做 *, / 运算不合法

2. 包含空值的数学表达式的值都为空值

3. 别名使用双引号!

4. oracle 中连接字符串使用 "||", 而不是 java 中的 "+"

5. 日期和字符只能在单引号中出现. 输出 last_name`s email is email

select last_name || ' `s email is ' || email EMAIL
from employees

6. distinct 关键字, 以下语法错误

select last_name, distinct department_id
from employees

过滤和排序数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
/*************************************************************************************************/
                              第 2 章  过滤和排序数据
/*************************************************************************************************/
7. WHERE 子句紧随 FROM 子句

8. 查询 last_name 为 'King' 的员工信息

错误1: King 没有加上 单引号

select first_name, last_name
from employees
where last_name = King

错误2: 在单引号中的值区分大小写

select first_name, last_name
from employees
where last_name = 'king'

正确

select first_name, last_name
from employees
where last_name = 'King'

9. 查询 1998-4-24 来公司的员工有哪些?

注意: 日期必须要放在单引号中, 且必须是指定的格式

select last_name, hire_date
from employees
where hire_date = '24-4月-1998'

10. 查询工资在 5000 -- 10000 之间的员工信息.
    
    1). 使用 AND
    select *
    from employees
    where salary >= 5000 and salary <= 10000
    
    2). 使用 BETWEEN .. AND ..,  注意: 包含边界!!
    select *
    from employees
    where salary between 5000 and 10000

11. 查询工资等于 6000, 7000, 8000, 9000, 10000 的员工信息
    
    1). 使用 OR
    select *
    from employees
    where salary = 6000 or salary = 7000 or salary = 8000 or salary = 9000 or salary = 10000
    
    2). 使用 IN
    select *
    from employees
    where salary in (6000, 7000, 8000, 9000, 10000)

12. 查询 LAST_NAME 中有 'o' 字符的所有员工信息.
    
    select *
    from employees
    where last_name like '%o%'
    
13. 查询 LAST_NAME 中第二个字符是 'o' 的所有员工信息.

    select *
    from employees
    where last_name like '_o%'
    
14. 查询 LAST_NAME 中含有 '_' 字符的所有员工信息
    
    1). 准备工作:
    update employees
    set last_name = 'Jones_Tom'
    where employee_id = 195
    
    2). 使用 escape 说明转义字符.
    select *
    from employees
    where last_name like '%\_%' escape '\'

15. 查询 COMMISSION_PCT 字段为空的所有员工信息
    select last_name, commission_pct
    from employees
    where commission_pct is null
    
16. 查询 COMMISSION_PCT 字段不为空的所有员工信息
    select last_name, commission_pct
    from employees
    where commission_pct is not null

17. ORDER BY:
    1). 若查询中有表达式运算, 一般使用别名排序
    2). 按多个列排序: 先按第一列排序, 若第一列中有相同的, 再按第二列排序.
    格式:  ORDER BY 一级排序列 ASC/DESC,二级排序列 ASC/DESC;    

单行函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
/*************************************************************************************************/
                              第 3 章  单行函数
/*************************************************************************************************/

18. 打印出 "2009年10月14日 9:25:40" 格式的当前系统的日期和时间.

    select to_char(sysdate, 'YYYY"年"MM"月"DD"日" HH:MI:SS')
    from dual    

    注意: 使用双引号向日期中添加字符

19. 格式化数字: 1234567.89 为 1,234,567.89

    select to_char(1234567.89, '999,999,999.99')
    from dual

20. 字符串转为数字时
    1). 若字符串中没有特殊字符, 可以进行隐式转换:
    select '1234567.89' + 100
    from dual

    2). 若字符串中有特殊字符, 例如 '1,234,567.89', 则无法进行隐式转换, 需要使用 to_number() 来完成

    select to_number('1,234,567.89', '999,999,999.99') + 100
    from dual

21. 对于把日期作为查询条件的查询, 一般都使用 to_date() 把一个字符串转为日期, 这样可以不必关注日期格式

    select last_name, hire_date
    from employees
    where hire_date = to_date('1998-5-23', 'yyyy-mm-dd')
--    where to_char(hire_date,'yyyy-mm-dd') = '1998-5-23'

22. 转换函数: to_char(), to_number(), to_date()

23. 查询每个月倒数第 2 天入职的员工的信息.

    select last_name, hire_date
    from employees
    where hire_date = last_day(hire_date) - 1

24. 计算公司员工的年薪

    --错误写法: 因为空值计算的结果还是空值
    select last_name, salary * 12 * (1 + commission_pct) year_sal
    from employees

    --正确写法
    select last_name, salary * 12 * (1 + nvl(commission_pct, 0)) year_sal
    from employees

25. 查询部门号为 10, 20, 30 的员工信息, 若部门号为 10, 则打印其工资的 1.1 倍, 20 号部门, 则打印其工资的 1.2 倍, 30 号部门打印其工资的 1.3 倍数

    --使用 case-when-then-else-end
    select last_name, department_id, salary, case department_id when 10  then salary * 1.1
                                                                when 20  then salary * 1.2
                                                                when 30  then salary * 1.3
                                                                end new_sal
    from employees
    where department_id in (10, 20, 30)

    --使用 decode
    select last_name, department_id, salary, decode(department_id, 10, salary * 1.1,
                                                                   20, salary * 1.2,
                                                                   30, salary * 1.3
                                                   ) new_sal
        from employees
        where department_id in (10, 20, 30)

多表查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
/*************************************************************************************************/
                              第 4 章  多表查询
/*************************************************************************************************/
26. 多表连接查询时, 若两个表有同名的列, 必须使用表的别名对列名进行引用, 否则出错!

27. 查询出公司员工的 last_name, department_name, city

    select last_name, department_name, city
    from departments d, employees e, locations l
    where d.department_id = e.department_id and d.location_id = l.location_id

28. 查询出 last_name 为 'Chen' 的 manager 的信息. (员工的 manager_id 是某员工的 employee_id)
    0). 例如: 老张的员工号为: "1001", 我的员工号为: "1002",
            我的 manager_id 为 "1001" --- 我的 manager 是"老张"
    1). 通过两条 sql 查询:
  
            select manager_id
            from employees
            where lower(last_name) = 'chen' --返回的结果为 108
            
            select *
            from employees
            where employee_id = 108
            
    2). 通过一条 sql 查询(自连接):
            
            select m.*
            from employees e, employees m
            where e.manager_id = m.employee_id and e.last_name = 'Chen'        
            
    3). 通过一条 sql 查询(子查询):    
            
            select *
            from employees
            where employee_id = (
                                  select manager_id
                                  from employees
                                  where last_name = 'Chen'
                                )    

29. 查询每个员工的 last_name 和 GRADE_LEVEL(在 JOB_GRADES 表中). ---- 非等值连接

            select last_name, salary, grade_level, lowest_sal, highest_sal
            from employees e, job_grades j
            where e.salary >= j.lowest_sal and e.salary <= j.highest_sal
            
30. 左外连接和右外连接

            select last_name, e.department_id, department_name
            from employees e, departments d
            where e.department_id = d.department_id(+)
            
            select last_name, d.department_id, department_name
            from employees e, departments d
            where e.department_id(+) = d.department_id
            
            理解 "(+)" 的位置: 以左外连接为例, 因为左表需要返回更多的记录,
            右表就需要 "加上" 更多的记录, 所以在右表的链接条件上加上 "(+)"
            
            注意: 1). 两边都加上 "(+)" 符号, 会发生语法错误!
                  2). 这种语法为 Oracle 所独有, 不能在其它数据库中使用.            
                  
31. SQL 99 连接 Employees 表和 Departments 表
            1).
            select *
            from employees join departments
            using(department_id)
            
            缺点: 要求两个表中必须有一样的列名.
            
            2).
            select *
            from employees e join departments d
            on e.department_id = d.department_id
            
            3).多表连接
            select e.last_name, d.department_name, l.city
            from employees e join departments d
            on e.department_id = d.department_id
            join locations l
            on d.location_id = l.location_id                 
            
32. SQL 99 的左外连接, 右外连接, 满外连接
            1).
            select last_name, department_name
            from employees e left outer join departments d
            on e.department_id = d.department_id
            
            2).
            select last_name, department_name
            from employees e right join departments d
            on e.department_id = d.department_id
            
            3).
            select last_name, department_name
            from employees e full join departments d
            on e.department_id = d.department_id    

分组函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
/*************************************************************************************************/
                              第 5 章  分组函数
/*************************************************************************************************/    
            
33. 查询 employees 表中有多少个部门

        select count(distinct department_id)
        from employees        
        
34. 查询全公司奖金基数的平均值(没有奖金的人按 0 计算)

        select avg(nvl(commission_pct, 0))
        from employees        
        
35. 查询各个部门的平均工资

        --错误: avg(salary) 返回公司平均工资, 只有一个值; 而 department_id 有多个值, 无法匹配返回
        select department_id, avg(salary)
        from employees                 
        
        **在 SELECT 列表中所有未包含在组函数中的列都应该包含在 GROUP BY 子句中
        
        --正确: 按 department_id 进行分组
        select department_id, avg(salary)
        from employees
        group by department_id
        
36. Toronto 这个城市的员工的平均工资
        
SELECT avg(salary)
FROM employees e JOIN departments d
ON e.department_id = d.department_id
JOIN locations l
ON d.location_id = l.location_id
WHERE city = 'Toronto'        

37. (有员工的城市)各个城市的平均工资
    
SELECT city, avg(salary)
FROM employees e JOIN departments d
ON e.department_id = d.department_id
JOIN locations l
ON d.location_id = l.location_id
GROUP BY city        

38. 查询平均工资高于 8000 的部门 id 和它的平均工资.        

SELECT department_id, avg(salary)
FROM employees e
GROUP BY department_id
HAVING avg(salary) > 8000        
        
39. 查询平均工资高于 6000 的 job_title 有哪些

SELECT job_title, avg(salary)
FROM employees e join jobs j
ON e.job_id = j.job_id
GROUP BY job_title
HAVING avg(salary) > 6000

子查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
/*************************************************************************************************/
                              第 6 章  子查询
/*************************************************************************************************/    
40. 谁的工资比 Abel 高?
        
        1). 写两条 SQL 语句.
        
        SELECT salary
        FROM employees
        WHERE last_name = 'Abel'
        
        --返回值为 11000
        
        SELECT last_name, salary
        FROM employees
        WHERE salary > 11000
        
        2). 使用子查询 -- 一条 SQL 语句
        
        SELECT last_name, salary
        FROM employees
        WHERE salary > (
            SELECT salary
            FROM employees
            WHERE last_name = 'Abel'
        )
        
子查询注意:
        
        1). 子查询要包含在括号内
        2). 将子查询放在比较条件的右侧    

41. 查询工资最低的员工信息: last_name, salary    

42. 查询平均工资最低的部门信息

43*. 查询平均工资最低的部门信息和该部门的平均工资

44. 查询平均工资最高的 job 信息

45. 查询平均工资高于公司平均工资的部门有哪些?

46. 查询出公司中所有 manager 的详细信息.

47. 各个部门中 最高工资中最低的那个部门的 最低工资是多少

48. 查询平均工资最高的部门的 manager 的详细信息: last_name, department_id, email, salary

49. 查询 1999 年来公司的人所有员工的最高工资的那个员工的信息.

/*************************************************************************************************/
        
41. 查询工资最低的员工信息: last_name, salary    

        SELECT last_name, salary
        FROM employees
        WHERE salary = (
            SELECT min(salary)
            FROM employees
        )

42. 查询平均工资最低的部门信息
        
        SELECT *
        FROM departments
        WHERE department_id = (
            SELECT department_id
            FROM employees
            GROUP BY department_id
            HAVING avg(salary) = (
                SELECT min(avg(salary))
                FROM employees
                GROUP BY department_id
            )
        )

43. 查询平均工资最低的部门信息和该部门的平均工资

select d.*, (select avg(salary) from employees where department_id = d.department_id)
from departments d
where d.department_id = (
      SELECT department_id
      FROM employees
      GROUP BY department_id
      HAVING avg(salary) = (
             SELECT min(avg(salary))
             FROM employees
             GROUP BY department_id
              )
      )
        
44. 查询平均工资最高的 job 信息

    1). 按 job_id 分组, 查询最高的平均工资    
    SELECT max(avg(salary))
    FROM employees
    GROUP BY job_id
    
    2). 查询出平均工资等于 1) 的 job_id
    SELECT job_id
    FROM employees
    GROUP BY job_id
    HAVING avg(salary) = (
        SELECT max(avg(salary))
        FROM employees
        GROUP BY job_id
    )
    
    3). 查询出 2) 对应的 job 信息
    SELECT *
    FROM jobs
    WHERE job_id = (
        SELECT job_id
        FROM employees
        GROUP BY job_id
        HAVING avg(salary) = (
            SELECT max(avg(salary))
            FROM employees
            GROUP BY job_id
        )
    )

45. 查询平均工资高于公司平均工资的部门有哪些?

    1). 查询出公司的平均工资
    SELECT avg(salary)
    FROM employees
    
    2). 查询平均工资高于 1) 的部门 ID
    SELECT department_id
    FROM employees
    GROUP BY department_id
    HAVING avg(salary) > (
        SELECT avg(salary)
        FROM employees
    )
    

46. 查询出公司中所有 manager 的详细信息.
    1). 查询出所有的 manager_id
    SELECT distinct manager_id
    FROM employeess
    
    2). 查询出 employee_id 为 1) 查询结果的那些员工的信息
    SELECT employee_id, last_name
    FROM employees
    WHERE employee_id in (
        SELECT distinct manager_id
        FROM employees
    )
    
    
47. 各个部门中 最高工资中最低的那个部门的 最低工资是多少
    1). 查询出各个部门的最高工资
    SELECT max(salary)
    FROM employees
    GROUP BY department_id
    
    2). 查询出 1) 对应的查询结果的最低值: 各个部门中最低的最高工资(无法查询对应的 department_id)
    SELECT min(max(salary))
    FROM employees
    GROUP BY department_id
    
    3). 查询出 2) 所对应的部门 id 是多少: 各个部门中最高工资等于 2) 的那个部门的 id
    SELECT department_id
    FROM employees
    GROUP BY department_id
    HAVING max(salary) = (
        SELECT min(max(salary))
        FROM employees
        GROUP BY department_id
    )
    
    4). 查询出 3) 所在部门的最低工资
    SELECT min(salary)
    FROM employees
    WHERE department_id = (
        SELECT department_id
        FROM employees
        GROUP BY department_id
        HAVING max(salary) = (
            SELECT min(max(salary))
            FROM employees
            GROUP BY department_id
        )    
    )

48. 查询平均工资最高的部门的 manager 的详细信息: last_name, department_id, email, salary
    
    1). 各个部门中, 查询平均工资最高的平均工资是多少
    SELECT max(avg(salary))
    FROM employees
    GROUP BY department_id
    
    
    2). 各个部门中, 平均工资等于 1) 的那个部门的部门号是多少
    SELECT department_id
    FROM employees
    GROUP BY department_id
    HAVING avg(salary) = (
        SELECT max(avg(salary))
        FROM employees
        GROUP BY department_id
    )
    
    
    
    3). 查询出 2) 对应的部门的 manager_id
    SELECT manager_id
    FROM departments
    WHERE department_id = (
        SELECT department_id
        FROM employees
        GROUP BY department_id
        HAVING avg(salary) = (
            SELECT max(avg(salary))
            FROM employees
            GROUP BY department_id
        )    
    )
    
    
    4). 查询出 employee_id 为 3) 查询的 manager_id 的员工的 last_name, department_id, email, salary
    SELECT last_name, department_id, email, salary
    FROM employees
    WHERE employee_id = (
        SELECT manager_id
        FROM departments
        WHERE department_id = (
            SELECT department_id
            FROM employees
            GROUP BY department_id
            HAVING avg(salary) = (
                SELECT max(avg(salary))
                FROM employees
                GROUP BY department_id
            )    
        )    
    )
    

49. 查询 1999 年来公司的人所有员工的最高工资的那个员工的信息.
         
        1). 查询出 1999 年来公司的所有的员工的 salary
        SELECT salary
        FROM employees
        WHERE to_char(hire_date, 'yyyy') = '1999'
        
        2). 查询出 1) 对应的结果的最大值
        SELECT max(salary)
        FROM employees
        WHERE to_char(hire_date, 'yyyy') = '1999'
        
        3). 查询工资等于 2) 对应的结果且 1999 年入职的员工信息        
        SELECT *
        FROM employees
        WHERE to_char(hire_date, 'yyyy') = '1999' AND salary = (
            SELECT max(salary)
            FROM employees
            WHERE to_char(hire_date, 'yyyy') = '1999'
        )
        
50. 多行子查询的 any 和 all

        select department_id
        from employees
        group by department_id
        having avg(salary) >= any(
                                  --所有部门的平均工资
                                  select avg(salary)
                                  from employees
                                  group by department_id
                               )
        
any 和任意一个值比较, 所以其条件最为宽松, 所以实际上只需和平均工资最低的比较, 返回所有值
而 all 是和全部的值比较, 条件最为苛刻, 所以实际上返回的只需和平均工资最高的比较, 所以返回
平均工资最高的 department_id        
        

创建和管理表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
/*************************************************************************************************/
                              第 7 章  创建和管理表
/*************************************************************************************************/
51. 利用子查询创建表 myemp,
该表中包含 employees 表的 employee_id(id), last_name(name), salary(sal), email 字段

    1). 创建表的同时复制 employees 对应的记录
    
    create table myemp
    as
    select employee_id id, last_name name, salary sal, email from employees    
    
    2). 创建表的同时不包含 employees 中的记录, 即创建一个空表
    
    create table myemp
    as
    select employee_id id, last_name name, salary sal, email from employees where 1 = 2
    
52. 对现有的表进行修改操作

    1). 添加一个新列
    
    ALTER TABLE myemp
    ADD(age number(3))
    
    2). 修改现有列的类型
    
    ALTER TABLE myemp
    MODIFY(name varchar2(30));
    
    3). 修改现有列的名字
    
    ALTER TABLE myemp
    RENAME COLUMN sal TO salary;
    
    4). 删除现有的列
    
    ALTER TABLE myemp
    DROP COLUMN age;
    
53. 清空表(截断: truncate), 不能回滚!!    
        
54.

1). 创建一个表, 该表和 employees 有相同的表结构, 但为空表:  
    create table emp2 as select * from employees where 1 = 2;

2). 把 employees 表中 80 号部门的所有数据复制到 emp2 表中:

    insert into emp2 select * from employees where department_id = 80;

数据处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
/*************************************************************************************************/
                              第 8 章  数据处理
/*************************************************************************************************/
55. 更改 108 员工的信息: 使其工资变为所在部门中的最高工资, job 变为公司中平均工资最低的 job
    
    1). 搭建骨架
    update employees
    set salary = (
        
    ), job_id = (

    ) where employee_id = 108;
    
    2). 所在部门中的最高工资    
    select max(salary)
    from employees
    where department_id = (
        select department_id
        from employees
        where employee_id = 108
    )
    
    3). 公司中平均工资最低的 job
    select job_id
    from employees
    group by job_id
    having avg(salary) =  (
        select min(avg(salary))
        from employees
        group by job_id
    )
    
    4). 填充
    update employees e set salary = (
        select max(salary)
        from employees
        where department_id = e.department_id
    ), job_id = (
        select job_id
        from employees
        group by job_id
        having avg(salary) =  (
            select min(avg(salary))
            from employees
            group by job_id
        )
    ) where employee_id = 108;
    
56. 删除 108 号员工所在部门中工资最低的那个员工.

    1). 查询 108 员工所在的部门 id
    select department_id
    from employees
    where employee_id = 108;
    
    2). 查询 1) 部门中的最低工资:

    select min(salary)
    from employees
    where department_id = (
        select department_id
        from employees
        where employee_id = 108
    )
    
    3). 删除 1) 部门中工资为 2) 的员工信息:
    
    delete from employees e
        where department_id = (
                  select department_id
                  from employees e
                  where employee_id = 108
        ) and salary = (
                  select min(salary)
                  from employees
                  where department_id = e.department_id
        )    

约束

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
/*************************************************************************************************/
                                   第 9 章  约束
/*************************************************************************************************/
57. 定义非空约束

    1). 非空约束只能定义在列级.
    
    2). 不指定约束名
    create table emp2 (
    name varchar2(30) not null,
    age number(3)
    );
    
    3). 指定约束名    
    create table emp3(
    name varchar2(30) constraint name_not_null not null,
    age number(3));
    
58. 唯一约束
    1). 列级定义
        
        ①. 不指定约束名
        create table emp2 (
        name varchar2(30) unique,
        age number(3)
        );
        
        ②. 指定约束名
        create table emp3 (
        name varchar2(30) constraint name_uq unique,
        age number(3)
        );
        
    2). 表级定义: 必须指定约束名
        ①. 指定约束名
        create table emp3 (
        name varchar2(30),
        age number(3),
        constraint name_uq unique(name)
        );

58.1 主键约束:唯一确定一行记录。表明此属性:非空,唯一
        
59. 外键约束
    1). 列级定义
        
        ①. 不指定约束名
        create table emp2(
               emp_id number(6),
               name varchar2(25),
               dept_id number(4) references dept2(dept_id))
        
        ②. 指定约束名
        create table emp3(
               emp_id number(6),
               name varchar2(25),
               dept_id number(4) constraint dept_fk3 references dept2(dept_id))
        
    2). 表级定义: 必须指定约束名

        ①. 指定约束名
        create table emp4(
               emp_id number(6),
               name varchar2(25),
               dept_id number(4),
               constraint dept_fk2 foreign key(dept_id) references dept2(dept_id))
    
60. 约束需要注意的地方

    1). ** 非空约束(not null)只能定义在列级

    2). ** 唯一约束(unique)的列值可以为空

    3). ** 外键(foreign key)引用的列起码要有一个唯一约束        
    
61. 建立外键约束时的级联删除问题:
    1). 级联删除:
    
    create table emp2(
           id number(3) primary key,
           name varchar2(25) unique,
           dept_id number(3) references dept2(dept_id) on delete cascade)
    
    2). 级联置空
    
    create table emp3(
           id number(3) primary key,
           name varchar2(25) unique,
           dept_id number(3) references dept2(dept_id) on delete set null)

视图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
/*************************************************************************************************/
                              第 10 章  视图
/*************************************************************************************************
62. 查询员工表中 salary 前 10 的员工信息.

select last_name, salary
from (select last_name, salary from employees order by salary desc)
where rownum <= 10
    
    说明: rownum "伪列" ---- 数据表本身并没有这样的列, 是 oracle 数据库为每个数据表 "加上的"  列.
    可以标识行号.默认情况下 rownum 按主索引来排序. 若没有主索引则自然排序.
     
注意: **对 ROWNUM 只能使用 < 或 <=, 而是用 =, >, >= 都将不能返回任何数据.   

63. 查询员工表中 salary 10 - 20 的员工信息.    

select *
from(
  select rownum rn, temp.*
  from (
    select last_name, salary
    from employees e
    order by salary desc
  ) temp
)
where rn > 10 and rn < 21

64. 对 oralce 数据库中记录进行分页: 每页显示 10 条记录, 查询第 5 页的数据

select employee_id, last_name, salary
from (
        select rownum rn, employee_id, last_name, salary
        from employees
     ) e
where e.rn <= 50 and e.rn > 40   

注意: **对 oracle 分页必须使用 rownum "伪列"!

select employee_id, last_name, salary
from (
        select rownum rn, employee_id, last_name, salary
        from employees
     ) e
where e.rn <= pageNo * pageSize and e.rn > (pageNo - 1) * pageSize

其它数据库对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/*************************************************************************************************/
                              第 11 章  其它数据库对象
/*************************************************************************************************

65. 创建序列:

1). create sequence hs
    increment by 10
    start with 10

2). NEXTVAL 应在 CURRVAL 之前指定 ,二者应同时有效

65. 序列通常用来生成主键:

INSERT INTO emp2 VALUES (emp2_seq.nextval, 'xx', ...)

总结:  what -- why -- how
表table
视图view
序列sequence
索引index
同义词synonym

pqsql练习

基本语法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
0. 准备工作:
set serveroutput on

hellowrold 程序

begin
dbms_output.put_line('hello world');
end;
/

[语法格式]
--declare
  --声明的变量、类型、游标
begin
  --程序的执行部分(类似于java里的main()方法)
  dbms_output.put_line('helloworld');
--exception
  --针对begin块中出现的异常,提供处理的机制
  --when .... then ...
  --when  .... then ...
end;

*********************************************************************************************************
                        基本语法
*********************************************************************************************************
1. 使用一个变量

declare
  --声明一个变量
  v_name varchar2(25);
begin
  --通过 select ... into ... 语句为变量赋值
select last_name into v_name
from employees
where employee_id = 186;
-- 打印变量的值
dbms_output.put_line(v_name);
end;

2. 使用多个变量

declare
  --声明变量
  v_name varchar2(25);
  v_email varchar2(25);
  v_salary number(8, 2);
  v_job_id varchar2(10);
begin
  --通过 select ... into ... 语句为变量赋值
  --被赋值的变量与SELECT中的列名要一一对应
select last_name, email, salary, job_id into v_name, v_email, v_salary, v_job_id
from employees
where employee_id = 186;
-- 打印变量的值
dbms_output.put_line(v_name || ', ' || v_email || ', ' ||  v_salary || ', ' ||  v_job_id);
end;
----------------------------------------------------------------
                           记录类型
----------------------------------------------------------------
3.1 自定义记录类型

declare
  --定义一个记录类型
  type customer_type is record(
    v_cust_name varchar2(20),
    v_cust_id number(10));

  --声明自定义记录类型的变量
  v_customer_type customer_type;
begin
  v_customer_type.v_cust_name := '刘德华';
  v_customer_type.v_cust_id := 1001;
  
  dbms_output.put_line(v_customer_type.v_cust_name||','||v_customer_type.v_cust_id);
end;

3.2 自定义记录类型

declare
  --定义一个记录类型
  type emp_record is record(
    v_name varchar2(25),
    v_email varchar2(25),
    v_salary number(8, 2),
    v_job_id varchar2(10));
    
  --声明自定义记录类型的变量
  v_emp_record emp_record;
begin
  --通过 select ... into ... 语句为变量赋值
select last_name, email, salary, job_id into v_emp_record
from employees
where employee_id = 186;
-- 打印变量的值
dbms_output.put_line(v_emp_record.v_name || ', ' || v_emp_record.v_email || ', ' ||  
                                        v_emp_record.v_salary || ', ' ||  v_emp_record.v_job_id);
end;

4. 使用 %type 定义变量,动态的获取数据的声明类型

declare
  --定义一个记录类型
  type emp_record is record(
    v_name employees.last_name%type,
    v_email employees.email%type,
    v_salary employees.salary%type,
    v_job_id employees.job_id%type);
    
  --声明自定义记录类型的变量
  v_emp_record emp_record;
begin
  --通过 select ... into ... 语句为变量赋值
select last_name, email, salary, job_id into v_emp_record
from employees
where employee_id = 186;
-- 打印变量的值
dbms_output.put_line(v_emp_record.v_name || ', ' || v_emp_record.v_email || ', ' ||  
                                        v_emp_record.v_salary || ', ' ||  v_emp_record.v_job_id);
end;

5. 使用 %rowtype

declare
--声明一个记录类型的变量
  v_emp_record employees%rowtype;
begin
  --通过 select ... into ... 语句为变量赋值
select * into v_emp_record
from employees
where employee_id = 186;
-- 打印变量的值
dbms_output.put_line(v_emp_record.last_name || ', ' || v_emp_record.email || ', ' ||  
                                        v_emp_record.salary || ', ' ||  v_emp_record.job_id  || ', ' ||  
                                        v_emp_record.hire_date);
end;

6.1 赋值语句:通过变量实现查询语句

declare
  v_emp_record employees%rowtype;
  v_employee_id employees.employee_id%type;
begin
  --使用赋值符号位变量进行赋值
  v_employee_id := 186;

  --通过 select ... into ... 语句为变量赋值
select * into v_emp_record
from employees
where employee_id = v_employee_id;
-- 打印变量的值
dbms_output.put_line(v_emp_record.last_name || ', ' || v_emp_record.email || ', ' ||  
                                        v_emp_record.salary || ', ' ||  v_emp_record.job_id  || ', ' ||  
                                        v_emp_record.hire_date);
end;

6.2  通过变量实现DELETE、INSERT、UPDATE等操作

declare
  v_emp_id employees.employee_id%type;

begin
  v_emp_id := 109;
  delete from employees
  where employee_id = v_emp_id;
  --commit;
end;

流程控制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
*********************************************************************************************************
                        流程控制
*********************************************************************************************************
-----------------------------------------------------
           条件判断
-----------------------------------------------------
7. 使用 IF ... THEN ... ELSIF ... THEN ...ELSE ... END IF;

要求: 查询出 150号 员工的工资, 若其工资大于或等于 10000 则打印 'salary >= 10000';
若在 5000 到 10000 之间, 则打印 '5000<= salary < 10000'; 否则打印 'salary < 5000'
(方法一)
declare
  v_salary employees.salary%type;
begin
  --通过 select ... into ... 语句为变量赋值
select salary into v_salary
from employees
where employee_id = 150;
dbms_output.put_line('salary: ' || v_salary);
-- 打印变量的值
if v_salary >= 10000 then
    dbms_output.put_line('salary >= 10000');
elsif v_salary >= 5000 then
    dbms_output.put_line('5000 <= salary < 10000');
else
    dbms_output.put_line('salary < 5000');
end if;
(方法二)
declare
     v_emp_name employees.last_name%type;
     v_emp_sal employees.salary%type;
     v_emp_sal_level varchar2(20);
begin
     select last_name,salary into v_emp_name,v_emp_sal from employees where employee_id = 150;
     
     if(v_emp_sal >= 10000) then v_emp_sal_level := 'salary >= 10000';
     elsif(v_emp_sal >= 5000) then v_emp_sal_level := '5000<= salary < 10000';
     else v_emp_sal_level := 'salary < 5000';
     end if;
     
     dbms_output.put_line(v_emp_name||','||v_emp_sal||','||v_emp_sal);
end;

7+ 使用 CASE ... WHEN ... THEN ...ELSE ... END 完成上面的任务

declare
       v_sal employees.salary%type;
       v_msg varchar2(50);
begin     
       select salary into v_sal
       from employees
       where employee_id = 150;
       
       --case 不能向下面这样用
       /*
       case v_sal when salary >= 10000 then v_msg := '>=10000'
                  when salary >= 5000 then v_msg := '5000<= salary < 10000'
                  else v_msg := 'salary < 5000'
       end;
       */
       v_msg :=
             case trunc(v_sal / 5000)
                  when 0 then 'salary < 5000'
                  when 1 then '5000<= salary < 10000'
                  else 'salary >= 10000'
             end;
       
       dbms_output.put_line(v_sal ||','||v_msg);
end;

8. 使用 CASE ... WHEN ... THEN ... ELSE ... END;

要求: 查询出 122 号员工的 JOB_ID, 若其值为 'IT_PROG', 则打印 'GRADE: A';
                        'AC_MGT', 打印 'GRADE B',
                        'AC_ACCOUNT', 打印 'GRADE C';
                        否则打印 'GRADE D'

declare
       --声明变量
       v_grade char(1);
       v_job_id employees.job_id%type;
begin
       select job_id into v_job_id
       from employees
       where employee_id = 122;
       
       dbms_output.put_line('job_id: ' || v_job_id);
       
       --根据 v_job_id 的取值, 利用 case 字句为 v_grade 赋值
       v_grade :=  
               case v_job_id when 'IT_PROG' then 'A'
                             when 'AC_MGT' then 'B'
                             when 'AC_ACCOUNT' then 'C'
                             else 'D'
                end;
                
       dbms_output.put_line('GRADE: ' || v_grade);
end;
-----------------------------------------------------
           循环结构
-----------------------------------------------------
9. 使用循环语句打印 1 - 100.(三种方式)

1).  LOOP ... EXIT WHEN ... END LOOP
declare
       --初始化条件
       v_i number(3) := 1;
begin
       loop
       --循环体
        dbms_output.put_line(v_i);
    --循环条件
        exit when v_i = 100;
    --迭代条件
        v_i := v_i + 1;
       end loop;
end;

2). WHILE ... LOOP ... END LOOP
declare
       --初始化条件
       v_i number(3) := 1;
begin
       --循环条件
       while v_i <= 100 loop
         --循环体
             dbms_output.put_line(v_i);
         --迭代条件
             v_i := v_i + 1;
       end loop;
end;

3).
begin
       for i in 1 .. 100 loop
             dbms_output.put_line(i);
       end loop;
end;

10. 综合使用 if, while 语句, 打印 1 - 100 之间的所有素数
(素数: 有且仅用两个正约数的整数, 2, 3, 5, 7, 11, 13, ...).
declare
  v_flag number(1):=1;
  v_i number(3):=2;
  v_j number(2):=2;
begin

  while (v_i<=100) loop
        while v_j <= sqrt(v_i) loop
              if (mod(v_i,v_j)=0) then v_flag:= 0;
          end if;
             
          v_j :=v_j +1;
        end loop;
        
    if(v_flag=1) then dbms_output.put_line(v_i);
    end if;

        v_flag :=1;
        v_j := 2;
        v_i :=v_i +1;
   end loop;

end;

(法二)使用for循环实现1-100之间的素数的输出
declare
  --标记值, 若为 1 则是素数, 否则不是
  v_flag number(1) := 0;
begin
   for i in 2 .. 100 loop

       v_flag := 1;     
         
       for j in 2 .. sqrt(i) loop
           if i mod j = 0 then
              v_flag := 0;    
           end if;        
       end loop;
       
       if v_flag = 1 then
           dbms_output.put_line(i);
       end if;
       
   end loop;
end;

11. 使用 goto
declare
  --标记值, 若为 1 则是素数, 否则不是
  v_flag number(1) := 0;
begin
   for i in 2 .. 100 loop
       v_flag := 1;     
         
       for j in 2 .. sqrt(i) loop
           if i mod j = 0 then
              v_flag := 0;
              goto label;
           end if;        
       end loop;
       
       <<label>>
       if v_flag = 1 then
           dbms_output.put_line(i);
       end if;
       
   end loop;
end;

11+.打印1——100的自然数,当打印到50时,跳出循环,输出“打印结束”
(方法一)
begin
  for i in 1..100 loop
      dbms_output.put_line(i);
      if(i = 50) then
      goto label;
      end if;
  end loop;
      
      <<label>>
      dbms_output.put_line('打印结束');

end;
(方法二)
begin
  for i in 1..100 loop
      dbms_output.put_line(i);
      if(i mod 50 = 0) then dbms_output.put_line('打印结束');
      exit;
      end if;
  end loop;
end;

游标的使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
*********************************************************************************************************
                        游标的使用
*********************************************************************************************************
12.1 使用游标

要求: 打印出 80 部门的所有的员工的工资:salary: xxx
declare
  --1. 定义游标
  cursor salary_cursor is select salary from employees where department_id = 80;
  v_salary employees.salary%type;
begin
--2. 打开游标
open salary_cursor;

--3. 提取游标
fetch salary_cursor into v_salary;
--4. 对游标进行循环操作: 判断游标中是否有下一条记录
while salary_cursor%found loop
      dbms_output.put_line('salary: ' || v_salary);
      fetch salary_cursor into v_salary;
end loop;  
--5. 关闭游标
close  salary_cursor;
end;

12.2 使用游标

要求: 打印出 80 部门的所有的员工的工资: Xxx 's salary is: xxx
declare
  cursor sal_cursor is select salary ,last_name from employees where department_id = 80;
  v_sal number(10);
  v_name varchar2(20);
begin
  open sal_cursor;
  
  fetch sal_cursor into v_sal,v_name;
  
  while sal_cursor%found loop
        dbms_output.put_line(v_name||'`s salary is '||v_sal);
        fetch sal_cursor into v_sal,v_name;
  end loop;
  
  close sal_cursor;
  
end;

13. 使用游标的练习:
打印出 manager_id 为 100 的员工的 last_name, email, salary 信息(使用游标, 记录类型)

declare  
           --声明游标    
           cursor emp_cursor is select last_name, email, salary from employees where manager_id = 100;
           
           --声明记录类型
           type emp_record is record(
                name employees.last_name%type,
                email employees.email%type,
                salary employees.salary%type
           );
           
           -- 声明记录类型的变量
           v_emp_record emp_record;
begin
           --打开游标
           open emp_cursor;
           
           --提取游标
           fetch emp_cursor into v_emp_record;
           
           --对游标进行循环操作
           while emp_cursor%found loop
                  dbms_output.put_line(v_emp_record.name || ', ' || v_emp_record.email || ', ' || v_emp_record.salary );                
                  fetch emp_cursor into v_emp_record;
           end loop;
           
           --关闭游标
           close emp_cursor;
end;
(法二:使用for循环)
declare
   
      cursor emp_cursor is
      select last_name,email,salary
      from employees
      where manager_id = 100;

begin

      for v_emp_record in emp_cursor loop
          dbms_output.put_line(v_emp_record.last_name||','||v_emp_record.email||','||v_emp_record.salary);
      end loop;
end;

14. 利用游标, 调整公司中员工的工资:
    
    工资范围       调整基数
    0 - 5000       5%
    5000 - 10000   3%
    10000 - 15000  2%
    15000 -        1%

declare
    --定义游标
    cursor emp_sal_cursor is select salary, employee_id from employees;
    
    --定义基数变量
    temp number(4, 2);
    
    --定义存放游标值的变量
    v_sal employees.salary%type;
    v_id employees.employee_id%type;
begin
    --打开游标
    open emp_sal_cursor;
    
    --提取游标
    fetch emp_sal_cursor into v_sal, v_id;
    
    --处理游标的循环操作
    while emp_sal_cursor%found loop
          --判断员工的工资, 执行 update 操作
          --dbms_output.put_line(v_id || ': ' || v_sal);
            
          if v_sal <= 5000 then
             temp := 0.05;
          elsif v_sal<= 10000 then
             temp := 0.03;   
          elsif v_sal <= 15000 then
             temp := 0.02;
          else
             temp := 0.01;
          end if;
          
          --dbms_output.put_line(v_id || ': ' || v_sal || ', ' || temp);
          update employees set salary = salary * (1 + temp) where employee_id = v_id;
                  
          fetch emp_sal_cursor into v_sal, v_id;
    end loop;
    --关闭游标
    close emp_sal_cursor;
end;

使用SQL中的 decode 函数

update employees set salary = salary * (1 + (decode(trunc(salary/5000), 0, 0.05,
                                                                        1, 0.03,
                                                                        2, 0.02,
                                                                        0.01)))

15. 利用游标 for 循环完成 14.

declare
    --定义游标
    cursor emp_sal_cursor is select salary, employee_id id from employees;
    
    --定义基数变量
    temp number(4, 2);
begin
    --处理游标的循环操作
    for c in emp_sal_cursor loop
          --判断员工的工资, 执行 update 操作
          --dbms_output.put_line(v_id || ': ' || v_sal);
            
          if c.salary <= 5000 then
             temp := 0.05;
          elsif c.salary <= 10000 then
             temp := 0.03;   
          elsif c.salary <= 15000 then
             temp := 0.02;
          else
             temp := 0.01;
          end if;
          
          --dbms_output.put_line(v_id || ': ' || v_sal || ', ' || temp);
          update employees set salary = salary * (1 + temp) where employee_id = c.id;
    end loop;
end;

16*. 带参数的游标

declare
    --定义游标
    cursor emp_sal_cursor(dept_id number, sal number) is
           select salary + 1000 sal, employee_id id
           from employees
           where department_id = dept_id and salary > sal;
    
    --定义基数变量
    temp number(4, 2);
begin
    --处理游标的循环操作
    for c in emp_sal_cursor(sal => 4000, dept_id => 80) loop
          --判断员工的工资, 执行 update 操作
          --dbms_output.put_line(c.id || ': ' || c.sal);
          
          if c.sal <= 5000 then
             temp := 0.05;
          elsif c.sal <= 10000 then
             temp := 0.03;   
          elsif c.sal <= 15000 then
             temp := 0.02;
          else
             temp := 0.01;
          end if;
          
          dbms_output.put_line(c.sal || ': ' || c.id || ', ' || temp);
          --update employees set salary = salary * (1 + temp) where employee_id = c.id;
    end loop;
end;

17. 隐式游标: 更新指定员工 salary(涨工资 10),如果该员工没有找到,则打印”查无此人” 信息

begin
         update employees set salary = salary + 10 where employee_id = 1005;
         
         if sql%notfound then
            dbms_output.put_line('查无此人!');
         end if;
end;

异常处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
*********************************************************************************************************
                        异常处理
*********************************************************************************************************
[预定义异常]
declare

  v_sal employees.salary%type;
begin
  select salary into v_sal
  from employees
  where employee_id >100;
  
  dbms_output.put_line(v_sal);

exception
  when Too_many_rows then dbms_output.put_line('输出的行数太多了');
end;

[非预定义异常]
declare

  v_sal employees.salary%type;
  --声明一个异常
  delete_mgr_excep exception;
  --把自定义的异常和oracle的错误关联起来
  PRAGMA EXCEPTION_INIT(delete_mgr_excep,-2292);
begin
  delete from employees
  where employee_id = 100;
  
  select salary into v_sal
  from employees
  where employee_id >100;
  
  dbms_output.put_line(v_sal);

exception
  when Too_many_rows then dbms_output.put_line('输出的行数太多了');
  when delete_mgr_excep then dbms_output.put_line('Manager不能直接被删除');
end;

[用户自定义异常]
declare

  v_sal employees.salary%type;
  --声明一个异常
  delete_mgr_excep exception;
  --把自定义的异常和oracle的错误关联起来
  PRAGMA EXCEPTION_INIT(delete_mgr_excep,-2292);
  
  --声明一个异常
  too_high_sal exception;
begin

  select salary into v_sal
  from employees
  where employee_id =100;
  
  if v_sal > 1000 then
     raise too_high_sal;
  end if;
     
  delete from employees
  where employee_id = 100;

  dbms_output.put_line(v_sal);

exception
  when Too_many_rows then dbms_output.put_line('输出的行数太多了');
  when delete_mgr_excep then dbms_output.put_line('Manager不能直接被删除');
  --处理异常
  when too_high_sal then dbms_output.put_line('工资过高了');
end;

18. 异常的基本程序:
通过 select ... into ... 查询某人的工资, 若没有查询到, 则输出 "未找到数据"

declare
  --定义一个变量
  v_sal employees.salary%type;
begin
  --使用 select ... into ... 为 v_sal 赋值
  select salary into v_sal from employees where employee_id = 1000;
  dbms_output.put_line('salary: ' || v_sal);
exception
  when No_data_found then
       dbms_output.put_line('未找到数据');
end;



declare
  --定义一个变量
  v_sal employees.salary%type;
begin
  --使用 select ... into ... 为 v_sal 赋值
  select salary into v_sal from employees;
  dbms_output.put_line('salary: ' || v_sal);
exception
  when No_data_found then
       dbms_output.put_line('未找到数据!');
  when Too_many_rows then
       dbms_output.put_line('数据过多!');     
end;

19. 更新指定员工工资,如工资小于300,则加100;对 NO_DATA_FOUND 异常, TOO_MANY_ROWS 进行处理.
declare
   v_sal employees.salary%type;
begin
   select salary into v_sal from employees where employee_id = 100;
   
   if(v_sal < 300) then update employees set salary = salary + 100 where employee_id = 100;
   else dbms_output.put_line('工资大于300');
   end if;
exception
   when no_data_found then dbms_output.put_line('未找到数据');
    when too_many_rows then dbms_output.put_line('输出的数据行太多');
end;

20. 处理非预定义的异常处理: "违反完整约束条件"

declare
  --1. 定义异常    
  temp_exception exception;

  --2. 将其定义好的异常情况,与标准的 ORACLE 错误联系起来,使用 EXCEPTION_INIT 语句
  PRAGMA EXCEPTION_INIT(temp_exception, -2292);
begin
  delete from employees where employee_id = 100;

exception
  --3. 处理异常
  when temp_exception then
       dbms_output.put_line('违反完整性约束!');
end;

21. 自定义异常: 更新指定员工工资,增加100;若该员工不存在则抛出用户自定义异常: no_result

declare
  --自定义异常                                   
  no_result exception;   
begin
  update employees set salary = salary + 100 where employee_id = 1001;

  --使用隐式游标, 抛出自定义异常
  if sql%notfound then
     raise no_result;
  end if;  

exception

  --处理程序抛出的异常
  when no_result then
     dbms_output.put_line('更新失败');
end;

存储函数和过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
*********************************************************************************************************
                    存储函数和过程
*********************************************************************************************************
[存储函数:有返回值,创建完成后,通过select function() from dual;执行]
[存储过程:由于没有返回值,创建完成后,不能使用select语句,只能使用pl/sql块执行]

[格式]
--函数的声明(有参数的写在小括号里)
create or replace function func_name(v_param varchar2)
    --返回值类型
    return varchar2
is
    --PL/SQL块变量、记录类型、游标的声明(类似于前面的declare的部分)
begin
    --函数体(可以实现增删改查等操作,返回值需要return)
       return 'helloworld'|| v_logo;
end;

22.1 函数的 helloworld: 返回一个 "helloworld" 的字符串

create or replace function hello_func
return varchar2
is
begin
       return 'helloworld';
end;

执行函数

begin
    dbms_output.put_line(hello_func());
end;

或者: select hello_func() from dual;

22.2 返回一个"helloworld: atguigu"的字符串,其中atguigu 由执行函数时输入。

--函数的声明(有参数的写在小括号里)
create or replace function hello_func(v_logo varchar2)
--返回值类型
return varchar2
is
--PL/SQL块变量的声明
begin
--函数体
       return 'helloworld'|| v_logo;
end;

22.3 创建一个存储函数,返回当前的系统时间
create or replace function func1
return date
is
--定义变量
v_date date;
begin
    --函数体
    --v_date := sysdate;
       select sysdate into v_date from dual;
       dbms_output.put_line('我是函数哦');
       
       return v_date;
end;

执行法1:
select func1 from dual;
执行法2:
declare
  v_date date;
begin
  v_date := func1;
  dbms_output.put_line(v_date);
end;
23. 定义带参数的函数: 两个数相加

create or replace function add_func(a number, b number)
return number
is
begin
       return (a + b);
end;

执行函数

begin
    dbms_output.put_line(add_func(12, 13));
end;
或者
    select add_func(12,13) from dual;

24. 定义一个函数: 获取给定部门的工资总和, 要求:部门号定义为参数, 工资总额定义为返回值.

create or replace function sum_sal(dept_id number)
       return number
       is
       
       cursor sal_cursor is select salary from employees where department_id = dept_id;
       v_sum_sal number(8) := 0;   
begin
       for c in sal_cursor loop
           v_sum_sal := v_sum_sal + c.salary;
       end loop;       

       --dbms_output.put_line('sum salary: ' || v_sum_sal);
       return v_sum_sal;
end;

执行函数

begin
    dbms_output.put_line(sum_sal(80));
end;

25. 关于 OUT 型的参数: 因为函数只能有一个返回值, PL/SQL 程序可以通过 OUT 型的参数实现有多个返回值

要求: 定义一个函数: 获取给定部门的工资总和 和 该部门的员工总数(定义为 OUT 类型的参数).
要求: 部门号定义为参数, 工资总额定义为返回值.

create or replace function sum_sal(dept_id number, total_count out number)
       return number
       is
       
       cursor sal_cursor is select salary from employees where department_id = dept_id;
       v_sum_sal number(8) := 0;   
begin
       total_count := 0;

       for c in sal_cursor loop
           v_sum_sal := v_sum_sal + c.salary;
           total_count := total_count + 1;
       end loop;       

       --dbms_output.put_line('sum salary: ' || v_sum_sal);
       return v_sum_sal;
end;   

执行函数:

delare
  v_total number(3) := 0;

begin
    dbms_output.put_line(sum_sal(80, v_total));
    dbms_output.put_line(v_total);
end;

26*. 定义一个存储过程: 获取给定部门的工资总和(通过 out 参数), 要求:部门号和工资总额定义为参数

create or replace procedure sum_sal_procedure(dept_id number, v_sum_sal out number)
       is
       
       cursor sal_cursor is select salary from employees where department_id = dept_id;
begin
       v_sum_sal := 0;
       
       for c in sal_cursor loop
           --dbms_output.put_line(c.salary);
           v_sum_sal := v_sum_sal + c.salary;
       end loop;       

       dbms_output.put_line('sum salary: ' || v_sum_sal);
end;
[执行]
declare
     v_sum_sal number(10) := 0;
begin
     sum_sal_procedure(80,v_sum_sal);
end;

27*. 自定义一个存储过程完成以下操作:
对给定部门(作为输入参数)的员工进行加薪操作, 若其到公司的时间在 (? , 95) 期间,    为其加薪 %5
                                                               [95 , 98)             %3       
                                                               [98, ?)               %1
得到以下返回结果: 为此次加薪公司每月需要额外付出多少成本(定义一个 OUT 型的输出参数).

create or replace procedure add_sal_procedure(dept_id number, temp out number)

is

       cursor sal_cursor is select employee_id id, hire_date hd, salary sal from employees where department_id = dept_id;
       a number(4, 2) := 0;
begin
       temp := 0;       

       for c in sal_cursor loop
           a := 0;    
       
           if c.hd < to_date('1995-1-1', 'yyyy-mm-dd') then
              a := 0.05;
           elsif c.hd < to_date('1998-1-1', 'yyyy-mm-dd') then
              a := 0.03;
           else
              a := 0.01;
           end if;
           
           temp := temp + c.sal * a;
           update employees set salary = salary * (1 + a) where employee_id = c.id;
       end loop;       
end;

触发器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
*********************************************************************************************************
                        触发器
*********************************************************************************************************
一个helloworld级别的触发器
create or replace trigger hello_trigger
after
update on employees
--for each row
begin
    dbms_output.put_line('hello...');
    --dbms_output.put_line('old.salary:'|| :OLD.salary||',new.salary'||:NEW.salary);
end;
然后执行:update employees set salary = salary + 1000;

28. 触发器的 helloworld: 编写一个触发器, 在向 emp 表中插入记录时, 打印 'helloworld'

create or replace trigger emp_trigger
after
insert on emp
for each row
begin
       dbms_output.put_line('helloworld');
end;

29. 行级触发器: 每更新 employees 表中的一条记录, 都会导致触发器执行

create or replace trigger employees_trigger
after
update on employees
for each row
begin
       dbms_output.put_line('修改了一条记录!');
end;

语句级触发器: 一个 update/delete/insert 语句只使触发器执行一次

create or replace trigger employees_trigger
after
update on employees
begin
       dbms_output.put_line('修改了一条记录!');
end;

30. 使用 :new, :old 修饰符

create or replace trigger employees_trigger
after
update on employees
for each row
begin
       dbms_output.put_line('old salary: ' || :old.salary || ', new salary: ' || :new.salary);
end;

31. 编写一个触发器, 在对 my_emp 记录进行删除的时候, 在 my_emp_bak 表中备份对应的记录

1). 准备工作:
    create table my_emp as select employee_id id, last_name name, salary sal from employees
    create table my_emp_bak as select employee_id id, last_name name, salary sal from employees where 1 = 2

2).
create or replace trigger bak_emp_trigger
       before delete on my_emp
       for each row
       
begin
       insert into my_emp_bak values(:old.id, :old.name, :old.sal);
end;

Oracle问题

环境问题

1.oracle用户登录后,执行sqlplus / as sysdba报错 ORA-12162错误:TNS:net service name is incorrectly specified

1
2
3
4
5
> vi ~/.profile

增加: export ORACLE\_SID=flk

> source ~/.profile

重新连接,成功

2.plsql连接,报错,TNS:no listener

在服务器$ORACLE_HOME/network/admin下增加listener.ora,tnsnames.ora,sqlnet.ora等

3.ORA-12560: TNS:protocol adapter error(TNS:协议适配器错误)

1
2
3
4
5
6
解决:
1.使用sysdba账号登录(运行cmd-->sqlplus / as sysdba)
2. 解除锁定账号(例如解除system用户)
alter user system account unlock;
3. 为system用户设置新密码(例如设置密码为(推存设置):manager)
alter user system identified by manager;

4.sqlplus SYS/SYS@122.67.109.136:1521/hpt as sysdba

1
sys用户登陆

5.Oracle 12c创建用户时出现“ORA-65096: invalid common user or role name”的错误

1
2
3
4
5
6
7
8
9
10
11
12
# 查看Oracle 12c的版本
select * from v$version;
select sys_context ('USERENV', 'CON_NAME') from dual;
# 通过ALTER SESSION SET CONTAINER 指定其他容器
alter pluggable database pdborcl open;
# 查看容器
select con_id,dbid,NAME,OPEN_MODE from v$pdbs; => read,write 就行
# 切换容器到pdb
alter session set container=PDBORCL;
# 查看当前使用容器
select sys_context ('USERENV', 'CON_NAME') from dual;
# 接着就可以创建用户了,注意创建表空间临时表空间和创建用户在flk容器下的会话里执行

6.重启Oracle数据库实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
lsnrctl stop
# 重启实例
su - oracle
(1) 切换需要启动的数据库实例:export ORACLE_SID=flk
(2) 进入Sqlplus控制台,命令:sqlplus /nolog
(3) 以系统管理员登录,命令:connect / as sysdba
(4) 如果是关闭数据库,命令:shutdown abort
(5) 启动数据库,命令:startup
(6) 退出sqlplus控制台,命令:exit

# 重启监听器
(7) 进入监听器控制台,命令:lsnrctl
(8) 启动监听器,命令:start
(9) 退出监听器控制台,命令:exit

7.临时表空间撑爆

1
2
3
4
5
6
7
8
# 删除,并重新创建临时表空间,限制最大为8G
drop tablespace flk_data_tmp including contents and datafiles cascade constraints;
create temporary tablespace flk_dta_tmp tempfile
'/datafs/oradata/flk/datatmp/flkcdbdatatmp01.dbf' size 2048M,
'/datafs/oradata/flk/datatmp/flkcdbdatatmp01.dbf' size 2048M
autoextend on next 50M maxsize 8G
tablespace GROUP ''
extent management local uniform size 1M;

8.oracle sqlldr 数据导入错误Field in data file exceeds maximum length解决

1
2
3
4
5
1. 数据字段确实比数据库中的字段要长,需要调整数据库字段的长度。
2. 要入库的字段问题(中文问题),在control(.ctl)文件中添加字符类型表示进行解决。如:  COMPLAINT_CONTENT char(4000)
3. 数据有换行
    判断: where instr(task_name,chr(10))>0
    解决: replace(task_name,chr(10),'\\n')

9.数据库连接上,可以查询,但是只能是select * ,加条件就会卡死。怀疑是数据库文件损坏

1
2
3
4
5
6
top 
oracle CPU%

将【%cpu】占用率最高的那条的【PID】复制取出,去数据库执行下面语句,即可查询出是哪条sql语句占导致cpu占用很高


10.表空间不足

造成问题原因分析

1.表空间不足。
2.表空间状态未开启自动扩展功能。

检查

1.查看表空间使用情况

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
SELECT UPPER(F.TABLESPACE_NAME) "表空间名",
D.TOT_GROOTTE_MB "表空间大小(M) ",
D.TOT_GROOTTE_MB - F.TOTAL_BYTES "已使用空间(M)",
TO_CHAR(ROUND((D.TOT_GROOTTE_MB - F.TOTAL_BYTES) / D.TOT_GROOTTE_MB * 100,
2),
'990.99') "使用比",
F.TOTAL_BYTES "空闲空间(M) ",
F.MAX_BYTES "最大块(M) "
FROM (SELECT TABLESPACE_NAME,
ROUND(SUM(BYTES) / (1024 * 1024), 2) TOTAL_BYTES,
ROUND(MAX(BYTES) / (1024 * 1024), 2) MAX_BYTES
FROM SYS.DBA_FREE_SPACE
GROUP BY TABLESPACE_NAME) F,
(SELECT TABLESPACE_NAME,
ROUND(SUM(BYTES) / (1024 * 1024), 2) TOT_GROOTTE_MB
FROM SYS.DBA_DATA_FILES
GROUP BY TABLESPACE_NAME) D
WHERE D.TABLESPACE_NAME = F.TABLESPACE_NAME
ORDER BY 4 DESC --已知表空间名,可以精确查询-- and D.TABLESPACE_NAME = '表空间名' ;

image-20230613155305120

2.查看表空间状态

1
2
3
4
5
6
7
8
SELECT D.TABLESPACE_NAME,
D.FILE_NAME,
D.AUTOEXTENSIBLE,
D.BYTES,
D.MAXBYTES,
D.STATUS
FROM DBA_DATA_FILES D
WHERE D.TABLESPACE_NAME = '表空间名';

解决
结论:AUTOEXTENSIBLE 自动扩展(YES);
STATUS 值为AVAILABLE,表空间状态为开启了自动扩展的功能。
综合上述检查结果,可断定遇到的问题是因为可能性1—表空间不足导致。解决办法也就是扩大表空间。

方法:
扩大表空间的四种方法:
1、增加数据文件

1
2
ALTER TABLESPACE '表空间名' ADD DATAFILE 
'D:\ORACLE\PRODUCT\10.2.0\ORADATA\DBFILE\TRD_2.DBF' SIZE 1024M;

2、增加数据文件并允许自动增长

1
2
ALTER TABLESPACE '表空间名' ADD DATAFILE 
'D:\ORACLE\PRODUCT\10.2.0\ORADATA\DBFILE\TRD_2.DBF' SIZE 1024M AUTOEXTEND ON NEXT 8M MAXSIZE 10240M;

3、允许已存在的数据文件自动增长

1
2
ALTER DATABASE DATAFILE 'D:\ORACLE\PRODUCT\10.2.0\ORADATA\DBFILE\TRD.DBF' 
AUTOEXTEND ON NEXT 8M MAXSIZE 10240M;

4、手工改变已存在数据文件的大小

1
2
ALTER DATABASE DATAFILE 'D:\ORACLE\PRODUCT\10.2.0\ORADATA\DBFILE\TRD.DBF' 
RESIZE 10240M;

实际解决过程中,我们使用的是方法2,加了5G,具体方法选择根据自身需求。

ORA-12537

某天早上,突然数据库链接不上了。一直报TNS:链接拒绝

尝试杀连接数,好像没有效果。

1
2
3
4
5
6
7
8
杀死session,就行了

# sys用户登陆
select sid,serial#,username,machine,program,status,logon_time from v$session
where status = 'ACTIVE'
and username is null

alter system kill session 'sid,serial#';

业务问题

1.invalid number

1
2
3
4
查询SQL 报错
因为关联的表是字符串字段和数字字段管理.隐式转换时,字符串字段有些数据包含中文什么的,无法转换.所以报错了.
我处理的方式是将不能转换的过滤掉
where regexp_like(T1.字段名,' ^[[:digit:]]+$')

参考

2.java调用shell脚本问题

1
2
3
4
5
6
7
8
9
有一个shell脚本,里面有sqlplus语法
我想通过java代码调用 
1.导入ganymed-ssh2.262依赖(比build210包精简些)
2.写代码,执行不了,报错登录错误,将/etc/ssh/sshd_config下的脚本修改为passwordAuthentication=yes
3.再次执行,找不到sqlplus.
查了好久,最后没办法,问了下组长,组长说.在开头加下source /home/oracle/.profile
调用成功.
唉,Linux还是不够深入了解!
组长厉害!

参考链接:

  • java远程执行linux服务器上的shell脚本:
  • java代码中调用执行shell脚本,sqlldr导数与使用sqlplus在shell调用执行存储过程:

3.oracle同步数据 | spool sqlldr 导入导出字段太长

  • field in data file exceeds maximum length
1
如果目标库的字段设置已经满足最大字段了。那就是因为CTL默认是char 255,要显式设置为char(xxx)
  • 【困扰】如果字段有标点符号或特殊符号或换行,会导致导出的数据串行,从而导致导致报错。而且报错是报字段太小的错误。很恶心。
1
目前只能忽略该字段拉取.但是如果要用到该字段的时候怎么办?
  • 如果要替换oracle中的中文换行
1
用replace(replace(item_name,chr(13),''),chr(10),'')

oracle同步数据 | oracle如果数据库表是date类型,可以先转为varchar,然后ctl导入本地,varchar自带,这样格式就对了

oracle如果数据库表是date类型,可以先转为varchar,然后ctl导入本地,varchar自带,这样格式就对了

4.oracle同步数据 | SQL Loader Internal Error

SQL Loader Internal Error

和腾姣秀沟通后,感觉是因为rows值过大(50W)导致缓存区满了。所以执行失败了

5.sqool同步时的编码问题

  • sqool同步时的编码问题
1
2
3
4
5
6
7
从库A同步到库B的时候,CTL设置了char(4000),然后还是报错:value too large for column(actual:3000,maximum:4000)
怀疑时编码问题:

select * from nls_database_parameters where parameter =’NLS_CHARACTERSET’;
查询库A(要拉取的,源数据库)的 编码:ZHS16GBK
执行sqlldr,客户端的编码:ZHS16GBK
查询库B(要插入的,目标数据库)的 编码:AL32UTF8

spool+sqlldr oracle数据库同步

调度中心(122.243.142.38)从GIT库(122.21.125.22)拉取git数据到脚本服务器(122.16.62.142),导入到生产库(122.29.61.133)

1
推测:数据库编码不一样

查询oracle服务器端的编码

select userenv(‘language’) from dual;

目标库:rmdb:AMERICAN_AMERICA.ZHS16GBK

本地库:flk:AMERICAN_AMERICA.AL32UTF8

  • 概念:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
    oracle10g UTF8编码:AMERICAN_AMERICA.AL32UTF8
          GBK编码:SIMPLIFIED CHINESE_CHINA.ZHS16GBK
    
    字符集子集向其超集转换是可行的,如此例 ZHS16GBK转换为AL32UTF8。
    导出使用的字符集将会记录在导出文件中,当文件导入时,将会检查导出时使用的字符集设置,如果这个字符集不同于导入客户端的NLS_LANG
设置,字符集将根据导入客户端NLS_LANG设置进行转换,如果必要,在数据插入数据库之前会进行进一步转换。
    通常在导出时最好把客户端字符集设置得和数据库端相同,这样可以避免在导出时发生不必要的数据转换,导出文件将和数据库具有相同的字符集。
    h即使将来会把导出文件导入到不同字符集的数据库中,这样做也可以把转换延缓至导入时刻。

当进行数据导入时,主要存在以下两种情况:
    1.源数据库和目标数据库具有相同字符集设置
    这时,只需要设置NLS_LANG等于数据库字符集即可导入(前提是,导出使用的是和源数据库相同字符集,即三者相同)
    2.源数据库和目标数据库字符集不同
    如果我们导出时候使用的NLS_LANG是和源数据库相同的字符集,那么导入时就可以设置客户端NLS_LANG等于导出时使用的字符集,这
样转换只发生在数据库端,而且只发生一次。

例如:
如果进行从ZHS16GBK到UTF8的转换
1)使用NLS_LANG=AMERICAN_AMERICA.ZHS16GBK导出数据库。
这时创建的导出文件包含ZHS16GBK的数据
2)导入时使用NLS_LANG=AMERICAN_AMERICA.ZHS16GBK
这时转换仅发生在insert数据到UTF8的数据库中。

以上假设的转换只在目标数据库字符集是源数据库字符集的超集时才能转换。

含有汉字的固定字符由ZHS16GBK数据库导入到AL32UTF8的数据

此文章是对于上一个实验的补充,上一次实验仅仅考虑的 varchar2 的情况。这次考虑到对于char类型的含有中文数据的情况。
对于英文:
对英文,在al32utf8中仍然和zhs16gbk一样用1个字节表示,因此导入固定长度英文字符数据时不会出错。
对于中文:
例如在字符集为zhs16gbk 数据库中创建表时指定字段 val char(15),该字段含有数据 ‘阿里云计算公司’在字符集为zhs16gbk 数据库中占用14个字节,而在字符集为al32utf8 数据库中占用21个字节 大于 char(15)所指定的长度15.此时导入数据就会失败。

对于英文字符可以实现由zhs16gbk 到 al32utf8的转换。
解释:用UTF-8,UNICODE的2字节字符用变长个(1-3个字节)表示:
1. 对英文,仍然和ASCII一样用1个字节表示,这个字节的值小于128(\x80);
2. 扩展的ASCII字符(主要是西欧),第一字节用C2 - DF之间的范围,双字节表示。
3.对其他语言,比如亚洲语系,还有各种特殊符号,使用3个字节表示;
因此,在应用中程序处理过程中所有字符都是16位(双字节),但在存取转换成字节流时使用UTF-8格式转换,对于英文字符来说和原来用ASCII方式存取

时相比大小仍然是一样的,而对中文来说和原来的GB2312编码方式相比,大小为:(3字节/2字节)=1.5倍,这也是下面导入数据失败的原因。

总结
1.当源端字符编码为ZHS16GBK,目标端编码为AL32UTF8,客户端随便为其中的一种编码,迁移数据不会出现乱码,但是会出现列长度不够现象。反过来不行,因为utf8中的部分字符转换到gbk中肯定会不支持
2.设置了源端客户端编码,仅仅是导出来的dmp文件头部有编码字符标示不一样,存储数据还是按照服务端存储
3.打破神话,exp/imp导入要不乱码,导出和导入的客户端编码要一致

原因分析,解决建议
在导入过程中,最多会发生三次编码转换:
1、执行exp时,数据库中数据的编码会转换为导出客户端编码
2、执行imp时,dmp文件的编码转换为导入客户端编码
3、导入客户端编码转换为目标端数据库的数据库编码

    在exp/imp操作的过程中,经常出现乱码的原因就是编码的相互转换的过程中出现了丢失或者相互不能转换导致。要解决这个问题,最好的办法就是通过NLS_LANG的灵活设置,减少编码转换的次数(如果相邻的转换操作编码一致,那么不会发生编码转换,如试验中的ZHS16GBK编码测试,就没有转换发生),或者使得相互的转换能够兼容,可以最大程度的减少乱码的出现。
    如果已经有了exp导出的dmp文件,然后在导入的过程中,出现乱码,一般的处理建议是nls_lang的编码设置和dmp文件的一致,让转换发生在导入客户端和数据库服务器间(要求:编码可以相互转换)

个人总结(非转载)

恢复备份执行(imp)时将客户端设置字符集和目标数据库字符集编码格式一致
(建议源数据库字符集、客户端、目标数据库字符集三者保持一致)

1)查看数据库字符集设置
sql--select userenv(‘language’)from dual;     oracle中查询的字符集USERENV('LANGUAGE')
我自己的服务器用的是zhs16gbk 中文字符集

image-20230927212934675

1
2
3
4
5
6
7
2)客户端操作系统字符--查询--配置

(1)  cmd-chcp 查询指定代码页。下表列出了所有支持的代码页及其国家(地区)或者语言:
代码页 国家(地区)或语言

936 中国 - 简体中文(GBk)
Windows XP、Windows7、WIN10、WIN sever 2008 R2 操作系统自带的都是GBK字符集(含2万余汉字)

image-20230927212716490

1
2
936即为中文GBK字符集
(2 )查看注册表-win+r--regedit

image-20230927212721215

1
2
3
(3)将客户端添加环境变量。
设置环境变量名NLS_LANG
变量值为SIMPLIFIED CHINESE_CHINA.ZHS16GBK     (一定重启电脑才生效)

image-20230927212724971

  • 解决办法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
可能出现的情况
1. 服务器指定字符集与客户字符集不同,而与加载数据字符集一致。

解决方法:对于这种情况,只需要设置客户端字符集与服务器端字符集一致就可以了

oracle10g UTF8编码:AMERICAN_AMERICA.AL32UTF8
          GBK编码:SIMPLIFIED CHINESE_CHINA.ZHS16GBK
客户端修改为中文是:
在linux的终端上设置环境变量
1.LANG=zh_CN
2.NLS_LANG=zhs16gbk

2. 服务器指定字符集与客户字符集相同,与加载数据字符集不一致。

解决方法:强制加载数据字符集与服务器端字符集一致。

这种情况一般发生在ORACLE版本升级或重新安装数据库时选择了与原来数据库不同的字符集,而恢复加载的备份数据仍是按原字符集卸出的场合。另一种情况是加载从其它使用不同字符集的ORACLE数据库卸出的数据。在这两种情况中,不管客户端字符集与服务器端字符集是否一致都无法正确显示汉字。如:

具体解决方案:
方案一:按服务端字符集的修改方法修改服务端字符集与加载数据字符集一致,然后导入数据。
方案二:利用数据格式转储,避开字符集带来的问题。即先将加载数据倒入到与其字符集一致的数据库中,然后再将数据要么按文本格式导出(数据量较小的情况下),要么通过第三方工具(如POWER BUILDER,ACCESS,FOXPRO等)倒出数据,最后将倒出的数据导入到目标数据库中。

3. 服务器指定字符集与客户字符集不同,与输入数据字符集不一致。

对于这种情况,目前为止都还没有太好的解决方法。
其中有的时候可以尝试通过 iconv -f utf-8 -t gbk filename
从原字符集 utf-8 转换成 gbk

ps:
1、 客户端字符集含义
客户端字符集定义了客户端字符数据的编码方式,任何发自或发往客户端的字符数据均使用客户端定义的字符集编码,客户端可以看作是能与数据库直接连接的各种应用,例如sqlplus,exp/imp等。客户端字符集是通过设置NLS_LANG参数来设定的。

6.ORA-1555快照太旧错误

日志

  • Alert 告警日志文件
1
2
报错信息类似:
ORA-01555: snapshot too old: rollback segment number 107 with name "_SYSSMU107_1253191395$" too small
  • 问题发生时的跟踪日志文件
1
2
3
默认情况,ORA-01555错误发生时不会自动生成跟踪日志文件。但是可以在系统里设置下面的事件,让它在错误发生时同时生成:
alter system set events '1555 trace name errorstack level 3';
设置了1555事件后,一旦错误发生,就会生成相应的日志文件,类似如下:

image-20220815145605140

可能原因

本质:产生ORA-01555错误的根本原因是由于UNDO块被覆盖,查询语句不能获取到查询语句发起时刻所构造的数据副本。

  • 相应行数据的UNDO记录已经过期了

这里说的过期是当前时间(指错误发生时)离数据提交时间已经超过UNDO_RETENTION设定的值。一旦已提交的记录对应的UNDO记录是“expired”状态,就表明这部分空间可以重用,数据可以被覆盖了。

你可能会问,为什么有时候我们遇到ORA-01555错误时,查询语句运行时间比UNDO_RETENTION要长很多,有时候却短很多呢?这没什么明确的答案。

要看具体问题发生时UNDO表空间有多忙,系统负载有多大。

  • 相应行数据的UNDO记录状态并非过期,但仍然被覆盖了

发生这种情况有2个必要条件:

  1. 对UNDO表空间没有设置RETENTION GUARANTEE
  2. UNDO表空间满了

这个时候,“UNEXPIRED”状态的UNDO记录就可能被覆盖了。

  • LOB段的LOB段的读一致性副本不再可用

依赖于LOB字段是怎样配置的,in-row还是out-of-row,in-row方式的LOB字段在UNDO表空间中采用跟普通行一样的UNDO算法。

Out-of-row方式的LOB字段不一样,它的读一致性副本有下面2种控制方式:

1)老的方式:PCTVERSIOIN

这个参数关系到LOB数据的一致读,指的是表lob字段所在的表空间需要预留给lob的前映象使用的最大百分比,默认值是10。也就是说,只要使用不超过10%,LOB字段的前映像的数据是不会被覆盖的。

2)新的方式(自动还原段管理使用):RETENTION

Oracle用UNDO_RETENTION参数来决定在数据库中保留多少已经提交的UNDO数据。这种方式LOB段跟普通段使用相同的过期策略。

在这种方式中,用LOB字段的语句发生ORA-1555的话,意味着:

LOB字段的前映像的使用已经超过PCTVERSION,读一致性数据被覆盖。

或者,LOB字段前映像超过了RETENTION的值,在查询发生时一些行的LOB字段前映像已经被覆盖,于是ORA-1555触发。

解决方案

检查错误日志信息

通过检查告警日志,或者包含ORA-1555错误信息的跟踪日志,可以更详细的看到具体的错误类型:

1)提示回滚段太小

错误提示:

1
ORA-01555: snapshot too old: rollback segment number with name "" too small(注意!!!这里的回滚段名字是空的)

或者,

1
ORA-22924: snapshot too old

这种错误说明是访问的UNDO数据是LOB字段类型。LOB字段访问遇到ORA-1555错误通常是下面几个原因导致的:

  • LOB段损坏

参考MOS文档检查是不是这个问题:

Document 452341.1 ORA-01555 And Other Errors while Exporting Table With LOBs, How To Detect Lob Corruption

  • 如果没有LOB损坏,检查Retention/Pctversion值是不是合适

参考MOS文档,确认是不是需要调高Retention/Pctversion:

Document 846079.1 LOBs and ORA-01555 troubleshooting

错误提示:

1
ORA-01555: snapshot too old: rollback segment number 107 with name "_SYSSMU107_1253191395$" too small

注意,这个错误提示跟上面不一样。”_SYSSMU107_1253191395$”表示UNDO数据是在UNDO表空间的。

这个ORA-1555错误报告是在访问UNDO表空间的UNDO数据时发生错误的,用后续的方法来诊断问题。

2)查询耗时太长

在一些ORA-1555错误发生时,会在alert日志或者是应用日志中出现查询失败时耗时(Duration)的秒数:

ORA-01555 caused by SQL statement below (Query Duration=1974 sec, SCN: 0x0002.bc30bcf7):

如果发现错误日志中,duration=0或者耗时很短,查看下面的文档:

Document 1131474.1 ORA-01555 When Max Query Length Is Less Than Undo Retention, small or 0 Seconds

如果查询耗时超过UNDO_RETENTION值,可以考虑加大UNDO_RETETION,同时要记得调整UNDO表空间大小。

如果查询耗时等于或者接近UNDO_RETENTION,继续看后面的文字。

检查UNDO数据文件

image-20220815151801770

如果UNDO数据文件没有关闭自动扩展功能,可能会导致TUNED_UNDORETENTION值被计算得很高,因此UNDO空间分配是偏向于更多的空间。那怎么破呢?使用UNDO数据文件自动扩展功能,即使存储可用空间足够的时候也带上MAXSIZE。

注意:强烈建议,不要让UNDO表空间里同时存在关闭自动扩展功能的UNDO数据文件和打开自动扩展功能的UNDO数据文件,因为这会导致TUNED_UNDORETENTION计算错乱。

检查 TUNED_UNDORETENTION

image-20220815151844348

1)TUNED_UNDORETENTION < MAXQUERYLEN

这种情况表明UNDO表空间有空间上的压力,实际保留的UNDO记录比理论上的要少。

解决办法是扩大UNDO表空间。

2)TUNED_UNDORETENTION > MAXQUERYLEN,大很多

通常发生在UNDO表空间的数据文件关闭自动扩展选项的情况下。内部的算法是尽可能久的保持UNDO记录,因此TUNED_RETENTION的值就会比较高。

解决办法是把所有的UNDO数据文件设置为自动扩展,并且加上MAXSIZE选项。

长时间运行的查询语句也会把TUNED_RETENTION的值搞得很高。这种情况。就要优化SQL语句来避免保留过多UNDO数据在UNDO表空间了。

通过下面的语句来识别哪些查询语句耗时较久:

image-20220815151920450

3)状态为ACTIVE/UNEXPIRED的UNDO占比很高

image-20220815152014989

下面的几种情况可能会产生大量ACTIVE/UNEXPIRED状态的UNDO:

UNDO_RETENTION或TUNED_UNDORETENTION值很大

在某个特定时间点产生了大量UNDO数据。用下面的语句诊断:

image-20220815152035009

大量的死事务回滚

使用了闪回数据查询

关于状态为ACTIVE的UNDO的信息,可以参阅:

Document 1337335.1 How To Check the Usage of Active Undo Segments in AUM

UNDO_RETENTION

建议将UNDO_RETENTION的值至少设置为MAXQUERYLEN的一半。如果发生了ORA-1555错误,则适当调大。

到此,基本上涉及ORA-01555错误的原理和诊断就说完了。

7.lfiopn failed for file

1
SQL*Loader-522: lfiopn failed for file (xxx.log)

原因:

  • 1、SQL*Loader用户无创建文件的权限;
  • 2、由于目标路径根本不存在,SQL*Loader报此错误(未验证);

业务常用

1.取今年月份,自定义格式,带0,不带0

1
2
select to_char(sysdate,'yyyy"年"mm"月份版本"') from dual; // 2020年04月份版本
select to_char(sysdate,'fmyyyy"年"mm"月份版本"') from dual; // 2020年4月份版本

1.取日期中的年份

1
2
3
4
5
6
select to_number(to_char(sysdate,'yyyy')) from dual;
或者可以直接使用Oracle提供的 Extract 函数
select sysdate from dual; --获得当前系统时间
select extract(year from sysdate) from dual; --获得系统当前年
select extract(month from sysdate) from dual; --获得系统当前月
select extract(day from sysdate) from dual; --获得系统当前日

2.取去年的数据

1
2
# 筛选字段为 2020年4月份版本 ; 取去年数据
select * from qa_adapt_issue_unclosed_loop where substr(project_verison,0,4) = extract(year from sysdate) - 1 

3.去年本月(去年同期)

1
2
# 2020年4月份版本 => 2019年4月份版本
select add_months(SYSDATE,-12),to_char(add_months(SYSDATE,-12),'fmyyyy"年"mm"月份版本"' from dual

4.oracle 去年第一天

1
2
3
4
# 返回日期格的话
select trunc(add_months(sysdate,-12),'year') from dual
# 返回字符格式的话
select to_char(trunc(add_months(sysdate,-12),'year'),'yyyy-mm-dd') from dual

5.是否包含某个字符串

1
select * from table where instr(t.name,'天涯')>0   

6.去除首尾指定字符串

1
SELECT LTRIM('thetomsbthhe', 'the'),RTRIM('thetomsbthhe', 'the') FROM dual;

7.今天是今年的第几天

1
select trunc(sysdate) - trunc(sysdate,'YYYY')+1 from dual

8.今年天数

1
SELECT ADD_MONTHS(TRUNC(SYSDATE, 'YYYY'), 12) - TRUNC(SYSDATE, 'YYYY') days FROM DUAL

9.造一行的数据,转为一列多行

1
2
3
with a as (select '/ABC/AA/AD/ABD/JI/CC/ALSKD/ALDKDJ' id from dual)
select regexp_substr(id,'[^/]+',1,rownum) id from a
connect by rownum <= length(regexp_replace(id,'[^/]+'))

10.oracle小数点补0

1
2
select to_char(0.12,'fm9999990.9999') || '%' from dual;
SELECT regexp_replace(l_num, '^\.', '0.') FROM dual

11.拿到最近一周的日期

1
2
3
4
5
6
7
8
9
10
11
12
13
  SELECT
    to_char (SYSDATE- LEVEL + 1, 'yyyy-mm-dd') createTime  
  FROM
    DUAL connect BY LEVEL <= 7

# createtime
# 2020-03-06
# 2020-03-05
# 2020-03-04
# 2020-03-03
# 2020-03-02
# 2020-03-01
# 2020-02-29

12.上周1

1
2
select *  from hrm_daily_monitor_data_detail_foreign 
where version between to_char(trunc(sysdate,'iw')-7,'yyyy/mm/dd') and to_char(trunc(sysdate,'iw')-1,'yyyy/mm/dd')

13.取第一条和最后一条

1
2
3
4
5
6
SELECT
    DISTINCT
    LAST_VALUE (E_VALUE) OVER (PARTITION BY E_CODE ORDER BY E_DATE ROWS BETWEEN unbounded preceding AND unbounded following) AS LAST_TIME_VALUE,
    FIRST_VALUE (E_VALUE) OVER (PARTITION BY E_CODE ORDER BY E_DATE ROWS BETWEEN unbounded preceding AND unbounded following) AS FIRST_TIME_VALUE
FROM
    TABLE_TEST

14.取上一条和下一条

1
2
3
4
# lag 上一条 lead 下一条
select 
version,lag(version,1,0) over(order by version),lead(version,1,0) over(order by version)
from hrm_capacity_workday_chart

15.判断为一周中第几天

1
2
select to_char(sysdate ,'d') from dual; -- 周末 1 周一2 周二3 周三4 周四5 周五6 周六7
select to_char(sysdate - 1,'d') from dual

16.模糊查询

1
2
3
and e.empId like CONCAT('%',#{empId},'%')  X Oracle模糊查询CONCAT参数个数无效
and e.empId like CONCAT(CONCAT('%',#{empId}),'%') √
and e.empId like '%' || #{empId} ||'%'; √

17.查看数据库字符集

1
2
SELECT value$ FROM sys.props$ WHERE name = 'NLS_CHARACTERSET' ;
# 如果是UTF-8 汉字3个字符,即最多1333个汉字,GBK 汉字2个字符 2000个汉字

18.根据季度获取所有月份

1
2
3
4
5
6
7
8
9
10
11
上季度所有月份:
SELECT TO_CHAR(ADD_MONTHS(ADD_MONTHS(TRUNC(SYSDATE, 'YYYY'),A * 3),-ROWNUM),'YYYYMM')  LAST_Q  
    FROM (SELECT TO_CHAR(SYSDATE,'Q')-1 A FROM DUAL)  
    CONNECT BY ROWNUM <= 3  
    ORDER BY 1;  

本季度所有月份:
SELECT TO_CHAR(ADD_MONTHS(ADD_MONTHS(TRUNC(SYSDATE, 'YYYY'),A * 3),-ROWNUM),'YYYYMM')  LAST_Q  
    FROM (SELECT TO_CHAR(SYSDATE,'Q') A FROM DUAL)  
    CONNECT BY ROWNUM <= 3  
    ORDER BY 1;

oracle一些用的复杂效果

19.connect by

1
2
3
4
5
6
7
8
9
我有多行结果集,有部分行是用分割符隔开的.
想要: 单列结果集,分割符转为多行。

select REGEXP_SUBSTR(A, '[^,]+', 1, LEVEL) A,B,C
from T
CONNECT BY LEVEL <= REGEXP_COUNT(A, '[^,]+')
and rowid= prior rowid
and prior dbms_random.value is not null;
ps:最后2个and防止重复行

ps: oracle 时间字符串比较的陷阱

比如时间字符串字段 > ‘2020年6月版本’。这样10月11月12月是筛选不出来的。!!!

20.substr

1
2
oracle去掉最后一个字符,
select  a, substr(a,1,length(a) -1 ) from t_table1

需求:

  • 格式为20191031这样的,统计数据库中的月份,20191001-20191031都算10月

SUBSTR(string,start, [length])函数:截取字符串, 从1开始查找。如果start是负数,则从string字符串末尾开始算起.

1
select distinct substr(mod_date,1,6) as version grom git_commit_rel_dtl order by version
  • 存在的月份查询出来后,倒序,并且保留当前月份及前3个月的月份
1
2
select to_char(sysdate,'yyyyMM') from dual -- 当前月份
取前几个,用子查询,先把排序后的结果查询出来,然后用select * from (子查询) where rownum <= 4;

21.查询某月的全部数据

查询全月的数据时,我们应该明白此查询只和年月有关和日无关,以下提供两种查询方法

1
2
3
4
5
6
7
第一种判断数据的年月相同即可
select * from TABLE t
where to_char(DateTime,'YYYY/MM') = '2016/01'

第二种方法直接对月份进行查询
select * from TABLE t
WHERE to_number(to_char(DateTime,'MM')) = '03' // 不过这种查询只能在同一年内进行

22.将varchar2(4000)改成CLOB大字段

如果直接将varchar2改为clob类型,会报错, 注:https://blog.csdn.net/cai7095576/article/details/23999549

  • 把表DT_CORP_ENTERPRISE中ASSetsinvestmen字段(varchar2,长度为4000)改为clob类型
1
2
3
4
5
6
7
8
-增加大字段项
alter table DT_CORP_ENTERPRISE add hehe clob;
--将需要改成大字段的项内容copy到大字段中
update DT_CORP_ENTERPRISE set hehe=ASSetsinvestment;
--删除原有字段
alter table DT_CORP_ENTERPRISE drop column ASSetsinvestment;
--将大字段名改成原字段名
alter table DT_CORP_ENTERPRISE rename column hehe to ASSetsinvestment;

我整合邮件服务的时候,要发送HTML邮件,用到大字段

23.需求:截取-及其以前的字符串

1.5有个需求里面的小需求是这样的,把一个字段的’-‘及其之前的字符格式化掉,其他保持不变。

1
select subnstr('上海一部-数据湖',instr('上海一部-数据湖','-')+1, length('上海一部-数据湖')) from dual

解读: substr截取, instr取到带有-的坐标(比如这里,返回5,所以,+1从6开始截取;如果没有,返回0,+1,从1开始截取.perfect!). length是拿到字符串长度。因为截取是[),所以,不用-1.

完整代码及截图

image-20230927213011169

  • 截取括号之间的字符串
1
2
3
4
5
6
7
select 
substr(
    name,
    instr(name,'(',1,1)+1,
    instr(name,')',1,2)-1-instr(name,'(',1,1)

from test;

24.生成列表

1
2
3
4
5
6
7
8
9
10
11
12
13
--小时列表
SELECT to_date('2013-07-01 12', 'yyyy-mm-dd hh24') + (ROWNUM - 1) / 24 sdate FROM dual
CONNECT BY ROWNUM <= (to_date('2013-07-02 22', 'yyyy-mm-dd hh24') - to_date('2013-07-01 12', 'yyyy-mm-dd hh24')) * 24 + 1;
--天列表
SELECT to_char(daylist,'yyyy-MM-dd') daylist from(SELECT TO_CHAR(TO_DATE('2020-07-01', 'yyyy-MM-dd') + ROWNUM - 1, 'yyyy-MM-dd') as daylist
FROM DUAL CONNECT BY ROWNUM <=trunc(to_date('2020-07-06', 'yyyy-MM-dd') -to_date('2020-07-01', 'yyyy-MM-dd')) + 1);
--月份列表
SELECT to_char(add_months(to_date('2013-01', 'yyyy-mm'), ROWNUM - 1), 'yyyy-mm') day_id FROM dual
CONNECT BY ROWNUM <= months_between(to_date('2013-07', 'yyyy-mm'), to_date('2013-01', 'yyyy-mm')) + 1;

--年份列表
SELECT TO_CHAR(ADD_MONTHS(TO_DATE('2014-10', 'yyyy-MM'), (ROWNUM - 1) * 12),'yyyy') as yearlist FROM DUAL
CONNECT BY ROWNUM <=months_between(to_date('2015-06', 'yyyy-MM'),to_date('2014-10', 'yyyy-MM')) / 12 + 2;

25.日期格式问题

错误:oracle input value not long enough for date format

1
2
3
4
5
6
7
8
9
10
11
12
13
14
-- create_time 的时间为 2019/3/26 23:00:00
SELECT *
FROM T_CLASS T
WHERE T.CREATE_TIME BETWEEN '2019-03-01' AND '2019-03-26';

1. Oracle 对字符串进行日期转换时,如果是年月日的形式,即“2019-03-26”,将会被转为“2019-03-26 00:00:00”
2. Oracle 在进行日期比较时,最好直接指定日期比较的格式,不要进行隐式转换

对 Oracle 日期字段的查询,建议通过 to_char 或者 to_date 这两个方法将相应的数据进行格式化。
对数据进行格式化的好处:

1. 可以使查询语句一目了然。
2. 避免因为隐式转换带来的业务逻辑上的风险。
3. 避免了因为调试问题而导致的时间浪费(如果不是熟悉Oracle,边界问题也是比较难发现的)
1
我是在报餐时间段导出的时候遇到该问题.我将表中的字段to_Date,有to_char.最后是时间格式后饮用失败的问题

https://developer.aliyun.com/article/695320

26.去重

去重

1
2
3
4
select * from 
(
    select t.*,row_number() over(partition by dep order by time) rn from t
) where rn = 1

27.多行想要再一行内拼接

  • 多行想要再一行内拼接
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
oracle没有WM_CONCAT函数问题用oracle自带脚本重建wmsys.wm_concat函数

方案一:wm_concat函数
select username, id, wmsys.wm_concat(subject) as subject, wmsys.wm_concat(score) as score
from STUDENTSCORES
group by username, id

方案二:listagg函数
select username, id, LISTAGG(subject, '-') within group(order by subject) as subject, LISTAGG(score, ',') within group(order by score) as score
from STUDENTSCORES
group by username, id

select username, id, translate(ltrim(subject, '/'), '*/', '*,') as subject,translate(ltrim (score, '/'), '*/', '*,') as score
from
(select row_number() over (partition by username, id order by username, id, lvl desc) as rn, username, id, subject, score
from
(select username, id, level lvl,                            sys_connect_by_path (subject, '/') as subject, sys_connect_by_path (score, '/') as score
from
(select username, id, subject, score,                                       row_number() over (partition by username,id order by username, id) as num from STUDENTSCORES order by username, id)
connect by username = prior username and id = prior id and num - 1 = prior num))
where rn = 1;

注意:
方案一中默认分隔符为 ‘,’
方案二只适合11g之后的版本

28.获取时间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 获取某一日期所在周的周一
1.select trunc(日期,'d')+1 from dual; --以周日为一周起始日期
2.select trunc(日期,'iw') from dual; –-以周日为一周结束日期

# 当前时间做加减
当前时间减去7分钟的时间
select  sysdate,sysdate - interval '7' MINUTE  from dual
当前时间减去7小时的时间
select  sysdate - interval '7' hour  from dual
当前时间减去7天的时间
select  sysdate - interval '7' day  from dual
当前时间减去7月的时间
select  sysdate,sysdate - interval '7' month from dual
当前时间减去7年的时间
select  sysdate,sysdate - interval '7' year   from dual
时间间隔乘以一个数字
select  sysdate,sysdate - 8 *interval '2' hour   from dual

# 格式化日期
日,月只有一位小数的只显示一位,不自动加0,在dd/mm 前面加上fm即可去掉前面的0
select to_char(t,'YYYY/fmMM/fmDD') from testdata

注意点:

1
2
3
4
# 2个日期字符串比较
2020/05/04(字符串) 和 2020/5/06(日期类型)
要先将不带0的转为相同格式的字符串  to_char('2020/5/06','yyyy/mm/dd') => 2020/05/06
然后to_date比较  to_date('2020/05/04','yyyy/mm/dd') <= to_date('2020/05/06','yyyy/mm/dd')

29.查询和插入时间字段

oracle 查询和插入时间字段

  • 错误写法
1
2
3
<select id="select" resultType="java.util.Map"> 
    SELECT CREATED_TIME FROM 表A 
</select>
  • 正确查询
1
2
3
<select id="select" resultType="java.util.Map"> 
    SELECT to_char(CREATED_TIME,'yyyy-mm-dd hh24:mi:ss') CREATED_TIME FROM 表A 
</select>
  • 正确插入
1
2
3
4
<insert id="insert" parameterType="java.util.Map">
    INSERT INTO 表A (CREATED_TIME)
    VALUES( to_date(#{CREATED_TIME},'yyyy-mm-dd hh24:mi:ss') )
</insert>

30.逗号分割的字符串转换为可放入in的语句

将逗号分割的id作为in的条件查询后拼接为一行

1
2
3
根据规则id,查询对应的决策树,
规则id可能有多个,以逗号分割。
将多个id对应的多条决策树拼接为一条结果返回

参考

31.Oracle逗号拼接的字段转为多行

image-20220703035332180

image-20220703035251316

如果想要部分行汇总,在所有行中占比,可以参考上面

image-20220703035214446

32.时间计算

1
2
3
4
5
6
7
select sysdate,add_months(sysdate,-12) from dual;        --减1年 
select sysdate,add_months(sysdate,-1) from dual; --减1月
select sysdate,to_char(sysdate-7,'yyyy-mm-dd HH24:MI:SS') from dual; --减1星期
select sysdate,to_char(sysdate-1,'yyyy-mm-dd HH24:MI:SS') from dual; --减1天
select sysdate,to_char(sysdate-1/24,'yyyy-mm-dd HH24:MI:SS') from dual; --减1小时
select sysdate,to_char(sysdate-1/24/60,'yyyy-mm-dd HH24:MI:SS') from dual; --减1分钟
select sysdate,to_char(sysdate-1/24/60/60,'yyyy-mm-dd HH24:MI:SS') from dual; --减1秒

33.星期比较的坑

2段不同的SQL在不同人的笔记本上执行的结果不一样。原因是2个人的NLS_LANG设置不一样,星期转换后一个是中文,一个是英文。用中文判断星期做聚合,所以结果不一样。

NLS_LANG=AMERICAN_AMERICA.ZHS16GBK

NLS_LANG=SIMPLIFIED CHINESE

1
2
3
4
5
6
7
8
9
10
11
一、显示中文星期天
select to_char(sysdate,'day','NLS_DATE_LANGUAGE=''SIMPLIFIED CHINESE''') from dual;

二、显示英文星期天
select to_char(sysdate,'day','nls_date_language=american') from dual;

三、显示中文月份
select to_char(sysdate,'month','NLS_DATE_LANGUAGE=''SIMPLIFIED CHINESE''') from dual;

四、显示英文月份
select to_char(sysdate,'month','nls_date_language=american') from dual;

解决问题

查询表空间

一般来说表的大小超过2G可以考虑分区表,怎么看表的大小?

1
2
3
4
5
select t.segment_name, t.segment_type, sum(t.bytes / 1024 / 1024) "占用空间(M)"
from dba_segments t
where t.segment_type='TABLE'
and t.segment_name='TABLE_NAME'
group by OWNER, t.segment_name, t.segment_type;

存储过程编译卡死

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
1:查V$DB_OBJECT_CACHE
SELECT * FROM V$DB_OBJECT_CACHE WHERE name='CUX_OE_ORDER_RPT_PKG' AND LOCKS!='0';

注意:CUX_OE_ORDER_RPT_PKG 为存储过程的名称。
发现 locks=2

2:按对象查出sid的值
select /*+ rule*/ SID from V$ACCESS WHERE object='CUX_OE_ORDER_RPT_PKG';

注意:CUX_OE_ORDER_RPT_PKG 为存储过程的名称。

3:查sid,serial#
SELECT SID,SERIAL#,PADDR FROM V$SESSION WHERE SID='刚才查到的SID';

4alter system kill session 'sid值,serial#值' immediate;

锁表也是要杀掉sid

数据迁移

导出数据-spool

在sqlplus中用来保存或打印查询结果。(可以保存到文件)

设置说明

1
2
3
4
5
6
7
8
9
10
11
set colsep',';    //域(列)输出分隔符 
set echo off;    //不显示start启动的脚本中的每个sql命令,缺省为on
set feedback off;  //不回显本次sql命令处理(查询/修改)的记录条数,缺省为on
set heading off;   //不输出域(列)标题,缺省为on
set pagesize 0;   //输出每页行数,缺省为24,为了避免分页,可设定为0。
set termout off;   //不显示脚本中的命令的执行结果,缺省为on
set trimout on;   //去除标准输出每行的拖尾空格,缺省为off
set trimspool on;  //去除重定向(spool)输出每行的拖尾空格,缺省为off
set term off; //不在屏幕上显示
set linesize 10000; //设置行宽,根据需要设置,默认100
set wrap off; //让它不要自动换行,当行的长度大于LINESIZE的时候,超出的部分会被截掉。

PL/SQL下使用spool脚本导出txt

spool.sql脚本如下:

1
2
3
4
5
6
7
8
SPOOL D:\测试.txt 
set echo off --不显示脚本中正在执行的SQL语句
set feedback off --不显示sql查询或修改行数
set term off --不在屏幕上显示
set heading off --不显示列
set linesize 1000; //设置行宽,根据需要设置,默认100
select AAB301||','||AAE002|| ',' ||AAC001|| ',' ||AAE252|| ',' ||AAE091|| ',' ||AAE020|| ',' ||AAE022 FROM JGCA; --需要导出的数据查询sql
SPOOL OFF

导入数据

不常用但是可能会用的命令

1、查询Oracle并发数、会话数、连接数:

1
2
3
4
5
select count(*) from v$session #当前的连接数
select count(*) from v$session where status='ACTIVE' #并发连接数
select value from v$parameter where name = 'processes' #数据库允许的最大连接数
show parameter processes #最大连接
select username,count(username) from v$session where username is not null group by username; #查看不同用户的连接数

2、查询、修改最大连接数

1
2
3
4
5
6
alter system set processes = 300 scope = spfile;
#注意:修改后需要重启数据库才能生效
#通过控制台进入SQL窗口
sqlplus / as sysdba
shutdown immediate;
startup;

3、查看oracle日志路径

1
select * from v$logfile;

4、查询最近所有的SQL操作

1
select * from v$sql;

5.查看oracle数据库的编码

1
select * from nls_database_parameters where parameter ='NLS_CHARACTERSET';

6.查看oracle表和表注释

1
2
3
4
5
6
7
8
9
10
11
12
1.根据表名注释查询表
select * from all_tab_comments where comments like%姓名表注释%

2.根据字段注释查询表
select * from all_col_comments t
where comments like%张三注释%

3.根据表名查询表
select * from all_tables where table_name=‘表名’

4.查询表和注释
select table_name,comments from user_tab_comments

7.数据恢复

1
select * from sys_user as of timestamp to_timestamp('2022/8/5 9:10:45','yyyy-mm-dd hh24:mi:ss');

8.查询今天是一个季度的第几天

1
SELECT trunc(sysdate)-TRUNC(SYSDATE,'Q') -1 FROM dual;

9.计算当前季度有多少天

1
2
3
4
5
6
SELECT
ADD_MONTHS(TRUNC(SYSDATE, 'Q'), -3) AS First,
TRUNC(SYSDATE, 'Q') - 1 AS Last,
# 最后一天减去第一天就行
TRUNC(SYSDATE, 'Q') - 1 - ADD_MONTHS(TRUNC(SYSDATE, 'Q'), -3)
FROM DUAL

不常用但是可能会用的技巧

1.模糊查询关联

1
2
SELECT a.nameA,b.nameB
FROM a LEFT JOIN b on instr(b.nameB,a.nameA)>0

image-20220922201911832

2.模糊语音查询分组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
with your_table (HumanName) as (
select 'Kamil' from dual union all
select 'Azer' from dual union all
select 'John' from dual union all
select 'Elmir' from dual union all
select 'Kamal' from dual union all
select 'Elmar' from dual union all
select 'Orxan' from dual union all
select 'Elnar' from dual
)
------ Test data setup ends here ------


select
listagg(humanname,',') within group (order by humanname) nameswhichlikes
from your_table
group by soundex(humanname)
having count(*) > 1;

image-20220922202742368

3.统计字符串中的字符出现次数

1
2
select REGEXP_COUNT('1,2,6,8,7,9',',') from dual
结果:5

4.模糊分组

比如有应用F-CSDM,想要把包含F-CSDM的,比如F-CSDM-VOC,都算到F-CSDM,并分组。

image-20230927205305890

切割字符按串完成列转行

目标: 切割字符串完成列转行(一列转成多行) 要点:regexp_substr 函数,connect by 子句,LEVEL 伪列
说明:加入伪列是为了说明 level 在语句中充当的责任。学过java或者C语言的可以认为level就是,i =1,i++。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
方法一

select regexp_substr('110,112,113,114','[^,]+',1,LEVEL),LEVEL from dual CONNECT BY LEVEL <5 ;

结果:

110 1
112 2
113 3
114 4

方法二

SELECT regexp_substr('110,112,113,114','[^,]+',1,LEVEL),LEVEL FROM dual
CONNECT BY regexp_substr('110,112,113,114','[^,]+',1,LEVEL) IS NOT NULL;

结果

110 1
112 2
113 3
114 4

举例,输出一年12个月,转行记录

select regexp_substr('2018-01,2018-02,2018-03,2018-04,2018-05,2018-06,2018-07,2018-08,2018-09,2018-10,2018-11,2018-12','[^,]+',1,LEVEL),LEVEL from dual CONNECT BY LEVEL <=12 ;

结果:

2018-01 1
2018-02 2
2018-03 3
2018-04 4
2018-05 5
2018-06 6
2018-07 7
2018-08 8
2018-09 9
2018-10 10
2018-11 11
2018-12 12

即可使用中间表左连接之类的相关查询了,在做统计的时候还是比较有用的。

逗号分割的字符串区分1和10过滤

方案1

image-20221025113506818

方案2:更巧妙

image-20221025113459504

unpivot 列转行

image-20230314092711831

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
SELECT 
STU_NAME, TERM, subject, score
FROM (
SELECT '罗飞' STU_NAME,
'2001-2002' TERM,
'90' 微积分,
'88' 线性代数,
'85' 数据结构,
'70' 操作系统
FROM DUAL
)
UNPIVOT(score FOR subject IN(微积分,
线性代数,
数据结构,
操作系统))

image-20230314092700900

支持别名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
SELECT 
STU_NAME, TERM, subject, score
FROM (
SELECT '罗飞' STU_NAME,
'2001-2002' TERM,
'90' 微积分,
'88' 线性代数,
'85' 数据结构,
'70' 操作系统
FROM DUAL
)
UNPIVOT(score FOR subject IN(微积分 as 'wjf',
线性代数 as 'xxds',
数据结构 as 'sjjg',
操作系统 as 'czxt'))
order by score

image-20230314092818019

pivot 行转列

image-20230314093149040

1
2
3
4
5
6
7
8
9
10
select 
studentcode, studentname, 数学, 语文 ,英语
from
(
select studentcode, studentname, score, subject from stu
) a
pivot(
sum(score) for subject in('数学' 数学,'语文' 语文 , '英语' 英语)
)

image-20230314093239890

计算当前月份是一个季度中的第几个月份

1
mod( (to_char(sysdate,'fmMM')+2,3 )+1

计算上个月的最后一天IMG_0146

1
trunc(sysdate,'MM') -1

难点

自定义函数在大结果集中调用性能慢

oracle自定义函数,然后在行记录比较多的表中调用处理,速度回慢很多很多,如何优化?

目前将函数内容直接调用,但是这样代码可读性很差

或者在mybatis java层,将函数的逻辑用java处理好,作为参数传进去? 时间不够,来不及测试这种情况。理论上可行。