Oracle数据库--从入门到装逼

目录

1 Oracle基础

1.1 Oracle简介

1.1.1 什么是Oracle

  Oracle数据库系统是美国Oracle公司(甲骨文)提供的以分布式数据库为核心的一组软件产品,是目前最流行的客户服务器(CLIENT/或B/S体系结构的数据库之一。
  Oracle通常应用于大型系统的数据库产品。
  Oracle数据库是目前世界上使用最为广泛的数据库管理系统,作为一个通用的数据库系统,它具有完整的数据管理功能;作为一个关系数据库,它是一个完备关系的产品;作为分布式数据库它实现了分布式处理功能
Oracle数据库具有以下特点:

  1. 支持多用户、大事务量的事务处理,处理并发能力狠强
  2. 数据安全性和完整性控制,在安全设计方面没有任何数据库可与之相比
  3. 支持分布式数据处理,一个实例部署到多台机器时由Oracle自身实现分布式
  4. 可移植性,在不同系统中的数据文件类型是一致的,可在不同版本系统做数据移植

1.1.2 Oracle体系结构

1.数据库
  Oracle数据库是数据的物理存储。这就包括(数据文件ora或者dbf、控制文件、联机日志、参数文件)。其实Oracle数据库的概念和其它数据库不一样,这里的数据库是一个操作系统只有一个库(而MySQL是在一个实例下可创建多个库)。可以看作是Oracle就只有一个大数据库。
2.实例
  一个Oracle实例(OracleInstance)由一系列的后台进程(BackguoundProcesses)和内存结构(MemoryStructures)组成。一个数据库可以有n个实例(一般使用时通常只建一个实例)。
3.数据文件(dbf)
  数据文件是数据库的物理存储单位。数据库的数据是存储在表空间中的,数据真正是在某一个或者多个数据文件(dbf)中。而一个表空间可以由一个或多个数据文件组成,一个数据文件只能属于一个表空间。一旦数据文件被加入到某个表空间后,就不能删除这个文件,如果要删除某个数据文件,只能删除其所属于的表空间才行。
4.表空间
  表空间是Oracle对物理数据库上相关数据文件(ora或者dbf文件)的逻辑映射。一个数据库在逻辑上被划分成一到若干个表空间,每个表空间包含了在逻辑上相关联的一组结构。每个数据库至少有一个表空间(称之为system表空间)。
  每个表空间由同一磁盘上的一个或多个文件组成,这些文件叫数据文件(datafile)。一个数据文件只能属于一个表空间。
在这里插入图片描述
注:表的数据,是由用户放入某一个表空间的,而这个表空间会随机把这些表数据放到一个或者多个数据文件(dbf)中。Oracle是由用户和表空间对数据进行管理和存放的。但是表不是由表空间去查询,而是由用户去查。因为不同用户可以在同一个表空间建立同一个名字的表,这里就是以用户作区分了。
在这里插入图片描述
5.用户
  用户是在表空间下建立的。用户登陆后只能看到和操作自己的表, Oracle的用户与MySQL的数据库类似,每建立一个应用需要创建一个用户。
在这里插入图片描述

1.2 Oracle安装与配置

本次演示安装Oracle 11g,安装环境为Windows10 64位专业版

1.2.1 安装前准备

下载地址:https://www.oracle.com/database/technologies/oracle-database-software-downloads.html
在这里插入图片描述
下载的这两个文件,将两个压缩包解压到同一路径下,使解压后的文件合并,解压后steup.exe安装
在这里插入图片描述
安装前务必要关闭各种杀毒软件,安全卫士

1.2.2 安装步骤

安装步骤可参考https://www.cnblogs.com/gengshao/p/10764248.html

1.2.3 可能出现的问题及处理

1.2.3.1 Environment variable:“PATH”

Environment variable: “PATH” - This test checks whether the length of
the environment variable “PATH” does not exceed the recommended
length. 预期值 : 1023 实际值 : 1184 错误列表: PRVF-3929 : Adding the Oracle
binary location to the PATH environment variable will exceed the OS
length limit of [ “1023” ] for the variable on the node “ABC” - Cause:
The installer needs to update the PATH environment variable to include
the value “%ORACLE_HOME%/bin;”. However, doing so will cause PATH to
exceed the maximum allowable length that this operating system allows.

  • Action: Ensure that the sum of the lengths of your current PATH environment variable and that of “%ORACLE_HOME%/bin;” does not exceed
    the operating system limit. Restart the installer after correcting the
    setting for environment variable

改 cvu_prereq.xml 里面 的配置,cvu_prereq.xml 文件在oracle安装解压包database\stage\cvu目录中找到自己对于的电脑系统,我的是win10的,没有的可以填加下面的内容(在CERTIFIED_SYSTEMS标签下)

<OPERATING_SYSTEM RELEASE="6.2">
	<VERSION VALUE="3"/>
	<ARCHITECTURE VALUE="64-bit"/>
	<NAME VALUE="Windows 10"/>
	<ENV_VAR_LIST>
		<ENV_VAR NAME="PATH" MAX_LENGTH="2023" />
	</ENV_VAR_LIST>
</OPERATING_SYSTEM>

把1023改大 比如2023 然后退出安装程序重新安装下就ok了

1.2.3.2 执行先决条件失败

在这里插入图片描述
处理方式请参考https://www.cnblogs.com/xiaoxiaoweng/p/7277969.html

1.2.3.3 实例化EM配置文件时出错

安装时如果没有关闭杀毒软件便可能出现此问题
在这里插入图片描述
处理方式1:重新配置EM
配置方式可参考https://blog.csdn.net/ajphsb0849/article/details/101285448
处理方式2:卸载Oracle,重新安装
卸载方式可参考
https://blog.csdn.net/Fisher_lht/article/details/78797141
https://blog.csdn.net/m0_38025207/article/details/80717071

1.2.4 可视化工具PL/SQL Developer

PL/SQL Developer下载、安装、使用教程请参考
https://blog.csdn.net/sinat_34104446/article/details/80330021

1.2.5 创建/删除表空间

create tablespace hellospace	--创建名为hellospace的表空间
datafile 'E:\Repository\hellospace.dbf'	--表空间对应的数据文件是hellospace.dbf
size 100 m	--数据文件的大小是100兆
autoextend on	--当数据超过100兆时自动增长
next 10 m;	--数据文件自动增长每次增长10兆
drop tablespace hellospace;	--删除表空间hellospace,不会关联删除数据文件

1.2.6 创建用户

create user bigboss	--创建名为bigboss的用户
identified by 666666	--用户密码是666666
default tablespace hellospace;	--该用户默认的表空间是hellospace,一个表空间下可创建多个用户

1.2.7 用户授权

grant dba to bigboss;	--给bigboss用户添加dba权限

1.3 表的创建、修改与删除

1.3.1 创建表

语法:

CREATE TABLE 表名称(
    字段名 类型(长度) primary key,
    字段名 类型(长度),
    .......
);

常用数据类型说明:

  1. 字符型
    CHAR:固定长度的字符类型,最多存储 2000 个字节,若存入的字节数不够设定的字节长度,会以空格补齐,取出时也会带空格
    VARCHAR2:可变长度的字符类型,最多存储 4000 个字节,类似MySQL的varchar
    LONG : 大文本类型,最大可以存储 2个G,类似MySQL的text类型,可存储长篇文章
  2. 数值型
    NUMBER:数值类型
    例如:
      NUMBER(5) 最大可以存的数为 99999
      NUMBER(5,2)最大可以存的数为 999.99
  3. 日期型
    DATE:日期时间型,精确到秒
    TIMESTAMP:精确到秒的小数点后9位
  4. 二进制型(大数据类型)
    CLOB : 存储字符,最大可以存4个G,当字符长度超过LONG的长度时,可使用该字段
    BLOB:存储图像、声音、视频等二进制数据,最多可以存4个G
    实例:
create table hero (
    id number primary key,
    name varchar2(30),
    price number(10,2),
    birthday date
);

1.3.2 修改表

增加字段语法:

ALTER TABLE 表名称 ADD( 
    列名1 类型 [DEFAULT 默认值],
    列名1 类型 [DEFAULT 默认值],
    ...
);

例如:给hero表追加两个字段

ALTER TABLE HERO ADD(
  REMARKS VARCHAR2(20),
  SEX VARCHAR2(30)
);

修改字段类型或长度语法:

ALTER TABLE 表名称 MODIFY( 
    列名1 类型 [DEFAULT 默认值],
    列名1 类型 [DEFAULT 默认值],
    ...
);

例如:修改hero表刚添加的两个字段的类型或长度

ALTER TABLE HERO MODIFY(
    REMARKS VARCHAR2(50),
    SEX CHAR(1)
);

修改字段名语法:

ALTER TABLE 表名称 RENAME COLUMN 原列名 TO 新列名

例如:修改hero中的sex字段名修改为gender

ALTER TABLE HERO RENAME COLUMN SEX TO GENDER

删除字段名语法:

--删除一个字段
ALTER TABLE 表名称 DROP COLUMN 列名
--删除多个字段
ALTER TABLE 表名称 DROP (列名 1, 列名 2...)

1.3.3 删除表

删除表语法:

DROP TABLE 表名称

1.4 数据增删改

1.4.1 插入数据

插入数据语法:

INSERT INTO 表名 [列名1,列名2,...)] VALUES(1,2,...);
COMMIT;

例如:

INSERT INTO HERO VALUES (1,'典韦',9000.50,sysdate,'战士','1');
COMMIT;

注:语句中的 sysdate 是系统变量用于获取当前日期,执行INSERT后一定要再执行commit提交事务hero表插入数据

1.4.2 修改数据

修改数据语法:

UPDATE 表名 SET 列名1=1,列名2=2,...WHERE 修改条件;
COMMIT;

需求:将hero表中id为1的英雄的birthday日期更改为三天前的日期

update hero set birthday=birthday-3 where id=1;
commit;

1.4.3 删除数据

删除语法1:

DELETE FROM 表名 WHERE 删除条件;
COMMIT;

需求:删除hero表中id为1的数据

delete from hero where id=1;
commit;

删除语法2:

TRUNCATE TABLE 表名称;

需求:删除hero表中所有数据

delete from hero;
commit;

TRUNCATE TABLE HERO;

比较truncat与delete实现数据删除:

  • delete删除的数据在commit之前可以执行rollback回滚,delete操作会将被删除的数据放入表空间的回滚段,然后若执行rollback,会将数据从回滚段恢复到表中,若执行commit,则将数据从回滚段删除最终删除数据。delete删除可能产生碎片,并且不释放空间
  • truncate是先摧毁表结构,再重构表结构,truncate操作可释放空间,truncate删除的执行效率更高。

1.5 Java通过JDBC操作Oracle

1.5.1 创建工程,引入驱动包

查找驱动包请参考https://www.cnblogs.com/tv151579/archive/2013/02/26/2933405.html
在eclipse中创建 java 工程 ,建立 lib 文件夹,将驱动包拷贝到此文件夹,然后 add→build path

1.5.2 编写数据访问类

package com.example.dao;
import java.sql.SQLException;
/** 基本数据访问类 */
public class BaseDao {
    //加载驱动
    static {
        try { 
            Class.forName("oracle.jdbc.driver.OracleDriver");
        } catch (ClassNotFoundException e) { 
            e.printStackTrace();
       }
    }
    /** 获取数据库连接 */
    public static java.sql.Connection getConnection() throws SQLException {
        return  java.sql.DriverManager.getConnection(
        "jdbc:oracle:thin:@192.168.80.10:1521:orcl", "system",  "123456");
    }
    /** 关闭资源 */
    public static void closeAll (java.sql.ResultSet rs, java.sql.Statement stmt,
                            java.sql.Connection conn){
        //关闭结果集
        if(rs!=null){
            try { 
                rs.close();
            } catch (SQLException  e)  {
                e.printStackTrace();
            }
        }
        //关闭执行对象
        if(stmt!=null){
            try {
                stmt.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        //关闭执行对象
        if(conn!=null){
            try {
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

注:jdbc:oracle:[email protected]:1521:orcl中的thin表示瘦连接,此连接方式不需要连接方具备Oracle客户端或相关环境
瘦连接
  大概是Java应用程序、JSP、EJB (Enterprise Java Beans,企业级Java Bean)等最常用的一种连接方式了,它为不直接访问Oracle库文件而创建代码提供了许多便利。
胖连接:
  和Oracle JDBC瘦驱动器相比,Oracle调用接口(OCI)驱动与Oracle C/C++库耦合得更加紧密。如果要使用Oracle调用接口,我们需要保证PATH、CLASSPATH和LD_LIBRARY_PATH环境变量映射到了Oracle库。这些库都需要基于相同的物理平台或通过存储区域网络(SAN)映射,如UNIX中的NFS

1.5.3 增删改代码编写

1.创建实体类

package com.example.entity;
import java.util.Date;

/** hero实体类 */
public class Hero {
private Long id;//编号
private String name;//英雄名称 
private Double price;//价格 
private Date birthday;//创建时间 
private String remarks;//备注信息 

public Long getId() {return id;}
public void setId(Long id) {this.id = id;}
public String getName() {return name;}
public void setName(String name) {this.name = name;}
public Double getPrice() {return price;}
public void setPrice(Double price) {this.price= price;}
public Date getBirthday() {return birthday;}
public void setBirthday(Date birthday) {this.birthday= birthday;}
public String getRemarks() {return remarks;}
public void setRemarks(String remarks) {this.remarks= remarks;}
}

创建 Dao 类实现增删改

package com.example.dao;
import java.sql.SQLException;
import com.example.entity.Hero;
/** 数据访问类 */
public class HeroDao {
    /** 新增 */
    public static void add(Hero hero){
        java.sql.Connection conn=null;
        java.sql.PreparedStatement stmt=null;//预处理对象,防止注入攻击
        try { 
            conn=BaseDao.getConnection();
            stmt=conn.prepareStatement("insert into hero values(?,?,?,?,?)");
            stmt.setLong(1, hero.getId());
            stmt.setString(2, hero.getName());
            stmt.setDouble(3, hero.getPrice());
            stmt.setBirthday(4, new java.sql.Date(hero.getBirthday().getTime()));
            stmt.setRemarks(5, hero.getRemarks());
            stmt.execute();
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            BaseDao.closeAll(null, stmt, conn);
        }
    }  
    /** 修改 */
    public static void update(Hero hero){  
        java.sql.Connection conn=null;
        java.sql.PreparedStatement stmt=null;
        try {
            conn=BaseDao.getConnection();
            stmt=conn.prepareStatement("update hero set name=?,price=?,birthday=?,"
                    + "remarks=? where id=?");
            stmt.setString(1, hero.getName());
            stmt.setDouble(2, hero.getPrice());
            stmt.setBirthday(3, new java.sql.Date(hero.getBirthday().getTime()));
            stmt.setRemarks(4, hero.getRemarks());
            stmt.setLong(5, hero.getId());
            stmt.execute();
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            BaseDao.closeAll(null, stmt, conn);
        }
    }
    /** 删除 */
    public static void delete(Long id){
        java.sql.Connection conn=null;
        java.sql.PreparedStatement stmt=null;
        try {
            conn=BaseDao.getConnection();
            stmt=conn.prepareStatement("delete from hero where id=?");
            stmt.setLong(1, id); stmt.execute();
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            BaseDao.closeAll(null, stmt, conn);
        }
    } 
}

1.6 数据库备份与恢复

1.6.1 整库导入与导出

整库导出命令:(直接在cmd窗口执行)

exp system/123456 full=y

注:添加参数full=y就是整库导出
在这里插入图片描述
执行命令后会在当前目录下生成一个叫EXPDAT.DMP ,此文件为备份文件。
如果想指定备份文件的名称,则添加file 参数即可,命令如下

exp system/123456 file=文件名 full=y

整库导入命令

imp system/123456 full=y

此命令如果不指定file 参数,则默认用备份文件 EXPDAT.DMP 进行导入如果指定file 参数,则按照file指定的备份文件进行恢复

imp system/123456 full=y file=hero.dmp

1.6.2 按用户导入与导出

按用户导出

exp system/123456 owner=用户名 file=XXX.dmp

按用户导入

imp system/123456 file=XXX.dmp fromuser=用户名

1.6.3 按表导入与导出

按表导出

exp 用户名/密码 file=XXX.dmp tables=表名1,表名2......

用tables参数指定需要导出的表,如果有多个表用逗号分割即可参数指定需要导出的表,如果有多个表用逗号分割即可按表导入

imp 用户名/密码 file=XXX.dmp tables=表名1,表名2......

2 Oracle查询

这里以项目案例《自来水公司收费系统》为场景来讲述Oracle的查询
项目介绍与需求分析:
  某自来水公司为更好地对自来水收费进行规范化管理,决定委托开发《自来水公司收费系统》。考虑到自来水业务数量庞大,数据并发量高,决定数据库采用ORACLE数据库。主要功能包括:
1.基础信息管理:
(1)业主类型设置
(2)价格设置
(3)区域设置
(4)收费员设置
(5)地址设置
2.业主信息管理:
(1)业主信息维护
(2)业主信息查询
3.收费管理:
(1)抄表登记
(2)收费登记
(3)收费记录查询
(4)欠费用户清单
4.统计分析:
(1)收费日报单
(2)收费月报表
表结构设计:
1.业主类型表(T_OWNERTYPE)

字段名 类型(位数) 是否必填 说明
ID NUMBER 主键
NAME VARCHAR2(30) 类型名称

2.价格表(T_PRICETABLE)

字段名 类型(位数) 是否必填 说明
ID NUMBER 主键
PRICE NUMBER(10,2) 价格
OWNERTYPEID NUMBER 业主类型
MINNUM NUMBER(10,2) 区间数开始值
MAXNUM NUMBER(10,2) 区间数截止值

3.区域表(T_AREA)

字段名 类型(位数) 是否必填 说明
ID NUMBER 主键
NAME VARCHAR2(30) 区域名称

4.收费员表(T_OPERATOR)

字段名 类型(位数) 是否必填 说明
ID NUMBER 主键
NAME VARCHAR2(30) 操作员名称

5.地址表(T_ADDRESS)

字段名 类型(位数) 是否必填 说明
ID NUMBER 主键
NAME VARCHAR2(30) 地址名称
AREAID NUMBER 区域
OPERATORID NUMBER 操作员

6.业主表(T_OWNERS)

字段名 类型(位数) 是否必填 说明
ID NUMBER 主键
NAME VARCHAR2(30) 业主名称
ADDRESSID NUMBER 地址ID
HOUSENUMBER NUMBER 门牌号
WATERMETER VARCHAR2(30) 水表编号
ADDDATE DATE 登记日期
OWNERTYPEID NUMBER 业主类型ID

7.收费台账(T_ACCOUNT)

字段名 类型(位数) 是否必填 说明
ID NUMBER 主键
OWNERID NUMBER 业主编号
OWNERTYPEID NUMBER 业主类型
AREAID NUMBER 所在区域
YEAR CHAR(4) 账务年份
MONTH CHAR(2) 账务月份
NUM0 NUMBER 上月累计数
NUM1 NUMBER 本月累计数
USENUM NUMBER 本月使用数
METERUSERID NUMBER 抄表员
METERDATE DATE 抄表日期
MONEY NUMBER(10,2) 应缴金额
ISFEE CHAR(1) 是否缴费
FEEDATE DATE 缴费日期
FEEUSERID NUMBER 收费员

上述 7 张表的物理模型如下:
在这里插入图片描述
建表sql及测试数据:

--价格区间表
create  table t_pricetable
(
id number primary key,
price number(10,2),
ownertypeid number,
minnum number,
maxnum number
);
--业主类型
create table t_ownertype
(
id number primary key,
name varchar2(30)
);
--业主表
create table t_owners
(
id number primary key,
name varchar2(30),
addressid number,
housenumber varchar2(30),
watermeter varchar2(30),
adddate date,
ownertypeid number
);
--区域表
create table t_area
(
id number,
name varchar2(30)
);
--收费员表
create table t_operator
(
id number,
name varchar2(30)
);
--地址表
create table t_address
(
id number primary key,
name varchar2(100),
areaid number,
operatorid number
);
--账务表--
create table t_account 
(
id number primary key,
owneruuid number,
ownertype number,
areaid number,
year char(4),
month char(2),
num0 number,
num1 number,
usenum number,
meteruser number,
meterdate date,
money number(10,2),
isfee char(1),
feedate date,
feeuser number
);
create sequence seq_account;
--业主类型
insert into t_ownertype values(1,'居民');
insert into t_ownertype values(2,'行政事业单位');
insert into t_ownertype values(3,'商业');
--地址信息
insert into t_address values( 1,'明兴花园',1,1);
insert into t_address values( 2,'鑫源秋墅',1,1);
insert into t_address values( 3,'华龙苑南里小区',2,2);
insert into t_address values( 4,'河畔花园',2,2);
insert into t_address values( 5,'霍营',2,2);
insert into t_address values( 6,'回龙观东大街',3,2);
insert into t_address values( 7,'西二旗',3,2);
--业主信息
insert into t_owners values(1,'范冰',1,'1-1','30406',to_date('2015-04-12','yyyy-MM-dd'),1 );
insert into t_owners values(2,'王强',1,'1-2','30407',to_date('2015-02-14','yyyy-MM-dd'),1 );
insert into t_owners values(3,'马腾',1,'1-3','30408',to_date('2015-03-18','yyyy-MM-dd'),1 );
insert into t_owners values(4,'林小玲',2,'2-4','30409',to_date('2015-06-15','yyyy-MM-dd'),1 );
insert into t_owners values(5,'刘华',2,'2-5','30410',to_date('2013-09-11','yyyy-MM-dd'),1 );
insert into t_owners values(6,'刘东',2,'2-2','30411',to_date('2014-09-11','yyyy-MM-dd'),1 );
insert into t_owners values(7,'周健',3,'2-5','30433',to_date('2016-09-11','yyyy-MM-dd'),1 );
insert into t_owners values(8,'张哲',4,'2-2','30455',to_date('2016-09-11','yyyy-MM-dd'),1 );
insert into t_owners values(9,'昌平区中西医结合医院',5,'2-2','30422',to_date('2016-10-11','yyyy-MM-dd'),2 );
insert into t_owners values(10,'美廉美超市',5,'4-2','30423',to_date('2016-10-12','yyyy-MM-dd'),3 );
--操作员
insert into t_operator values(1,'马小云');
insert into t_operator values(2,'李翠花');
--地区
insert into t_area values(1,'海淀');
insert into t_area values(2,'昌平');
insert into t_area values(3,'西城');
insert into t_area values(4,'东城');
insert into t_area values(5,'朝阳');
insert into t_area values(6,'玄武');
--价格表
insert into t_pricetable values(1,2.45,1,0,5);
insert into t_pricetable values(2,3.45,1,5,10);
insert into t_pricetable values(3,4.45,1,10,null);
insert into t_pricetable values(4,3.87,2,0,5);
insert into t_pricetable values(5,4.87,2,5,10);
insert into t_pricetable values(6,5.87,2,10,null);
insert into t_pricetable values(7,4.36,3,0,5);
insert into t_pricetable values(8,5.36,3,5,10);
insert into t_pricetable values(9,6.36,3,10,null);
--账务表--
insert into t_account values( seq_account.nextval,1,1,1,'2012','01',30203,50123,0,1,sysdate,34.51,'1',to_date('2012-02-14','yyyy-MM-dd'),2 );
insert into t_account values( seq_account.nextval,1,1,1,'2012','02',50123,60303,0,1,sysdate,23.43,'1',to_date('2012-03-14','yyyy-MM-dd'),2 );
insert into t_account values( seq_account.nextval,1,1,1,'2012','03',60303,74111,0,1,sysdate,45.34,'1',to_date('2012-04-14','yyyy-MM-dd'),2 );
insert into t_account values( seq_account.nextval,1,1,1,'2012','04',74111,77012,0,1,sysdate,52.54,'1',to_date('2012-05-14','yyyy-MM-dd'),2 );
insert into t_account values( seq_account.nextval,1,1,1,'2012','05',77012,79031,0,1,sysdate,54.66,'1',to_date('2012-06-14','yyyy-MM-dd'),2 );
insert into t_account values( seq_account.nextval,1,1,1,'2012','06',79031,80201,0,1,sysdate,76.45,'1',to_date('2012-07-14','yyyy-MM-dd'),2 );
insert into t_account values( seq_account.nextval,1,1,1,'2012','07',80201,88331,0,1,sysdate,65.65,'1',to_date('2012-08-14','yyyy-MM-dd'),2 );
insert into t_account values( seq_account.nextval,1,1,1,'2012','08',88331,89123,0,1,sysdate,55.67,'1',to_date('2012-09-14','yyyy-MM-dd'),2 );
insert into t_account values( seq_account.nextval,1,1,1,'2012','09',89123,90122,0,1,sysdate,112.54,'1',to_date('2012-10-14','yyyy-MM-dd'),2 );
insert into t_account values( seq_account.nextval,1,1,1,'2012','10',90122,93911,0,1,sysdate,76.21,'1',to_date('2012-11-14','yyyy-MM-dd'),2 );
insert into t_account values( seq_account.nextval,1,1,1,'2012','11',93911,95012,0,1,sysdate,76.25,'1',to_date('2012-12-14','yyyy-MM-dd'),2 );
insert into t_account values( seq_account.nextval,1,1,1,'2012','12',95012,99081,0,1,sysdate,44.51,'1',to_date('2013-01-14','yyyy-MM-dd'),2 );
insert into t_account values( seq_account.nextval,2,1,3,'2012','01',30334,50433,0,1,sysdate,34.51,'1',to_date('2013-02-14','yyyy-MM-dd'),2 );
insert into t_account values( seq_account.nextval,2,1,3,'2012','02',50433,60765,0,1,sysdate,23.43,'1',to_date('2013-03-14','yyyy-MM-dd'),2 );
insert into t_account values( seq_account.nextval,2,1,3,'2012','03',60765,74155,0,1,sysdate,45.34,'1',to_date('2013-04-14','yyyy-MM-dd'),2 );
insert into t_account values( seq_account.nextval,2,1,3,'2012','04',74155,77099,0,1,sysdate,52.54,'1',to_date('2013-05-14','yyyy-MM-dd'),2 );
insert into t_account values( seq_account.nextval,2,1,3,'2012','05',77099,79076,0,1,sysdate,54.66,'1',to_date('2013-06-14','yyyy-MM-dd'),2 );
insert into t_account values( seq_account.nextval,2,1,3,'2012','06',79076,80287,0,1,sysdate,76.45,'1',to_date('2013-07-14','yyyy-MM-dd'),2 );
insert into t_account values( seq_account.nextval,2,1,3,'2012','07',80287,88432,0,1,sysdate,65.65,'1',to_date('2013-08-14','yyyy-MM-dd'),2 );
insert into t_account values( seq_account.nextval,2,1,3,'2012','08',88432,89765,0,1,sysdate,55.67,'1',to_date('2013-09-14','yyyy-MM-dd'),2 );
insert into t_account values( seq_account.nextval,2,1,3,'2012','09',89765,90567,0,1,sysdate,112.54,'1',to_date('2013-10-14','yyyy-MM-dd'),2 );
insert into t_account values( seq_account.nextval,2,1,3,'2012','10',90567,93932,0,1,sysdate,76.21,'1',to_date('2013-11-14','yyyy-MM-dd'),2 );
insert into t_account values( seq_account.nextval,2,1,3,'2012','11',93932,95076,0,1,sysdate,76.25,'1',to_date('2013-12-14','yyyy-MM-dd'),2 );
insert into t_account values( seq_account.nextval,2,1,3,'2012','12',95076,99324,0,1,sysdate,44.51,'1',to_date('2014-01-14','yyyy-MM-dd'),2 );
insert into t_account values( seq_account.nextval,100,1,3,'2012','12',95076,99324,0,1,sysdate,44.51,'1',to_date('2014-01-01','yyyy-MM-dd'),2 );
insert into t_account values( seq_account.nextval,101,1,3,'2012','12',95076,99324,0,1,sysdate,44.51,'1',to_date('2015-01-01','yyyy-MM-dd'),2 );

update t_account set usenum=num1-num0;
update t_account set money=usenum*2.45;
commit;

2.1 单表查询

2.1.1 简单条件查询

2.1.1.1 精确查询

需求:查询水表编号为30408的业主记录

select * from T_OWNERS where watermeter='30408';

查询结果:
在这里插入图片描述

2.1.1.2 模糊查询

需求:查询业主名称包含“刘”的业主记录

select * from t_owners where name like '%刘%';

查询结果:
在这里插入图片描述

2.1.1.3 and运算符

需求:查询业主名称包含“刘”的并且门牌号包含5的业主记录

select * from t_owners where name like '%刘%' and housenumber like '%5%';

查询结果:
在这里插入图片描述

2.1.1.4 or运算符

需求:查询业主名称包含“刘”的或者门牌号包含5的业主记录

select * from t_owners where name like '%刘%' or housenumber like '%5%';

查询结果:
在这里插入图片描述

2.1.1.5 and与or运算符混合使用

需求:查询业主名称包含“刘”的或者门牌号包含5的业主记录,并且地址编号为3的记录。

select * from t_owners where (name like '%刘%' or housenumber like '%5%') and addressid=3

查询结果:
在这里插入图片描述
注:因为and的优先级比or大,所以需要用( )来改变优先级

2.1.1.6 范围查询

需求:查询台账记录中用水字数大于等于10000,并且小于等于20000的记录
可以用>= 和<=来实现,语句:

select * from T_ACCOUNT where usenum>=10000 and usenum<=20000;

也可以用between … and …来实现:

select * from T_ACCOUNT where usenum between 10000 and 20000;

2.1.1.7 空值查询

需求:查询T_PRICETABLE表中MAXNUM为空的记录

select * from T_PRICETABLE t where maxnum is null;

查询结果:
在这里插入图片描述
需求:查询T_PRICETABLE 表中MAXNUM不为空的记录

select * from T_PRICETABLE t where maxnum is not null;

查询结果:
在这里插入图片描述

2.1.2 去掉重复记录

需求:查询业主表中的地址ID,不重复显示
语句:

select distinct addressid from T_OWNERS;

2.1.3 排序查询

2.1.3.1 升序排序

需求:对T_ACCOUNT 表按使用量进行升序排序

select * from T_ACCOUNT order by usenum;

查询结果:
在这里插入图片描述

2.1.3.2 降序排序

需求:对T_ACCOUNT表按使用量进行降序排序

select * from T_ACCOUNT order by usenum desc;

查询结果:
在这里插入图片描述

2.1.4 基于伪列的查询

在Oracle的表的使用过程中,实际表中还有一些附加的列,称为伪列。伪列就像表中的列一样,但是在表中并不存储。伪列只能查询,不能进行增删改操作。
伪列ROWID和ROWNUM:
ROWID
表中的每一行在数据文件中都有一个物理地址,ROWID伪列返回的就是该行的物理地址。使用ROWID可以快速的定位表中的某一行。ROWID值可以唯一的标识表中的一行。由于ROWID返回的是该行的物理地址,因此使用ROWID可以显示行是如何存储的。

select rowID,t.* from T_AREA t

查询结果如下:
在这里插入图片描述
可以通过指定ROWID 来查询记录

select rowID,t.* from T_AREA t where ROWID='AAASvxAAGAAAADPAAA';

查询结果如下:
在这里插入图片描述
ROWNUM
在查询的结果集中,ROWNUM为结果集中每一行标识一个行号,第一行返回1,第二行返回2,以此类推。通过ROWNUM 伪列可以限制查询结果集中返回的行数。

select rownum,t.* from T_OWNERTYPE t;

查询结果如下:
在这里插入图片描述
在分页查询需要用到此伪列,在2.4章节详细讲解

2.1.5 聚合统计

Oracle的聚合统计是通过分组函数来实现的,与MySQL一致。

2.1.5.1 聚合函数

2.1.5.1.1 求和sum

需求:统计2012年所有用户的用水量总和。

select sum(usenum) from t_account where year='2012';
2.1.5.1.2 求平均avg

需求:统计2012年所有用水量(字数)的平均值。

select avg(usenum) from T_ACCOUNT where year='2012';
2.1.5.1.3 求最大值max

需求:统计2012 年最高用水量(字数)

select max(usenum) from T_ACCOUNT where year='2012';
2.1.5.1.4 求最小值min

需求:统计2012 年最低用水量(字数)

select min(usenum) from T_ACCOUNT where year='2012';
2.1.5.1.5 统计记录个数count

需求:统计业主类型ID 为1 的业主数量

select count(*) from T_OWNERS t where ownertypeid=1;

2.1.5.2 分组聚合group by

注:分组聚合group by查询的列名必须是group by后面的列,或者聚合函数
需求:按区域分组统计水费合计数

select areaid,sum(money) from t_account group by areaid;

查询结果:
在这里插入图片描述
需求:按区域,年份分组统计水费合计数

select year,areaid,sum(money) from t_account group by areaid,year;

2.1.5.3 分组后条件查询having

需求:查询水费合计大于16900的区域及水费合计

select areaid,sum(money) from t_account group by areaid having sum(money)>169000;

查询结果:
在这里插入图片描述

2.2 连接查询

2.2.1 多表内连接查询

需求1:查询显示业主编号,业主名称,业主类型名称,如下图:
在这里插入图片描述

select o.id 业主编号,o.name 业主名称,ot.name 业主类型 from T_OWNERS o,T_OWNERTYPE ot 
where o.ownertypeid=ot.id;

需求2:查询显示业主编号,业主名称、地址和业主类型:
在这里插入图片描述
分析:此查询需要三表关联查询。分别是业主表,业主分类表和地址表

select o.id 业主编号,o.name 业主名称,ad.name 地址,ot.name 业主类型
from T_OWNERS o,T_OWNERTYPE ot,T_ADDRESS ad
where o.ownertypeid=ot.id and o.addressid=ad.id

需求3:查询显示业主编号、业主名称、地址、所属区域、业主分类
在这里插入图片描述
分析:这里需要四个表关联查询,比上边多了一个区域表T_AREA

select o.id 业主编号,o.name 业主名称,ar.name 区域, ad.name 地址, ot.name 业主类型
from T_OWNERS o ,T_OWNERTYPE ot,T_ADDRESS ad,T_AREA ar
where o.ownertypeid=ot.id and o.addressid=ad.id and ad.areaid=ar.id

需求4:查询显示业主编号、业主名称、地址、所属区域、收费员、业主分类
在这里插入图片描述
分析:此查询比上边又多了一个表T_OPERATOR

select ow.id 业主编号,ow.name 业主名称,ad.name 地址,ar.name 所属区域,op.name 收费员, ot.name 业主类型
from T_OWNERS ow,T_OWNERTYPE ot,T_ADDRESS ad,T_AREA ar,T_OPERATOR op
where ow.ownertypeid=ot.id and ow.addressid=ad.id and ad.areaid=ar.id and ad.operatorid=op.id

2.2.2 左外连接查询

需求:查询业主的账务记录,显示业主编号、名称、年、月、金额。如果此业主没有账务记录也要列出姓名。
在这里插入图片描述
分析:要查询这个结果,需要用到T_OWNERS(业主表),T_ACCOUNT(台账表)按照查询结果,业主表为左表、账务表为右表。
按照SQL1999标准的语法,查询语句如下:

SELECT ow.id,ow.name,ac.year ,ac.month,ac.money
FROM T_OWNERS ow left join T_ACCOUNT ac
on ow.id=ac.owneruuid

按照Oracle提供的语法,就很简单了:

SELECT ow.id,ow.name,ac.year ,ac.month,ac.money 
FROM T_OWNERS ow,T_ACCOUNT ac
WHERE ow.id=ac.owneruuid(+)

注:如果是左外连接,就在右表所在的条件一端填上(+)

2.2.3 右外连接查询

需求:查询业主的账务记录,显示业主编号、名称、年、月、金额。如果账务记录没有对应的业主信息,也要列出记录。如下图:
在这里插入图片描述
SQL1999标准的语句

select ow.id,ow.name,ac.year,ac.month,ac.money 
from T_OWNERS ow right join T_ACCOUNT ac
on ow.id=ac.owneruuid

Oracle的语法

select ow.id,ow.name,ac.year,ac.month,ac.money 
from T_OWNERS ow , T_ACCOUNT ac
where ow.id(+) =ac.owneruuid

2.3 子查询

2.3.1 where子句中的子查询

2.3.1.1 单行子查询

需求:查询2012年1月用水量大于平均值的台账记录

select * from T_ACCOUNT
where year='2012' and month='01' and usenum>
( select avg(usenum) from T_ACCOUNT where year='2012' and month='01' );

查询结果:
在这里插入图片描述
平均值为:
在这里插入图片描述

2.3.1.2 多行子查询

需求:查询地址含有“花园”的业主的信息

select * from T_OWNERS
where addressid in
( select id from t_address where name like '%花园%' );

需求:查询地址不含有“花园”的业主的信息
语句:

select * from T_OWNERS
where addressid not in
( select id from t_address where name like '%花园%' );

2.3.2 from子句中的子查询

from 子句的子查询为多行子查询
需求:查询显示业主编号,业主名称,业主类型名称,条件为业主类型为”居民”,使用子查询实现。
语句:

select * from
(select o.id 业主编号,o.name 业主名称,ot.name 业主类型
    from T_OWNERS o,T_OWNERTYPE ot
    where o.ownertypeid=ot.id)
where 业主类型='居民';

2.3.3 select子句中的子查询

select子句的子查询必须为单行子查询
需求1:列出业主信息,包括ID,名称,所属地址。

select id,name,
(select name from t_address where id=addressid) addressname
from t_owners;

查询结果如下:
在这里插入图片描述
需求2:列出业主信息,包括ID,名称,所属地址,所属区域。

select id,name,
    (select name from t_address where id=addressid) addressname,
    (select 
        (select name from t_area where id=areaid) 
    from t_address 
    where id=addressid)adrename
from t_owners;

查询结果如下:
在这里插入图片描述

2.4 分页查询

2.4.1 简单分页

需求:分页查询台账表T_ACCOUNT,每页10条记录

分析:在Oracle进行分页查询,需要用到伪列ROWNUM和嵌套查询
首先显示前10条记录,语句如下:

select rownum,t.* from T_ACCOUNT t where rownum<=10;

显示结果如下:
在这里插入图片描述
显示第11条到第20条的记录,语句:

--错误演示
select rownum,t.* from T_ACCOUNT t where rownum>10 and rownum<=20;

查询结果:
在这里插入图片描述
结果没有数据是因为rownum是在查询语句扫描每条记录时产生的,所以不能使用“大于”符号,只能使用“小于”或“小于等于” 可以使用子查询来实现

select * from
(select rownum r,t.* from T_ACCOUNT t where rownum<=20)
where r>10;

查询结果如下:
在这里插入图片描述

2.4.2 基于排序的分页

需求:分页查询台账表T_ACCOUNT,每页10条记录,按使用字数降序排序。
我们查询第2页数据,如果基于上边的语句添加排序,语句如下:

--错误演示
select * from
(select rownum r,t.* from T_ACCOUNT t where rownum<=20 order by usenum desc)
where r>10;

查询结果如下:
在这里插入图片描述
经过验证,我们看到第2页的结果应该是下列记录,所以推断刚才的语句是错误的
在这里插入图片描述
我们可以先单独执行嵌套查询里面的那句话

select rownum r,t.* from T_ACCOUNT t
where rownum<=20 order by usenum desc;

你会看到查询结果如下:
在这里插入图片描述
你会发现排序后的R是乱的。这是因为ROWNUM伪列的产生是在表记录扫描是产生的,而排序是后进行的,排序时R已经产生了,所以排序后R是乱的。
只要再嵌套一层循环(一共三层),让结果先排序,然后对排序后的结果再产生R,这样就不会乱了。

select * from
    (select rownum r,t.* from
        (select * from T_ACCOUNT order by usenum desc) t
            where rownum<=20 )
where r>10;

结果如下:
在这里插入图片描述

2.5 单行函数

2.5.1 字符函数

函数 说明
ASCII 返回对应字符的十进制值
CHR 给出十进制返回字符
CONCAT 拼接两个字符串,与||相同
INITCAT 将字符串的第一个字母变为大写
INSTR 找出某个字符串的位置
INSTRB 找出某个字符串的位置和字节数
LENGTH 以字符给出字符串的长度
LENGTHB 以字节给出字符串的长度
LOWER 将字符串转换成小写
LPAD 使用指定的字符在字符的左边填充
LTRIM 在左边裁剪掉指定的字符
RPAD 使用指定的字符在字符的右边填充
RTRIM 在右边裁剪掉指定的字符
REPLACE 执行字符串搜索和替换
SUBSTR 取字符串的子串
SUBSTRB 取字符串的子串(以字节)
SOUNDEX 返回一个同音字符串
TRANSLATE 执行字符串搜索和替换
TRIM 裁剪掉前面或后面的字符串
UPPER 将字符串变为大写

2.5.1.1 求字符串长度LENGTH

select length('ABCD') from dual;

显示结果为:
在这里插入图片描述

2.5.1.2 求字符串的子串SUBSTR

select substr('ABCD',2,2) from dual;

显示结果为:
在这里插入图片描述

2.5.1.3 字符串拼接CONCAT

select concat('ABC','D') from dual;

查询结果如下:
在这里插入图片描述

2.5.1.4 字符串拼接||

可以用||对字符串进行拼接,查询结果同上。

select 'ABC'||'D' from dual;

2.5.2 数值函数

函数 说明
ABS(value) 绝对值
CEIL(value) 大于或等于value的最小整数
COS(value) 余弦
COSH(value) 反余弦
EXP(value) e的value次幂
FLOOR(value) 小于或等于value的最大整数
LN(value) value的自然对数
LOG(value) value的以10为底的对数
MOD(value,divisor) 求模
POWER(value,exponent) value的exponent次幂
ROUND(value,precision) 按precision精度4舍5入
SIGN(value) value为正返回1;为负返回-1;为0返回0.
SIN(value) 余弦
SINH(value) 反余弦
SQRT(value) value的平方根
TAN(value) 正切
TANH(value) 反正切
TRUNC(value,按precision) 按照precision截取value
VSIZE(value) 返回value在Oracle的存储空间大小

常用数值函数讲解:

2.5.2.1 四舍五入函数ROUND

select round(100.567) from dual;

查询结果如下:
在这里插入图片描述
语句:

select round(100.567,2) from dual;

查询结果如下:
在这里插入图片描述

2.5.2.2 截取函数TRUNC

select trunc(100.567) from dual;

查询结果:
在这里插入图片描述
语句:

select trunc(100.567,2) from dual;

查询结果:
在这里插入图片描述

2.5.2.3 取模MOD

select mod(10,3) from dual;

结果:
在这里插入图片描述

2.5.3 日期函数

函数 描述
ADD_MONTHS 在日期date上增加count个月
GREATEST(date1,date2,…) 从日期列表中选出最晚的日期
LAST_DAY(date) 返回日期date所在月的最后一天
LEAST(date1,date2,…) 从日期列表中选出最早的日期
MONTHS_BETWEEN(date2,date1) 给出Date2-date1的月数(可以是小数)
NEXT_DAY(date,’day’) 给出日期date之后下一天的日期,这里的day为星期,如:MONDAY,Tuesday等
NEW_TIME(date,’this’,’other’) 给出在this时区=Other时区的日期和时间
ROUND(date,’format’) 未指定format时,如果日期中的时间在中午之前,则将日期中的时间截断为12A.M.(午夜,一天的开始),否则进到第二天。时间截断为12A.M.(午夜,一天的开始),否则进到第二天
TRUNC(date,’format’) 未指定format时,将日期截为12A.M.(午夜,一天的开始)

用 sysdate 这个系统变量来获取当前日期和时间

select sysdate from dual;

常用日期函数讲解:

2.5.3.1 加月函数ADD_MONTHS:在当前日期基础上加指定的月

select add_months(sysdate,2) from dual;

查询结果如下:
在这里插入图片描述

2.5.3.2 求所在月最后一天LAST_DAY

语句:

select last_day(sysdate) from dual;

查询结果如下:
在这里插入图片描述

2.5.3.3 日期截取TRUNC

语句

select TRUNC(sysdate) from dual;

查询结果如下:
在这里插入图片描述
语句:

select TRUNC(sysdate,'yyyy') from dual;

查询结果如下:
在这里插入图片描述
语句:

select TRUNC(sysdate,'mm') from dual;

查询结果如下:
在这里插入图片描述

2.5.4 转换函数

函数 描述
CHARTOROWID 将字符转换到rowid类型
CONVERT 转换一个字符节到另外一个字符节
HEXTORAW 转换十六进制到raw类型
RAWTOHEX 转换raw到十六进制
ROWIDTOCHAR 转换ROWID到字符
TO_CHAR 转换日期格式到字符串
TO_DATE 按照指定的格式将字符串转换到日期型
TO_MULTIBYTE 把单字节字符转换到多字节
TO_NUMBER 将数字字串转换到数字
TO_SINGLE_BYTE 转换多字节到单字节

常用转换函数讲解:

2.5.4.1 数字转字符串TO_CHAR

语句:

select TO_CHAR(1024) from dual;

查询结果:
在这里插入图片描述

2.5.4.2 日期转字符串TO_CHAR

语句

select TO_CHAR(sysdate,'yyyy-mm-dd') from dual;

查询结果:
在这里插入图片描述
语句:

select TO_CHAR(sysdate,'yyyy-mm-dd hh:mi:ss') from dual;

查询结果:
在这里插入图片描述

2.5.4.3 字符串转日期TO_DATE

语句:

select TO_DATE('2017-01-01','yyyy-mm-dd') from dual;

查询结果如下:
在这里插入图片描述

2.5.4.4 字符串转数字TO_NUMBER

select to_number('100') from dual;

2.5.5 其他函数

2.5.5.1 空值处理函数NVL

用法:
NVL(检测的值,如果为null的值)

select NVL(NULL,0) from dual;

查询结果如下:
在这里插入图片描述
需求:显示价格表中业主类型ID为1的价格记录,如果上限值为 NULL,则显示9999999

select PRICE,MINNUM,NVL(MAXNUM,9999999)
from T_PRICETABLE where OWNERTYPEID=1;

查询结果:
在这里插入图片描述

2.5.5.2 空值处理函数NVL2

用法:NVL2(检测的值,如果不为null的值,如果为null的值);
需求:显示价格表中业主类型ID为1的价格记录,如果上限值为NULL,显示“不限”
在这里插入图片描述
语句:

select PRICE,MINNUM,NVL2(MAXNUM,to_char(MAXNUM) , '不限')
from T_PRICETABLE where OWNERTYPEID=1;

2.5.5.3 条件取值 decode

语法:decode(条件,值1,翻译值1,值2,翻译值2,…值n,翻译值n,缺省值)
——根据条件返回相应值
需求:显示下列信息(不要关联查询业主类型表,直接判断 1 2 3 的值)
在这里插入图片描述
语句:

select name,decode( ownertypeid,1,'居民',2,'行政事业单位',3,'商业') as 类型 from T_OWNERS;

上边的语句也可以用 case when then 语句来实现

select name ,(case ownertypeid
                when 1 then '居民'
                when 2 then '行政事业单位'
                when 3 then '商业'
                else '其它'
                end
) from T_OWNERS;

还有另外一种写法:

select name,(case
            when ownertypeid= 1 then '居民'
            when ownertypeid= 2 then '行政事业'
            when ownertypeid= 3 then '商业'
            end ) 
from T_OWNERS;

2.6 行列转换

需求:按月份统计2012年各个地区的水费,如下图
在这里插入图片描述

select (select name from T_AREA where id= areaid ) 区域,
    sum( case when month='01' then money else 0 end) 一月,
    sum( case when month='02' then money else 0 end) 二月,
    sum( case when month='03' then money else 0 end) 三月,
    sum( case when month='04' then money else 0 end) 四月,
    sum( case when month='05' then money else 0 end) 五月,
    sum( case when month='06' then money else 0 end) 六月,
    sum( case when month='07' then money else 0 end) 七月,
    sum( case when month='08' then money else 0 end) 八月,
    sum( case when month='09' then money else 0 end) 九月,
    sum( case when month='10' then money else 0 end) 十月,
    sum( case when month='11' then money else 0 end) 十一月,
    sum( case when month='12' then money else 0 end) 十二月
from T_ACCOUNT where year='2012' group by areaid;

需求:按季度统计2012年各个地区的水费,如下图
在这里插入图片描述
语句如下:

select (select name from T_AREA where id= areaid ) 区域,
sum( case when month>='01' and month<='03' then money else 0 end) 第一季度,
sum( case when month>='04' and month<='06' then money else 0 end) 第二季度,
sum( case when month>='07' and month<='09' then money else 0 end) 第三季度,
sum( case when month>='10' and month<='12' then money else 0 end) 第四季度
from T_ACCOUNT where year='2012' group by areaid;

2.7 分析函数

以下三个分析函数可以用于排名使用。
下图为三种排名方式的举例
在这里插入图片描述

2.7.1 RANK相同的值排名相同,排名跳跃

需求:对T_ACCOUNT表的usenum 字段进行排序,相同的值排名相同,排名跳跃
语句:

select rank() over(order by usenum desc ),usenum from T_ACCOUNT;

结果:
在这里插入图片描述

2.7.2 DENSE_RANK相同的值排名相同,排名连续

需求:对T_ACCOUNT表的usenum字段进行排序,相同的值排名相同,排名连续
语句:

select dense_rank() over(order by usenum desc ),usenum from T_ACCOUNT;

结果:
在这里插入图片描述

2.7.3 ROW_NUMBER返回连续的排名,无论值是否相等

需求:对T_ACCOUNT表的usenum字段进行排序,返回连续的排名,无论值是否相等
语句:

select row_number() over(order by usenum desc),usenum from T_ACCOUNT;

结果:
在这里插入图片描述
用row_number()分析函数实现的分页查询相对三层嵌套子查询要简单的多:

select * from
(select row_number() over(order by usenum desc )
rownumber,usenum from T_ACCOUNT)
where rownumber>10 and rownumber<=20;

查询结果如下:
在这里插入图片描述

2.8 集合运算

2.8.1 什么是集合运算

集合运算,集合运算就是将两个或者多个结果集组合成为一个结果集。集合运算包括:
UNION ALL(并集),返回各个查询的所有记录,包括重复记录。
UNION(并集),返回各个查询的所有记录,不包括重复记录。
INTERSECT(交集),返回两个查询共有的记录。
MINUS(差集),返回第一个查询检索出的记录减去第二个查询检索出的记录之后剩余的记录。
在这里插入图片描述

2.8.2 并集运算

UNION ALL不去掉重复记录

select * from t_owners where id<=7
union all
select * from t_owners where id>=5;

结果如下:
在这里插入图片描述
UNION去掉重复记录

select * from t_owners where id<=7
union
select * from t_owners where id>=5;

结果如下:
在这里插入图片描述

2.8.3 交集运算

select * from t_owners where id<=7
intersect
select * from t_owners where id>=5;

结果:
在这里插入图片描述

2.8.4 差集运算

select * from t_owners where id<=7
minus
select * from t_owners where id>=5;

结果:
在这里插入图片描述
如果我们用minus运算符来实现分页,语句如下:

select rownum,t.* from T_ACCOUNT t where rownum<=20
minus
select rownum,t.* from T_ACCOUNT t where rownum<=10;

结果:
在这里插入图片描述

2.9 使用system账号给scott解锁/改密码:

解锁:

alter user scott account unlock;

改密码:

alter user scott identified by tiger;  --scott的默认密码是tiger

3 Oracle对象

3.1 视图

3.1.1 什么是视图

视图是一种数据库对象,是从一个或者多个数据表或视图中导出的虚表,视图所对应的数据并不真正地存储在视图中,而是存储在所引用的数据表中,视图的结构和数据是对数据表进行查询的结果。根据创建视图时给定的条件,视图可以是一个数据表的一部分,也可以是多个基表的联合,它存储了要执行检索的查询语句的定义,以便在引用该视图时使用。
使用视图的优点:

  • 简化数据操作:视图可以简化用户处理数据的方式。
  • 着重于特定数据:不必要的数据或敏感数据可以不出现在视图中。
  • 视图提供了一个简单而有效的安全机制,可以定制不同用户对数据的访问权限。
  • 提供向后兼容性:视图使用户能够在表的架构更改时为表创建向后兼容接口。

3.1.2 创建或修改视图语法

CREATE [OR REPLACE] [FORCE] VIEW view_name
AS subquery
[WITH CHECK OPTION ]
[WITH READ ONLY];

选项解释:
OR REPLACE:若所创建的试图已经存在ORACLE自动重建该视图;
FORCE:不管基表是否存在ORACLE都会自动创建该视图;
subquery:一条完整的SELECT语句,可以在该语句中定义别名;
WITH CHECK OPTION :插入或修改的数据行必须满足视图定义的约束;
WITH READ ONLY :该视图上不能进行任何DML操作。

3.1.3 删除视图语法

DROP VIEW view_name;

3.1.4 示例

3.1.4.1 简单视图的创建与使用

简单视图:如果视图中的语句只是单表查询,并且没有聚合函数,我们就称之为简单视图。
需求:创建视图:业主类型为 1 的业主信息
语句:

create or replace view view_owners1 as
select * from T_OWNERS where ownertypeid= 1;

利用该视图进行查询

select * from view_owners1 where addressid= 1;

就像使用表一样去使用视图就可以了。
对于简单视图,可以用查询,还可以增删改记录。
执行下面写一条更新的语句:

update view_owners1 set name 王刚 where id 2;

再次查询:

select * from view_owners1;

查询结果如下:
在这里插入图片描述
结果已经更改成功。再次查询表数据
在这里插入图片描述
发现表的数据也跟着更改了。由此得出结论:视图其实是一个虚拟的表,它的数据其实来自于表。如果更改了视图的数据,表的数据也自然会变化,更改了表的数据,视图也自然会变化。一个视图所存储的并不是数据,而是一条SQL语句。

3.1.4.2 带检查约束的视图

需求:根据地址表T_ADDRESS创建视图VIEW_ADDRESS2 , 内容为区域ID为2的记录。
语句:

create or replace view view_address2 as
select * from T_ADDRESS where areaid= 2
with check option;

执行下列更新语句:

update view_address2 set areaid=11 where id=44;

系统提示如下错误信息:
在这里插入图片描述

3.1.4.3 只读视图的创建与使用

需求:将上边的视图修改为只读视图
语句:

create or replace view view_owners1 as
select * from T_OWNERS where ownertypeid= 1
with read only;

修改后,再次执行update 语句,会出现如下错误提示
在这里插入图片描述

3.1.4.4 创建带错误的视图

创建一个视图,如果视图的SQL语句所设计的表并不存在,如下:

create or replace view view_TEMP as
select * from T_TEMP;

T_TEMP表并不存在,此时系统会给出错误提示表并不存在,此时系统会给出错误提示
在这里插入图片描述
有的时候,我们创建视图时的表可能并不存在,但是以后可能会存在,我们如果此时需要创建这样的视图,需要添加FORCE选项,SQL语句如下:

create or replace FORCE view view_TEMP as
select * from T_TEMP;

此时视图创建成功。

3.1.4.5 复杂视图的创建与使用

复杂视图,就是视图的SQL语句中,有聚合函数或多表关联查询。
示例:

3.1.4.5.1 多表关联查询示例:

需求:创建视图,查询显示业主编号业主名称,业主类型名称
语句:

create or replace view view_owners as
select o.id 业主编号 ,o.name 业主名称 ,ot.name 业主类型
from T_OWNERS o,T_OWNERTYPE ot
where o.ownertypeid=ot.id;

试一下下面的语句:

update view_owners set 业主名称 范小冰 where 业主编号 1;

可以修改成功。再试一下下面的语句:

update view_owners set 业主类型 普通居民 where 业主编号 1;

系统弹出错误提示:
在这里插入图片描述
是说所需改的列不属于键保留表的列。
键保留表是理解连接视图修改限制的一个基本概念。该表的主键列全部显示在视图中并且它们的值在视图中都是唯一且非空的。也就是说,表的键值在一个连接视图中也是键值,那么就称这个表为键保留表。
在这个例子中,视图中存在两个表,业主表 T_OWNERS和业主类型表T_OWNERTYPE, 其中 T_OWNERS 表就是键保留表,因为T_OWNERS的主键也是作为视图的主键。键保留表的字段是可以更新的,而非键保留表是不能更新的

3.1.4.5.2 分组聚合统计查询的例子

需求:创建视图,按年月统计水费金额,效果如下:

语句:

create view view_accountsum as
select year month sum (money) moneysum
from T_ACCOUNT
group by year month
order by year month;

此例用到聚合函数,没有键保留表,所以无法执行update 。

3.2 物化视图

3.2.1 什么是物化视图

视图是一个虚拟表(也可以认为是一条语句),基于它创建时指定的查询语句返回的结果集。每次访问它都会导致这个查询语句被执行一次。为了避免每次访问都执行这个查询,可以将这个查询结果集存储到一个物化视图(也叫实体化视图)。
物化视图与普通的视图相比的区别是物化视图是建立的副本,它类似于一张表,需要占用存储空间。而对一个物化视图查询的执行效率与查询一个表是一样的。

3.2.2 创建物化视图语法

CREATE METERIALIZED VIEW view_name
[BUILD IMMEDIATE | BUILD DEFERRED]
REFRESH [FAST | COMPLETE | FORCE]
[
ON [COMMIT | DEMAND] | START WITH (start_time) NEXT (next_time)
]
AS
subquery
  • BUILD IMMEDIATE是在创建物化视图的时候就生成数据
  • BUILD DEFERRED则在创建时不生成数据, 以后根据需要再生成数据。默认为BUILD IMMEDIATE。
  • 刷新REFRESH:指当基表发生了DML操作后,物化视图何时采用哪种方式和基表进行同步。

REFRESH后跟着指定的刷新方法有三种:FAST、COMPLETE、FORCE。FAST刷新采用增量刷新,只刷新自上次刷新以后进行的修改。 COMPLETE刷新对整个物化视图进行完全的刷新。如果选择FORCE方式,则Oracle在刷新时会去判断是否可以进行快速刷新,如果可以则采用FAST方式,否则采用COMPLETE的方式。FORCE是默认的方式。

  • 刷新的模式有两种:ON DEMAND和ON COMMIT。ON DEMAND指需要手动刷新物化视图(默认)。ON COMMIT 指在基表发生COMMIT操作时自动刷新。

3.2.3 示例

3.2.3.1 创建手动刷新的物化视图

需求:查询地址ID, 地址名称和所属区域名称 , 结果如下:
在这里插入图片描述
语句:

create materialized view mv_address
as
select ad.id,ad.name adname,ar.name ar_name
from t_address ad,t_area ar
where ad.areaid=ar.id

执行上边的语句后查询

select * from mv_address;

查询结果如下:
在这里插入图片描述

这时,向地址表(T_ADDRESS )中插入一条新记录

insert into t_address values(8,'宏福苑小区',1,1);

再次执行上边的语句进行查询,会发现新插入的语句并没有出现在物化视图中。
需要通过下面的语句(PL/SQL ),手动刷新物化视图

begin 
DBMS_MVIEW.refresh('MV_ADDRESS','C');
end;

或者通过下面的命令手动刷新物化视图:

EXEC DBMS_MVIEW.refresh('MV_ADDRESS','C');
--注意:此语句需要在命令窗口中执行

执行此命令后再次查询物化视图,就可以查询到最新的数据了。
DBMS_MVIEW.refresh实际上是系统内置的存储过程,关于存储过程在第4章会详细讲解。

3.2.3.2 创建自动刷新的物化视图

和上例一样的结果集
语句如下:

create materialized view mv_address2
refresh
on commit 
as
select ad.id,ad.name adname,ar.name ar_name 
from t_address ad,t_area ar
where ad.areaid=ar.id

创建此物化视图后,当T_ADDRESS表发生变化时,MV_ADDRESS2自动跟着改变。

3.2.3.3 创建时不生成数据的物化视图

create materialized view mv_address3
build deferred
refresh
on commit
as
select ad.id,ad.name adname,ar.name ar_name
from t_address ad,t_area ar
where ad.areaid=ar.id;

创建后执行下列语句查询物化视图

select * from mv_address3

查询结果:
在这里插入图片描述
执行下列语句生成数据

begin
DBMS_MVIEW.refresh('MV_ADDRESS3','C');
end;

再次查询,得到结果:
在这里插入图片描述
由于创建时指定的on commit所以在修改数据后能立刻看到最新数据,无须再次执行refresh

3.2.3.4 创建增量刷新的物化视图

如果创建增量刷新的物化视图,必须首先创建物化视图日志

create materialized view log on t_address with rowid
create materialized view log on t_area with rowid

创建的物化视图日志名称为MLOG$_ 表名称
创建物化视图

create materialized view mv_address4
refresh fast
as
selec t ad.rowid adrowid ,ar.rowid arrowid, ad.id,ad.name
adname,ar.name ar_name
from t_address ad,t_area ar
where ad.areaid=ar.id;

注意:创建增量刷新的物化视图,必须:
1.创建物化视图中涉及表的物化视图日志。
2.在查询语句中,必须包含所有表的rowid (以rowid方式建立物化视图日志)
当我们向地址表插入数据后,物化视图日志的内容:
在这里插入图片描述
SNAPTIME$$:用于表示刷新时间。
DMLTYPE$$:用于表示DML操作类型,I表示INSERT,D表示DELETE,U表示UPDATE 。
OLD_NEW$$ NEW$$:用于表示这个值是新值还是旧值。 N(EW)表示新值O(LD)表示旧值,U表示UPDATE 操作。
CHANGE_VECTOR$$ VECTOR$$:表示修改矢量,用来表示被修改的是哪个或哪几个字段。此列是 RAW 类型,其实Oracle采用的方式就是用每个BIT位去映射一个列。
插入操作显示为FE,删除显示为OO,更新操作则根据更新字段的位置而显示不同的值。
当我们手动刷新物化视图后,物化视图日志被清空,物化视图更新。

begin
DBMS_MVIEW.refresh('MV_ADDRESS4','C');
end;

3.3 序列

3.3.1 什么是序列

序列是Oracle提供的用于产生一系列唯一数字的数据库对象 。

3.3.2 创建与使用简单序列

创建序列语法:

create sequence 序列名称

通过序列的伪列来访问序列的值

  • NEXTVAL返回序列的下一个值
  • CURRVAL返回序列的当前值

注意:在刚建立序列后,无法提取当前值,只有先提取下一个值时才能再次提取当前值。

--提取下一个值
select 序列名称.nextval from dual
--提取当前值
select 序列名称.currval from dual

3.3.3 创建复杂序列

语法:

CREATE SEQUENCE sequence //创建序列名称
[INCREMENT BY n] //递增的序列值是n 如果n是正数就递增,如果是负数就递减 默认是1
[START WITH n] //开始的值,递增默认是minvalue 递减是maxvalue
[{MAXVALUE n | NOMAXVALUE}] //最大值
[{MINVALUE n | NOMINVALUE}] //最小值
[{CYCLE | NOCYCLE}] //循环/不循环
[{CACHE n | NOCACHE}];//分配并存入到内存中

3.3.4 示例

3.3.4.1 有最大值的非循环序列

创建序列的语句:

create sequence seq_test1
increment by 10
start with 10
maxvalue 300
minvalue 20

结果:
在这里插入图片描述
以上的错误,是由于开始值小于最小值。开始值不能小于最小值,修改以上语句:

create sequence seq_test1
increment by 10
start with 10
maxvalue 300
minvalue 5

执行该语句提取序列值,当序列值为 300 (最大值)的时候再次提取值系统会报异常信息。
在这里插入图片描述

3.3.4.2 有最大值的循环序列

create sequence seq_test2
increment by 10
start with 10
maxvalue 300
minvalue 5
cycle;

当序列当前值为300 (最大值),再次提取序列的值

select seq_test2.nextval from dual

提取的值为:
在这里插入图片描述

由此得出结论,循环的序列,第一次循环是从开始值开始循环,而第二次循环是从最小值开始循环。
思考问题:下列语句是否会报错?为什么?

create sequence seq_test3
increment by 10
start with 10
minvalue 5
cycle;

答:此为错误的语句。因为你创建的是一个循环的序列,所以必须指定最大值,否则会报错。

3.3.4.3 带缓存的序列

执行下列语句:

create sequence seq_test3
increment by 10
start with 10
maxvalue 300
minvalue 5
cycle
cache 50;

执行上边语句的意思是每次取出50个缓存值,但是执行会提示错误
在这里插入图片描述
上边错误提示的意思是:缓存设置的数必须小于每次循环的数。
缓存设定的值是50 ,而最大值是300,为什么还会提示这样的信息呢?
其实cache虽然是50 ,但是每次增长值是10。这样50次缓存提取出的数是500(50*10)
更改为下列的语句:

create sequence seq_test4
increment by 10
start with 10
maxvalue 500
min value 10
cycle
cache 50;

下列语句依然会提示上边的错误,这是因为还存在一个minvalue,minvalue 和maxvalue之间是490个数,也就是一次循环可以提取490,但是缓存是500 。
再次修改语句:

create sequence seq_test5
increment by 10
start with 10
maxvalue 500
minvalue 9
cycle
cache 50;

把最小值减1,或把最大值加1,都可以通过。

3.3.5 修改和删除序列

修改序列:使用ALTER SEQUENCE语句修改序列,不能更改序列的START WITH参数

ALTER SEQUENCE 序列名称 MAXVALUE 5000 CYCLE;

删除序列:

DROP SEQUENCE 序列名称

3.4 同义词

3.4.1 什么是同义词

同义词实质上是指定方案对象的一个别名。通过屏蔽对象的名称和所有者以及对分布式数据库的远程对象提供位置透明性,同义词可以提供一定程度的安全性。同时,同义词的易用性较好,降低了数据库用户的SQL 语句复杂度。同义词允许基对象重命名或者移动,这时,只需对同义词进行重定义,基于同义词的应用程序可以继续运行而无需修改。
你可以创建公共同义词和私有同义词。其中,公共同义词属于PUBLIC特殊用户组,数据库的所有用户都能访问;而私有同义词包含在特定用户的方案中,只允许特定用户或者有基对象访问权限的用户进行访问。
同义词本身不涉及安全,当你赋予一个同义词对象权限时,你实质上是在给同义词的基对象赋予权限,同义词只是基对象的一个别名。

3.4.2 创建与使用同义词

创建同义词的具体语法是:

create [public] SYNONYM synooym for object;

synonym表示要创建的同义词的名称,object 表示表,视图 ,序列等要创建同义词的对象的名称。

3.4.3 示例

3.4.3.1 私有同义词

需求:为表T_OWNERS 创建私有同义词名称为OWNERS
语句:

create synonym OWNERS for T_OWNERS;

使用同义词:

select * from OWNERS;

查询结果如下:
在这里插入图片描述

3.4.3.2 公有同义词

需求:为表T_OWNERS创建公有同义词名称为OWNERS2

create public synonym OWNERS 2 for T_OWNERS;

以另外的用户登陆,也可以使用公有同义词:

select * from OWNERS 2

3.5 索引

3.5.1 什么是索引

索引是用于加速数据存取的数据对象。合理的使用索引可以大大降低i/o次数从而提高数据访问性能 。
索引是需要占据存储空间的,也可以理解为是一种特殊的数据。形式类似于下图的一棵“树”,而树的节点存储的就是每条记录的物理地址,也就是伪列ROWID
在这里插入图片描述

3.5.2 普通索引

语法:

create index 索引名称 on 表名(列名);

需求:
根据业主名称搜索业主信息,所以基于业主表的name字段来建立索引。语句如下:

create index index_owners_name on T_OWNERS(name)

索引性能测试:
创建一个两个字段的表

creat e table T_INDEXTEST (
ID NUMBER,
NAME VARCHAR2(30)
);

编写PL/SQL插入100万条记录(关于PL/SQL在第4节)

BEGIN
    FOR i in 1 ..1000000
    loop
        INSERT INTO T_INDEXTEST VALUES ( i,'AA'||i);
    end loop;
    commit;
END;

创建完数据后,根据name列创建索引

CREATE INDEX INDEX_TESTINDEX o n T_INDEXTEST(name)

执行下面两句SQL执行

SELECT * from T_INDEXTEST where ID 765432;
SELECT * from T_INDEXTEST where NAME ='AA765432';

发现根据name查询所用的时间会比根据id查询所用的时间要短

3.5.3 唯一索引

如果需要在某个表某个列创建索引,而这列的值是不会重复的。这时可以创建唯一索引。
语法:

create unique index 索引名称 on 表名(列名);

需求:在业主表的水表编号一列创建唯一索引
语句:

create unique index index_owners_watermeter on T_OWNERS(watermeter);

3.5.4 复合索引

如果经常要对某几列进行查询,比如,经常要根据学历和性别对学员进行搜索,如果对这两列建立两个索引,因为要查两棵树,查询性能不一定高。可以建立复合索引,也就是基于两个以上的列建立一个索引 。
语法:

create index 索引名称 on 表名(列名,列名......);

根据地址和门牌号对学员表创建索引,语句如下:

create index owners_index_ah
on T_OWNERS(addressid,housenumber);--字段顺序要与查询时的一致

3.5.5 反向键索引

应用场景:当某个字段的值为连续增长的值,如果构建标准索引,会形成歪脖子树。这样会增加查询的层数,性能会下降。建立反向键索引,可以使索引的值变得不规则,从而使索引树能够均匀分布。
在这里插入图片描述
语法:

create index 索引名称 on 表名(列名) reverse;

3.5.6 位图索引

使用场景:位图索引适合创建在低基数列上位图索引不直接存储ROWID ,而是存储字节位到 ROWID 的映射
优点:减少响应时间,节省空间占用,语法:

create bitmap index 索引名称 on 表名(列名);

需求:在T_owners表的ownertypeid列上建立位图索引,语句:

create bitmap index index_owners_typeid
on T_OWNERS(ownertypeid)

4 Oracle编程

4.1 PL/SQL

4.1.1 什么是PL/SQL

PL/SQL(ProcedureLanguage/SQL)是Oracle对sql语言的过程化扩展,指在SQL命令语言中增加了过程处理语句(如分支、循环等),使SQL语言具有过程处理能力。把SQL语言的数据操纵能力与过程语言的数据处理能力结合起来,使得PLSQL面向过程但比过程语言简单、高效、灵活和实用。
基本语法结构

[declare
    --声明变量
    ]
 begin
        --代码逻辑
    [exception
        --异常处理
        ]
end;

4.1.2 变量

声明变量的语法:

变量名 类型 (长度)

变量赋值的语法:

变量名:=变量值

需求:声明变量水费单价、水费字数、吨数、金额。对水费单价、字数、进行赋值。吨数根据水费字数换算,规则为水费字数除以1000 ,并且四舍五入,保留两位小数。计算金额,金额 单价 吨数。
输出单价、数量和金额。
–变量的用法–

declare
    v_price number(10,2); --水费单价
    v_usenum number; --水费字数
    v_usenum2 number(10,2); --吨数
    v_money number(10,2); --金额
begin
    v_price:=2.45; --水费单价
    v_usenum:=8012; --字数
    --字数换算为吨数
    v_usenum2:=round(v_usenum/1000,2);
    --计算金额
    v_money:=round(v_price*v_usenum2,2);
    dbms_output.put_line('单价:'||v_price||'吨数:'||v_usenum2||'金额:'||v_money);
end;

select into方式赋值
语法结构:

select 列名 into 变量名 from 表名 where 条件

注意:结果必须是一条记录,有多条记录和没有记录都会报错

declare
v_price number(10,2);--单价
v_usenum number;--水费字数
v_num0 number;--上月字数
v_num1 number;--本月字数
v_usenum2 number(10,2);--使用吨数
v_money number(10,2);--水费金额
begin
--对单价进行赋值
v_price:=3.45;
--变量赋值
select usenum,num0,num1 into v_usenum,V_num0,V_num1 from T_ACCOUNT
where year='2012' and month='01' and owneruuid=1;

v_usenum2:= round(v_usenum/1000,2); 
v_money:=v_price*v_usenum2;
DBMS_OUTPUT.put_line('单价:'||v_price||'吨数:'
||v_usenum2||'金额:'||v_money||'上月字数:'||v_num0||'本月  字数'||v_num1);
end;

4.1.3 属性类型

%TYPE 引用型
作用:引用某表某列的字段类型

declare
v_price number(10,2);--单价
v_usenum T_ACCOUNT.USENUM%TYPE;--水费字数
v_num0 T_ACCOUNT.NUM0%TYPE;--上月字数
v_num1 T_ACCOUNT.NUM1%TYPE;--本月字数
v_usenum2 number(10,2);--使用吨数
v_money number(10,2);--水费金额begin
--对单价进行赋值
v_price:=3.45;
--v_usenum:=8090;
select usenum,num0,num1 into v_usenum,V_num0,V_num1 from T_ACCOUNT
where year='2012' and month='01' and owneruuid=1;
--使用吨数
v_usenum2:= round(v_usenum/1000,2);
--计算金额
v_money:=v_price*v_usenum2; DBMS_OUTPUT.put_line('单价:'||v_price||'吨数:'
||v_usenum2||'金额:'||v_money||'上月字数:'||v_num0||'本月字数'||v_num1); 
end;

%ROWTYPE 记录型 ,上例中的例子可以用下面的代码代替
作用: 标识某个表的行记录类型

declare
v_price number(10,2);--单价
v_account T_ACCOUNT%ROWTYPE;--记录型
v_usenum2 number(10,2);--使用吨数
v_money number(10,2);--水费金额
begin
--对单价进行赋值
v_price:=3.45;
--赋值
select * into v_account from T_ACCOUNT where year='2012' and month='01' and owneruuid=1;
--使用吨数
v_usenum2:= round(v_account.usenum/1000,2);
--计算金额
v_money:=v_price*v_usenum2;
DBMS_OUTPUT.put_line('单价:'||v_price||'吨数:'
||v_usenum2||'金额:'||v_money||'上月字数:'||v_account.num0||'本月字数'||v_account.num1); 
end;

4.1.4 异常

在运行程序时出现的错误叫做异常
发生异常后,语句将停止执行,控制权转移到PL/SQL块的异常处理部分
异常有两种类型:

  • 预定义异常-当PL/SQL程序违反Oracle规则或超越系统限制时隐式引发
  • 用户定义异常-用户可以在PL/SQL块的声明部分定义异常,自定义的异常通过RAISE语句显式引发
    预定义异常

Oracle 预定义异常 21 个

命名的系统异常 产生原因
ACCESS_INTO_NULL 未定义对象
CASE_NOT_FOUND CASE中若未包含相应的WHEN,并且没有设置ELSE时
COLLECTION_IS_NULL 集合元素未初始化
CURSER_ALREADY_OPEN 游标已经打开
DUP_VAL_ON_INDEX 唯一索引对应的列上有重复的值
INVALID_CURSOR 在不合法的游标上进行操作
INVALID_NUMBER 内嵌的SQL语句不能将字符转换为数字
NO_DATA_FOUND 使用selectinto未返回行
TOO_MANY_ROWS 执行selectinto时,结果集超过一行
ZERO_DIVIDE 除数为0
SUBSCRIPT_BEYOND_COUNT 元素下标超过嵌套表或VARRAY的最大值
SUBSCRIPT_OUTSIDE_LIMIT 使用嵌套表或VARRAY时,将下标指定为负数
VALUE_ERROR 赋值时,变量长度不足以容纳实际数据
LOGIN_DENIED PL/SQL应用程序连接到oracle数据库时,提供了不正确的用户名或密码
NOT_LOGGED_ON PL/SQL应用程序在没有连接oralce数据库的情况下访问数据
PROGRAM_ERROR PL/SQL内部问题,可能需要重装数据字典&pl./SQL系统包
ROWTYPE_MISMATCH 宿主游标变量与PL/SQL游标变量的返回类型不兼容
SELF_IS_NULL 使用对象类型时,在null对象上调用对象方法
STORAGE_ERROR 运行PL/SQL时,超出内存空间
SYS_INVALID_ID 无效的ROWID字符串
TIMEOUT_ON_RESOURCE Oracle在等待资源时超时

语法结构:

exception
when 异常类型 then
异常处理逻辑

根据上例中的代码,添加异常处理部分:

declare
v_price number(10,2);--水费单价
v_usenum T_ACCOUNT.USENUM%type; --水费字数
v_usenum2 number(10,3);--吨数
v_money number(10,2);--金额
begin
v_price:=2.45;--水费单价
select usenum into v_usenum from T_ACCOUNT where owneruuid=1 and year='2012' and month='01';
--字数换算为吨数
v_usenum2:= round( v_usenum/1000,3);
--计算金额
v_money:=round(v_price*v_usenum2,2); 
dbms_output.put_line('单价:'||v_price||'吨数:'||v_usenum2||'金额:'||v_money);
exception
    when NO_DATA_FOUND then
        dbms_output.put_line('未找到数据,请核实');
    when TOO_MANY_ROWS then
        dbms_output.put_line('查询条件有误,返回多条信息,请核实');
end;

基本语法1:

if 条件 then 
    业务逻辑
end if;

基本语法2:

if 条件 then 
    业务逻辑 
else 
    业务逻辑 
end if;

基本语法3:

if 条件 then 
    业务逻辑 
elsif 条件 then 
    业务逻辑 
else 
    业务逻辑 
end if;

需求:设置三个等级的水费5吨以下2.45元/吨5吨到10吨部分3.45元/吨,超过10吨部分4.45,根据使用水费的量来计算阶梯水费。

declare
v_price1 number(10,2);--不足5吨的单价
v_price2 number(10,2);--超过5吨不足10吨单价
v_price3 number(10,2);--超过10吨单价
v_account T_ACCOUNT%ROWTYPE;--记录型
v_usenum2 number(10,2);--使用吨数v_money number(10,2);--水费金额
begin
--对单价进行赋值
v_price1:=2.45;
v_price2:=3.45;
v_price3:=4.45;

--赋值
select * into v_account from T_ACCOUNT
where year='2012' and month='01' and owneruuid=1;
--使用吨数
v_usenum2:= round(v_account.usenum/1000,2);

--计算金额(阶梯水费)
if v_usenum2<=5 then--第一个阶梯
    v_money:=v_price1*v_usenum2;
elsif  v_usenum2>5 and v_usenum2<=10 then --第二个阶梯
    v_money:=v_price1*5 + v_price2*( v_usenum2-5);
else	--第三个阶梯
    v_money:=v_price1*5 +v_price2*5 + v_price3*( v_usenum2-10 ); 
end if;

DBMS_OUTPUT.put_line('吨数:'||v_usenum2||'金额:'||v_money||'上月字数:'||v_account.num0
    ||'本月字数'||v_account.num1);

exception
    when NO_DATA_FOUND then 
        DBMS_OUTPUT.put_line('没有找到数据');
    when TOO_MANY_ROWS then
        DBMS_OUTPUT.put_line('返回的数据有多行');
end;

4.1.6 循环

4.1.6.1 无条件循环

语法结构:

loop
--循环语句
end loop;

范例:输出从1开始的100个数

declare
v_num number:=1;
begin
    loop
      dbms_output.put_line(v_num);
      v_num:=v_num+1;
    exit when v_num>100;
    end loop;
end;

4.1.6.2 条件循环

语法结构:

while 条件
loop
end loop;
declare
    v_num number:=1;
begin
    while v_num<=100
    loop
        dbms_output.put_line(v_num);
        v_num:=v_num+1;
    end loop;
end;

4.1.6.3 for循环

基本语法:

for 变量 in 起始值..终止值
loop
end loop;
begin
    for v_num in 1..100
    loop
        dbms_output.put_line(v_num);
    end loop;
end;

4.1.7 游标

4.1.7.1 什么是游标

游标是系统为用户开设的一个数据缓冲区,存放SQL语句的执行结果。我们可以把游标理解为PL/SQL中的结果集。
在这里插入图片描述

4.1.7.2 语法结构及示例

在声明区声明游标,语法如下:

cursor游标名称 is SQL 语句;

使用游标语法

open 游标名称
loop
fetch 游标名称 into 变量
exit when 游标名称%notfound 
end loop;
close 游标名称

需求:打印业主类型为 1 的价格表代码:

declare
    v_pricetable T_PRICETABLE%rowtype;--价格行对象
    cursor cur_pricetable is select * from T_PRICETABLE where ownertypeid=1;--定义游标begin
open cur_pricetable;--打开游标loop
    fetch cur_pricetable into v_pricetable;--提取游标到变量
    exit when cur_pricetable%notfound;--当游标到最后一行下面退出循环
    dbms_output.put_line('价格:'||v_pricetable.price ||'吨位:'||v_pricetable.minnum||'-'
            ||v_pricetable.maxnum);
end loop;
close cur_pricetable;--关闭游标
end;

4.1.7.3 带参数的游标

declare
    v_pricetable T_PRICETABLE%rowtype;--价格行对象
    cursor cur_pricetable(v_ownertypeid number) is 
        select * from T_PRICETABLE where ownertypeid=v_ownertypeid;-- 定义游标
begin
open cur_pricetable(2);--打开游标loop
fetch cur_pricetable into v_pricetable;--提取游标到变量
exit when cur_pricetable%notfound;--当游标到最后一行下面退出循环
dbms_output.put_line('价格:'||v_pricetable.price ||'吨位 :'||v_pricetable.minnum||'-'
            ||v_pricetable.maxnum );
end loop;
close cur_pricetable;--关闭游标
end;

4.1.7.4 for循环提取游标值

declare
cursor cur_pricetable(v_ownertypeid number) is 
select * from T_PRICETABLE where ownertypeid=v_ownertypeid;--定义游标
begin
for v_pricetable in cur_pricetable(3)
loop
dbms_output.put_line('价格:'||v_pricetable.price ||'吨位 :'||v_pricetable.minnum||'-'
                        ||v_pricetable.maxnum);
end loop;
end;

4.2 存储函数

4.2.1 什么是存储函数

存储函数又称为自定义函数。可以接收一个或多个参数,返回一个结果。在函数中我们可以使用PL/SQL进行逻辑的处理。

4.2.2 存储函数语法结构

创建或修改存储过程的语法如下:

CREATE[OR REPLACE] FUNCTION 函数名称
(参数名称 参数类型, 参数名称 参数类型, ...RETURN 结果变量数据类型
IS
    变量声明部分; 
BEGIN
    逻辑部分;
RETURN 结果变量;
 [EXCEPTION
    异常处理部分]
END;

4.2.3 示例

需求:创建存储函数,根据地址ID查询地址名称。语句:

create function fn_getaddress(v_id number) 
return varchar2
is
v_name varchar2(30); 
begin
select name into v_name from t_address where id=v_id; return v_name;
end;

测试此函数:

select fn_getaddress(3) from dual

输出内容
在这里插入图片描述
需求:查询业主ID,业主名称,业主地址,业主地址使用刚才创建的函数来实现。

select id 编号,name 业主名称,fn_getaddress(addressid) 地址 from t_owners

查询结果如下:
在这里插入图片描述

4.3 存储过程

4.3.1 什么是存储过程

存储过程是被命名的PL/SQL块,存储于数据库中,是数据库对象的一种。应用程序可以调用存储过程,执行相应的逻辑。
存储过程与存储函数都可以封装一定的业务逻辑并返回结果,存在区别如下:

  • 存储函数中有返回值,且必须返回;而存储过程没有返回值,可以通过传出参数返回多个值。
  • 存储函数可以在select语句中直接使用,而存储过程不能。过程多数是被应用程序所调用。
  • 存储函数一般都是封装一个查询结果,而存储过程一般都封装一段事务代码。

4.3.2 存储过程语法结构

创建或修改存储过程的语法如下:

CREATE [OR REPLACE] PROCEDURE 存储过程名称
(参数名 类型, 参数名 类型, 参数名 类型)
IS|AS
    变量声明部分;
BEGIN
    逻辑部分
[EXCEPTION
    异常处理部分]
END;

参数只指定类型,不指定长度
过程参数的三种模式:

  • IN传入参数(默认)
  • OUT传出参数,主要用于返回程序运行结果
  • IN OUT传入传出参数

4.3.3 示例

4.3.3.1 创建不带传出参数的存储过程:添加业主信息

--增加业主信息序列
create sequence seq_owners start with 11;
--增加业主信息存储过程
create or replace procedure pro_owners_add (
v_name varchar2,
v_addressid number,
v_housenumber varchar2,
v_watermeter varchar2,
v_type number
)
is
begin
    insert into T_OWNERS
        values(seq_owners.nextval,v_name,v_addressid,v_housenumb er,v_watermeter,sysdate,v_type);
commit;
end

PL/SQL中调用存储过程:

call pro_owners_add('赵伟',1,'999-3','132-7',1);

JDBC调用存储过程:

/** 增加 */
public static void add(Owners owners){
    java.sql.Connection conn=null;
    java.sql.CallableStatement  stmt=null;
    try {
        conn=BaseDao.getConnection();
        stmt=conn.prepareCall("{call pro_owners_add(?,?,?,?,?)}");
        stmt.setString(1, owners.getName());
        stmt.setLong(2, owners.getAddressid());
        stmt.setString(3, owners.getHousenumber());
        stmt.setString(4, owners.getWatermeter());
        stmt.setLong(5, owners.getOwnertypeid());
        stmt.execute();
    } catch (SQLException e)  {
        e.printStackTrace();
    }finally {
        BaseDao.closeAll(null, stmt, conn);
    }
}

4.3.3.2 创建带传出参数的存储过程

需求:添加业主信息,传出参数为新增业主的ID

--增加业主信息存储过程
create or replace procedure pro_owners_add (
v_name varchar2, v_addressid number, v_housenumber varchar2, v_watermeter varchar2, v_type number,
v_id out number
)
is
begin
select seq_owners.nextval into v_id from dual;
insert into T_OWNERS
    values( v_id,v_name,v_addressid,v_housenumber,v_watermete r,sysdate,v_type );
commit;
end;

PL/SQL调用该存储过程

declare
v_id number;--定义传出参数的变量
begin
pro_owners_add('王旺旺',1,'922-3','133-7',1,v_id);
DBMS_OUTPUT.put_line('增加成功,ID:'||v_id);
end;

执行成功后输出结果:
在这里插入图片描述
JDBC调用存储过程

/** 增加 */
public static long add(Owners owners){
    long id=0;
    java.sql.Connection conn=null;
    java.sql.CallableStatement stmt=null;
    try {
        conn=BaseDao.getConnection();
        stmt=conn.prepareCall("{call pro_owners_add(?,?,?,?,?,?)}");
        stmt.setString(1,owners.getName());
       stmt.setLong(2,owners.getAddressid());
       stmt.setString(3,owners.getHousenumber());
       stmt.setString(4, owners.getWatermeter());
       stmt.setLong(5, owners.getOwnertypeid());
       stmt.registerOutParameter(6,OracleTypes.NUMBER);//注册传出参数类型
       stmt.execute();
       id=stmt.getLong(6);//提取传出参数
    } catch (SQLException e) {
        e.printStackTrace();
    }finally {
        BaseDao.closeAll(null, stmt, conn);
    }
    return id;
}

4.4 触发器

4.4.1 什么是触发器

数据库触发器是一个与表相关联的、存储的 PL/SQL 程序。每当一个特定的数据操作语句(Insert,update,delete)在指定的表上发出,Oracle 自动地执行触发器中定义的语句序列。
触发器可用于

  • 数据确认
  • 实施复杂的安全性检查
  • 做审计,跟踪表上所做的数据操作等
  • 数据的备份和同步

触发器分类

  • 前置触发器(BEFORE)
  • 后置触发器(AFTER)

4.4.2 创建触发器的语法

语法:

CREATE [or REPLACE] TRIGGER	触发器名
BEFORE|AFTER
[DELETE][[or] INSERT][[or]UPDATE [OF 列名]]
ON 表名
[FOR EACH ROW ][WHEN(条件)]
declare
......
begin
PLSQL 块
end;

FOR EACH ROW作用是标注此触发器是行级触发器/语句级触发器
在触发器中触发语句与伪记录变量的值

触发语句 :old :new
Insert 所有字段都是空(null) 将要插入的数据
Update 更新以前该行的值 更新后的值
delete 删除以前该行的值 所有字段都是空(null)

4.4.3 案例

4.4.3.1 前置触发器

需求:当用户输入本月累计表数后,自动计算出本月使用数 。

create or replace trigger tri_account_updatenum1
    before
    update of num1
    on t_account
    for each row
declare
begin
:new.usenum:=:new.num1-:new.num0;
end;

4.4.3.2 后置触发器

需求:当用户修改了业主信息表的数据时记录修改前与修改后的值

--创建业主名称修改日志表:用于记录业主更改前后的名称
create table t_owners_log (
updatetime date, ownerid number, oldname varchar2(30), newname varchar2(30)
);

--创建后置触发器,自动记录业主更改前后日志
create trigger tri_owners_log 
after
update of name
on t_owners
for each row
declare
begin
insert into t_owners_log values(sysdate,:old.id,:old.name,:new.name);
end;

测试

--更新数据
update t_owners set name='杨小花' where id=3; 
commit;
--查询日志表
select * from t_owners_log;
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章