快速入门到精通:一篇学会Oracle
Oracle全面的基础知识和知识点的举例,帮助初学者快速入门。SQL语句,SQL查询,Oracle函数,代码块知识,条件判断/循环语句,索引、游标,锁,视图,存储过程,触发器,分区表,Oracle优化,建模
本文是将Oracle主要知识点由浅及深的系统性整理下来
目的有二:一是用于回顾与自查,二是希望能帮到学习Oracle的同志们
涉及的知识点多且广,定有很多不及的地方,请指点
目录
1 Oracle系统体系结构介绍
Oracle是一种关系型数据库。
Oracle体系结构主要有两部分组成:实例(Instance)和数据库(Database)。
1.1 启动关闭Oracle实例
1.如何启动Oracle实例:
(1)先打开计算机管理中的服务,查看Oracle的几个服务是否打开。
(2)打开终端。
(3)启动监听。
lsnrctl start
查看监听内容:lsnrctl status
关闭监听: lsnrctl stop
(4)打开新的终端,用管理员权限登录到Oracle数据库服务器。
sqlplus / as sysdba
(5)启动Oracle实例。
startup
如果希望启动实例时自动应用任何未完成的恢复操作:
startup recover
2.关闭数据库实例
数据库的实例关闭有几种模式:
SHUTDOWN ABORT:用于紧急情况的关闭
SHUTDOWN IMMEDIATE:快速关闭,终止一切用户的相关活动。
SHUTDOWN NORMAL:默认方式,关闭数据库需用户断开连接。
2 SQL语句
Oracle的SQL语句分为4类:数据控制语言 DCL、数据定义语言 DDL、数据查询语言 DQL、数据操作语言 DML
2.1数据控制语言 DCL
为数据库用户赋予权限或者收回权限。
grant 赋予权限
revoke 收回权限
1.给用户赋予基本的操作权限:
grant 权限的类型和名字 to 用户名;
grant connect,resource,dba to gzd;
2.收回权限:
revoke 权限的类型和名字 from 用户名;
revoke resource,dba from gzd;
3.主要的权限的类型和名字:
connect 连接的权限
resource 写代码的权限
dba 管理的权限
2.2数据定义语言 DDL
create、drop可对数据库用户、表、索引、自定义函数、存储过程等进行创建、删除。
alter可对用户信息进行修改,对表结构内容进行修改。
truncate可清空表中数据。
create 创建
alter 修改
drop 删除
truncate 清空
2.2.1 用户类的创建、删除和修改:
在创建用户时需要登陆有管理员DBA权限的账户。
create user 用户名 identified by 密码;
新建的用户必须赋予权限
create user user1 identified by 123456;
删除用户
drop user 用户名;
修改用户密码:
alter user 用户名 identified by 密码;
2.2.2 表的创建、删除、修改、清空
创建表:
create table 表名(
列名1 数据类型 [primary key],
列名2 数据类型 [unique],
列名3 数据类型 [not null | null],
列名4 数据类型 check(条件),
列名5 数据类型 [references 表名(列名)]
...
列名N 数据类型 [default 默认值] [各种约束]
);
举例:
create table student_info(
s_name varchar2(20) primary key,
s_card varchar2(20) unique,
s_age number not null,
s_sex varchar2(5) check(s_sex in ('M','F')),
class_id number not null,
status varchar(20) default 'active'
);
删除表:
drop table 表名;
修改表的名字:
alter table 旧表名 rename to 新表名;
清空表中数据:
truncate 表名;
2.2.3 alter的用法(关于表结构)
字段有相应的数据类型、约束条件。
一、数据类型:
数据类型主要有三种:字符型(varchar)、数字型(number)、时间日期型(date)。
添加字段:alter table TABLE_NAME add COLUMN_NAME varchar2(10);
删除字段:alter table TABLE_NAME drop column COLUMN_NAME;
更新字段名:alter table TABLE_NAME rename column column_old to column_new;
修改字段数据类型:alter table table_name modify 字段名 varchar2(20);
二、约束条件:
约束条件有以下几种类型:
1. 主键:表格中最核心的列 primary key 一个表只能有一个主键
不能重复唯一,也不能为空
2. 非空:not null
填写的内容不能为空
3. 唯一: unique
数据不能重复可以为空,例如身份证或者手机号码
4. 检查: check
限制数据在某个范围之内
5. 外键: foreign key 外键只能和另一个表的主键关联,外键可以有多个
foreign key (从表列名) references 主表的表名(它的主键字段名)
举例:有表student_info 和表class
alter table student_info
add constraint fk_class_id
foreign key(class_id) references class(class_id);
6.默认值:default
当没有给列指定值的时候,则取默认值。
修改约束条件:
alter table 表名modify(列名 数据类型 约束条件);
增加约束条件:
alter table 表名 add constraint 约束条件的名字 约束关系(列名);
2.3数据操作语言 DML
主要是对表进行插入数据,修改数据,删除数据。
insert 插入
update 修改
delete 删除
我就以曾写过的SQL来展示。
insert into student_info(s_name,s_age,s_sex,class_id) values('dave',18,'M',110);
update student_info set s_age = 20 where s_name = 'dave';
delete from student_info where s_name = 'dave';
2.4数据查询语言 DQL
Select 查询
查询语句是工作中使用最频繁的语句。
select * from student_info;
3 DQL数据查询语言
查询语句大致有6种处理过程:去重distinct查询、where条件查询、group by分组查询、having过滤查询、order by排序查询、分页查询,还有3种多表进行的数据共查:多表查询、联合查询、子查询。这些方式都是为了便于得到我们想要的数据。
语法:
select [DISTINCT] [字段]|[*]
from 表名
[where 查询条件]
[group by 分组条件]
[having 过滤条件]
[order by 排序条件 asc|desc]
[分页条件];
查询条件执行顺序:
1. FROM阶段
2. WHERE阶段
3. GROUP BY阶段
4. HAVING阶段
5. SELECT阶段
6. ORDER BY阶段
3.1 去重distinct查询
--查询所有部门的编号
select distinct deptno from emp;
3.2 where条件查询
除去上面的运算符,还有
逻辑运算符与或非:and、or、not
类似于in的exist
例between的用法
--输出工资在2000-4000范围的数据
select * from emp
where sal between 2000 and 4000;
3.3 group by分组查询
3.4 having过滤查询
Having 过滤查询的功能和where一致,不同的地方是执行顺序不同。
3.5 order by排序查询
这里order by 为啥可以用别名sum_sal,而having不能用别名呢?
是因为前面所讲的执行顺序的知识。
Order by的执行顺序在select之后。
3.6 分页查询
分页查询在MySQL上用法比较方便:limit
在Oracle的用法是:
给表加上行号的字段,再通过子查询的方式来实现。
3.7 多表查询
多表查询是多张表相关联进行的查询,关键点在于表关联字段。
3.7.1 内连接
select * from a
join b on a.条件=b.条件
where 条件;
e.deptno=d.deptno的作用:它对两个表进行连接查询,oracle会扫描dept表,分别检查每条记录在连接条件e.deptno=d.deptno中字段的值从emp表取出的记录的列值是否相等。如果相等,则将这两个记录连接,产生一条新的记录作为查询到的一行,再继续从表emp中取下一条记录。重复这个步骤,一直到处理完emp表的全部符合条件的记录。
3.7.2 外连接
外连接有三种类型:左连接、右连接、全连接。
3.7.2.1 左连接
作用:可以显示相等连接时左表存在右表不存在的数据,右表不存在的记录用空值显示
select * from a left join b on a.条件=b.条件
3.7.2.2 右连接
作用:可以显示相等连接时左表不存在右表存在的数据,左表不存在的记录用空值显示
select * from a right join b on a.条件=b.条件
3.7.2.3 全连接
作用:可以显示相等连接时左右表全部存在的数据、左右表互相不存在的记录用空值显示
select * from a full join b on a.条件=b.条件
3.7.3 交叉连接
交叉连接的原理是笛卡尔积,也就是两表的乘积。
假如a表有3条数据,b表有4条数据,那么交叉连接后共12条数据。
select * from a cross join b
3.8 联合查询
联合查询就是将上下两个结果集进行并集、交集、差集运算。
取结果集时有几点注意:
1.上下两个结果集的字段的类型要一致。
2.上下两个结果集的字段的顺序要一致。
3.上下两个结果集的字段的数量要一致。
3.8.1 并集
并集有两种处理方式:union all和union,一个是两个结果集数据全取合并成一个结果集,一个是做去重后的结果集。
例:
3.8.2 交集
intersect:显示两个结果集共有的数据部分。
3.8.3 差集
minus:显示第一个结果集A有的内容,但是第二个结果集B没有的数据,即A-B。
例:
运行该语句的结果显示如下:
运行这条语句显示:
取差集后:
3.9 子查询
3.9.1 FROM子句中的子查询
3.9.2 运算符的子查询
运算符主要有:>、>=、<、<=、!=、<>、=、<=>、in、not in、any、some、all、exits、not exists
现以exists举例(主要当初学习exists,摸索了好两天才学明白它的作用):
用法:select * from A where exists(select * from B where A.id = B.id);
原理:
- 先执行外查询select * from A,然后取出一条数据传给内查询。
- 再执行内查询select * from B,外查询传入的数据和内查询的数据如果满足where条件A.id = B.id,则返回ture,如果一条都不满足,则返回false。
- 内查询返回true,则外查询的这行数据保留,反之内查询返回false则外查询的这行数据不保留。外查询的所有数据逐行查询传入内查询匹对。
例:
3.9.3 SELECT下的子查询
Select下的子查询使用情况我列举了3个常见用法,可看案例:
3.9.4 with...as 子查询
with...as用来定义一个sql片段,且该片段会被整个sql语句反复使用很多次,这个sql片段就相当于是一个公用临时表
例:
with aaa as (select * from emp)
select * from aaa;
with...as需和查询语句一起执行,不能在with..as后加分号,不然报错无select关键字。另外查询语句有多表查询不能有相同的列,否则,with..as无法识别具体哪个列。
4 Oracle函数
Oracle的常见函数我有整理成表格形式,没有用法,只有函数的功能,可通过搜索该函数去网上找相应的用法。列举一些常用的,不过我会持续更新。
4.1 数值型函数
数值函数 |
近似函数,五舍四入:round(x,2) |
向下取整函数:floor(x) |
向上取整函数:ceil(x) |
取随机函数:DBMS_RANDOM.VALUE(a,b) |
幂运算函数:power(x,n) |
平方根函数:sqrt(x) |
绝对值函数:abs(x) |
取余函数:mod(x,y) |
4.2 字符型函数
字符串函数 |
截取字符串的一部分:substr(字符串, 开始位置, 连续取值的长度) |
两个字符串的拼接:concat(x1, x2) |
字符串替换:replace(x, old, new) |
字符查找函数:instr(源字符串, 目标字符串, 起始位置, 匹配序号) |
转大写字母:upper() |
转小写字母:lower() |
返回一个字符串的长度:length() |
4.3 时间日期函数
日期函数 |
返回当前日期和时间:SYSDATE |
从一个date类型中截取year,month,day:extract() |
计算日期的月份偏移 正数时间往后推移,负数时间往前推移:add_months(x, m) |
计算日期的最后一天:last_day(x) |
计算两个日期之间的月份差:months_between(x1, x2) |
例:
select extract(year from sysdate),extract(month from sysdate),extract(day from sysdate) from dual;
select months_between(date'2020-9-2',date'2019-9-1') from dual;
4.4 聚合分组函数
聚合函数 |
SUM:计算一组数值的总和。 |
AVG:计算一组数值的平均值。 |
MAX:返回一组数值中的最大值。 |
MIN:返回一组数值中的最小值。 |
COUNT:计算一组数值的个数。 |
4.5 开窗函数
4.5.1 用法
计算函数部分() over(partition by 列名 order by 列名 asc|desc)
--计算函数部分:sum,count,max,min,avg,row_number(),rank()等,只能是一个函数
--over() 关键字
--partition by :分组,根据分区表达式的条件逻辑将单个结果集分成N组,不是必选项
--order by:对分区中的数据排序,不是必选项
例:
--计算工资与部门最高工资的差
select
emp.*,max(sal) over(partition by deptno) maxsal,
sal-max(sal) over(partition by deptno) c
from emp ;
4.5.2 排名函数
1.row_number():根据某个列,按照顺序进行排序 1 2 3 4
2.rank():根据某个列,按照顺序进行排序,如果值相同,会出现并列的名次,会跳过占用的名次 1 2 2 4
3.dense_rank():根据某个列,按照顺序进行排序,如果值相同,会出现并列的名次,不会跳过名次 1 2 2 3
4.5.3 平移函数
在平常业务中一般用来实现数据的同比和环比的计算。
同比:今年的7月和去年的7月相比,在两个时间范围内,取相同时间节点的数据进行比较
环比:今年的7月和今年的6月相比,在同一个时间范围内,取相邻的时间节点的数据进行比较
lag():将数据往下平移一行 + 开窗函数
lead():将数据往上平移一行 + 开窗函数
例:
表及数据:
create table sale_info(
y number,
m number,
amount number(7,2)
);
insert into sale_info values(2018,1,2342);
insert into sale_info values(2018,2,1234);
insert into sale_info values(2018,3,3545);
insert into sale_info values(2018,4,3456);
insert into sale_info values(2018,5,2342);
insert into sale_info values(2018,6,4534);
insert into sale_info values(2018,7,3453);
insert into sale_info values(2018,8,2342);
insert into sale_info values(2018,9,4352);
insert into sale_info values(2018,10,1312);
insert into sale_info values(2018,11,3453);
insert into sale_info values(2018,12,1235);
insert into sale_info values(2019,1,3453);
insert into sale_info values(2019,2,1233);
insert into sale_info values(2019,3,3445);
insert into sale_info values(2019,4,1233);
insert into sale_info values(2019,5,1231);
insert into sale_info values(2019,6,4234);
insert into sale_info values(2019,7,1231);
insert into sale_info values(2019,8,2131);
insert into sale_info values(2019,9,1231);
insert into sale_info values(2019,10,3421);
insert into sale_info values(2019,11,1231);
insert into sale_info values(2019,12,1231);
commit;
需求1:计算2019年每一个月和2018年同月份的数据增长率。(同比)
需求2:计算2019年的每一个月,比上个月增长了百分之多少?(环比)
需求1:
select
b.*,c/lo,
round(c/lo*100,2)||'%' 增长率
from
(select
a.*,lag(amount) over(partition by m order by y) lo,
amount-lag(amount) over(partition by m order by y) c
from sale_info a) b
where y=2019;
注:这里可以看到同比增长率显示有问题,-0.08%不能显示。这是Oracle的显示问题,对于小于1的小数,小数点前面的0是不显示的。
解决方案:对该数值转为字符类型。
to_char(round(c/lo*100,2),'fm99990.0099')||'%' 增长率
需求2:
select
b.*,amount-lo c,
to_char(round((amount-lo)/lo*100,2),'fm99990.0099')||'%' 环比增长率
from
(select a.*,lag(amount) over(order by m) lo from sale_info a where y=2019) b;
当然,同环比的计算方法也可以用多表连接来处理,在方法上都不难。
4.6 转换函数
转换函数最常用的就4个:
- 转换成日期类型:to_date()
- 转换成字符串类型:to_char()
- 转换成数值类型:to_number()
- 将某种数据类型的表达式显式转换为另一种数据类型:cast(表达式 as 数据类型)
-- 日期转字符串
SELECT CAST(SYSDATE AS VARCHAR2(20)) FROM dual;
其他还有进制类等的相互转换,因也没用过,就不说明了。
4.7其他函数
其他函数 |
rownum:取行号函数 |
rowid:数据去重,表里面每一行数据,在数据库中的唯一编号,不会出现重复的数据 |
pivot:行列转换 |
case when:条件取值函数 |
decode:功能同case when差不多,也是条件取值 |
unpivot:列转行函数 |
nvl(列名, 如果这列为空时设置的默认值) |
nvl2(列名, 如果不为空显示的内容, 如果为空显示的内容) |
sign():是用来判断数据是负数、0、正数分别返回-1、0、1 |
5 PLSQL编程
5.1格式写法
declare
--变量的声明部分
a number;
b varchar2(10);
c date;
begin
--代码的逻辑和执行部分
a:=100;
b:='hello';
c:=sysdate;
dbms_output.put_line(a||b||c);
end;
5.2 变量
变量主要有3种类型:普通变量、引用型变量、记录型变量。
5.2.1 普通变量
普通变量
a number;
5.2.2 引用型变量
格式:变量名 表名.列名%type;
declare
v_sal emp.sal%type;
begin
select sal into v_sal from emp where empno=7369;
dbms_output.put_line('工资是'||v_sal);
end;
5.2.3 记录型变量
记录型变量也叫数组型变量
格式:变量名 表名%rowtype;
declare
v_user emp%rowtype;
begin
select * into v_user from emp where empno=7369;
dbms_output.put_line(v_user.ename||','||v_user.job);
end;
5.2.4 变量输入
变量值的输入:
数字类型的变量名:=&提示语句;
字符串类型的变量名:='&提示语句';
例:
declare
v_user emp%rowtype;
--v_empno emp.empno%type;
v_ename emp.ename%type;
begin
--v_empno:=&输入要查询的员工编号;
v_ename:='&输入员工姓名';
select * into v_user from emp where ename=v_ename;
dbms_output.put_line(v_user.ename||','||v_user.job);
end;
注:
1.在代码块中,所有的select都一定要有into来存放你查询出来的数据
into 变量,只能放一个数据,并且一定要有数据。
2.在变量输入提示语句中,字符串和数值的一个有引号一个没有引号
5.3 If条件判断语句
If判断语句3种写法,应该都不难于理解。
1.
if 条件判断 then
执行sql语句;
end if;
2.
if 条件判断 then
执行sql语句;
else
执行sql语句;
end if;
3.
if 条件判断 then
执行sql语句;
elsif 条件判断 then
执行sql语句;
elsif 条件判断 then
执行sql语句;
......
else
执行sql语句;
end if;
和if语句相同类型和效果的另一些判断语句的写法:case when
case
when 条件判断 then 执行的sql语句 ;
when 条件判断 then 执行的sql语句 ;
when 条件判断 then 执行的sql语句 ;
......
else 执行的sql语句;
end case;
例:输入部门编号得到部门员工数量。
declare
v_deptno number :=&输入部门编号;
c number;
v_user emp%rowtype;
begin
select count(*) into c from emp where deptno=v_deptno;
if c=0 then
dbms_output.put_line('部门编号:'||v_deptno||'的部门没有员工');
elsif c>=2 then
dbms_output.put_line('部门编号:'||v_deptno||'的部门有'||c ||'名员工');
else
select * into v_user from emp where deptno=v_deptno;
dbms_output.put_line('部门编号:'||v_deptno||'的部门'||'员工编号:'||v_user.empno
||'的工资是'||v_user.sal);
end if;
end;
5.4 循环语句
Oracle循环有3种:for循环、while循环、loop循环。
循环终止有两个关键字:
continue:直接开始下一次的循环,忽略掉continue后面的代码。
exit:跳出整个循环,停止循环。
在循环中有个特别的关键语句:goto,但是不建议使用,容易引起逻辑的混乱,不过也有奇效,后续不再讲,就在此处单独说说。
定义:可以理解为一个标签,使用goto能进行代码的任意的跳转。
例:(可以自己试验下结果就能理解它的定义作用)
DECLARE
BEGIN
FOR i IN 1..10 LOOP
<<loop_start>>
IF i = 5 THEN
DBMS_OUTPUT.PUT_LINE('hello');
GOTO loop_end;
END IF;
DBMS_OUTPUT.PUT_LINE(i);
<<loop_end>>
NULL;
END LOOP;
DBMS_OUTPUT.PUT_LINE('world');
END;
5.4.1 for循环
功能:有循环范围的循环方式,一开始就知道运行次数的循环方法。
格式:
for 变量名 in 循环范围 loop
执行的sql语句;
end loop;
例1:将1+3+5++......+99的和,计算出来
declare
s number := 0;
begin
for i in 1..99 loop
if i mod 2 = 1 then
s := s + i;
end if;
end loop;
dbms_output.put_line('1到99之间所有奇数的和为: ' || s);
end;
例2:输入部门编号,输出部门人数。
DECLARE
v_dept NUMBER := &请输入部门编号;
c NUMBER;
BEGIN
-- 统计指定部门的员工数量
SELECT COUNT(*) INTO c FROM emp WHERE deptno = v_dept;
-- 根据员工数量显示不同信息
CASE
WHEN c = 0 THEN
DBMS_OUTPUT.PUT_LINE('部门编号:' || v_dept || '的部门没有员工');
ELSE
DBMS_OUTPUT.PUT_LINE('部门编号:' || v_dept || '的部门有' || c || '名员工!!!');
-- 循环显示该部门所有员工信息
FOR i IN (SELECT * FROM emp WHERE deptno = v_dept) LOOP
DBMS_OUTPUT.PUT_LINE('-----' || i.empno || ' ' || i.ename || ' ' || i.job);
END LOOP;
END CASE;
END;
/
5.4.2 While循环
while循环,当循环的范围不是特别确定的时候。
while条件如果判断为真,就进入到循环,否则跳出循环
格式:
while 条件判断 loop
执行的sql语句;
end loop;
例:输出数字1到10
DECLARE
i NUMBER := 1; -- 初始化计数器
BEGIN
WHILE i <= 10 LOOP -- 循环条件
DBMS_OUTPUT.PUT_LINE(i); -- 输出当前数字
i := i + 1; -- 计数器递增
END LOOP;
END;
5.4.3 Loop循环
Loop循环也是适用当循环的范围不是特别确定的时候。
Loop循环条件判断为真,则exit退出循环,否则进入循环。
格式:
loop
exit when 条件判断;
执行的sql语句;
end loop;
例:输出10,8,6,4,2,0
DECLARE
n NUMBER;
BEGIN
n := 10;
LOOP
EXIT WHEN n < 0;
DBMS_OUTPUT.PUT_LINE(n);
n := n - 2;
END LOOP;
END;
5.3.4 continue和exit
定义:
continue:直接开始下一次的循环,忽略掉continue后面的代码
exit:跳出整个循环,停止循环
用例来说明它们之间的区别:
DECLARE
BEGIN
FOR i IN 1..5 LOOP
FOR j IN 1..3 LOOP
IF j = 2 THEN -- 当j=2时跳过当前迭代,继续下一个内循环j=3
CONTINUE;
END IF;
DBMS_OUTPUT.PUT_LINE('i=' || i || ', j=' || j);
END LOOP;
DBMS_OUTPUT.PUT_LINE('--- a ---'); -- 外层循环每次结束后输出
END LOOP;
END;
/
输出结果:
i=1, j=1
i=1, j=3
--- a ---
i=2, j=1
i=2, j=3
--- a ---
i=3, j=1
i=3, j=3
--- a ---
i=4, j=1
i=4, j=3
--- a ---
i=5, j=1
i=5, j=3
--- a ---
DECLARE
BEGIN
FOR i IN 1..5 LOOP
FOR j IN 1..3 LOOP
IF j = 2 THEN -- 当j=2时跳出内层循环
EXIT;
END IF;
DBMS_OUTPUT.PUT_LINE('i=' || i || ', j=' || j);
END LOOP;
DBMS_OUTPUT.PUT_LINE('--- a ---'); -- 外层循环每次结束后输出
END LOOP;
END;
/
输出:
i=1, j=1
--- a ---
i=2, j=1
--- a ---
i=3, j=1
--- a ---
i=4, j=1
--- a ---
i=5, j=1
--- a ---
根据结果显示:很明显的看出continue是结束内层当前循环,继续下一次的内层循环。exit是直接结束整个循环,整个循环没有后续循环操作了。
5.5 动态SQL
定义:将SQL语句写在一个字符串中,在存储过程中解析字符串执行SQL。
一般情况下代码块只支持dml和dql语句。 增加关键字 execute immediate 可以执行动态sql语句。
继续举例说明:
需求:备份所有E开头的表,备份的表名格式:原表名_月日,如:emp_0729。
--要先准备一个表,用来存储当前所需要的找出来的表名
-- 创建临时表存储表名
CREATE TABLE tmp_t (tn VARCHAR2(100));
DECLARE
c NUMBER; -- E开头的表的数量
v_tn VARCHAR2(100); -- 存储表名的变量
s VARCHAR2(200);
BEGIN
-- 找出E开头的表的数量
SELECT COUNT(*) INTO c FROM user_tables WHERE table_name LIKE 'E%';
-- 将表名保存到临时表中
INSERT INTO tmp_t SELECT table_name FROM user_tables WHERE table_name LIKE 'E%';
COMMIT;
-- 循环创建备份表
FOR i IN 1..c LOOP
SELECT tn INTO v_tn FROM (SELECT tn, ROWNUM r FROM tmp_t) WHERE r = i;
s := 'CREATE TABLE ' || v_tn || '_0926 AS SELECT * FROM ' || v_tn;
DBMS_OUTPUT.PUT_LINE('正在创建备份表: ' || s);
EXECUTE IMMEDIATE s;
END LOOP;
DBMS_OUTPUT.PUT_LINE('已完成所有E开头表的备份,共备份 ' || c || ' 张表');
END;
另外说明一个知识点,打印输出:
dbms_output.put是总共就输出一行,然后在后面依次输出数据(变成一行多列)。
dbms_output.put_line是总共就输出一列,然后在下面依次输出数据(变成一列多行)。
6 索引
6.1 索引含义
什么是索引?
索引相当于目录,是一种数据结构。是对某些特定列中的数据进行排序,生成索引表,该列作为WHERE条件时,扫描索引列,根据ROWID快速定位具体记录,提高查询效率。
创建索引的条件:
- 首先表要大,如果只有几兆,还不如不建,全表扫描说不定更好;
- select 操作占大部分的表上创建索引;
- where 子句中出现最频繁的列上创建索引;
- 索引的数量不宜过多,看表的大小和字段数量合适规划;
- 索引需要维护,它会降低DML语句的速度,所以大量的DML时,可以先删除再创建。
6.2 使用索引
添加索引的语法:
create index 索引名 on 表名(列名);
删除索引的语法:
drop index 索引名;
查看索引:
select * from user_indexes 查询现有的索引
select * from user_ind_columns 可获知索引建立在那些字段上
修改索引:alter
写法都没必要讲,一个表最多就那么几个索引,直接删了重建更好。
例:快速查到某个表的索引建立在那些字段上
select *
from user_ind_columns
where lower(TABLE_NAME) = 'emp';
6.3 索引类别
主要索引结构:B树索引、位图索引、函数索引、组合索引、唯一索引和分区表索引。
6.3.1 主键索引、唯一索引、外键索引
主键索引:在创建表的时候,添加了主键约束,那么就会自动生成主键索引。
alter table emp add constraint pri_empno primary key(empno);
--通过主键约束的创建,来自动生成这个主键索引
唯一索引:在创建表的时候,会根据唯一约束自动的生成唯一索引,唯一索引确保索引列中的值唯一。
外键索引:外键索引用于定义表的主键;外键索引用于加速外键关联查询。
6.3.2 普通索引
普通索引:这个列经常需要被查询,但是这个列又没有什么特点
create index idx_ename on emp(ename);
6.3.3 组合索引
就是普通索引的升级版。
组合索引:和普通索引相同,有多个列需要同时被查询,但是这些列也没有什么特点,那么就放在一起,创建一个组合索引
create index 索引名 on 表名(a,b,c);
create index idx_sal_job on emp(sal,job);
注:
1.在查询时需要使用第一列的列查询,否则组合索引会失效。
2.组合索引中应将最具选择性的列放在最前面。选择性就是列中的值唯一或几乎唯一。这样能有效的减少查询的结果集大小,从而提高查询性能。
6.3.4 函数索引
函数索引:如果你的列,是需要被使用函数计算之后再用来查询,那么计算过程需要的函数要写在索引里面
create index idx_func_hiredate on emp(to_char(hiredate,'yyyy'));
6.3.5 位图索引
位图索引:列的内容唯一值少,如性别、状态 bitmap
create bitmap index idx_emp_deptno on emp(deptno);
6.3.6 分区表索引
分区表索引分为:本地局部索引、全局索引。
局部索引用于表分区,每个分区都有自己的索引,而全局索引更适合用于需要跨多个分区进行查询的场景。
1.局部索引
每个表分区对应一个索引分区,增加表分区时会自动创建对应的索引分区,删除也是
create index idx_hash_ename on emp_hash(ename) local;
2.全局索引
用于跨分区查询数据
create index idx_hash_job on emp_hash(job) global;
--全局索引测试1,索引分区键和表分区键相同,成功
create index ig_test_id on testindex(id) global
partition by range(id)
( partition p1 values less than (5),
partition p2 values less than (30),
partition p3 values less than (maxvalue)
);
--全局索引测试2,:报错GLOBAL 分区索引必须加上前缀。(全局索引是指索引键值必须和分区键值相同,也就是所谓的前缀索引)
create index i_test_id on testindex(name) global
partition by range(id)
( partition p1 values less than (5),
partition p2 values less than (30),
partition p3 values less than (maxvalue)
);
区别:
局部索引是建在分区表上,全局索引和表没有直接的关联;
6.3.7 降序索引
降序存储索引键值,支持ORDER BY ... DESC查询优化
ceeate index emp_salary_desc_idx on employees(salary desc);
6.4 索引失效情况及解决方案
1. 经常需要被修改的列,索引是一直处于失效状态的
重建索引
2. 数据发生了隐性的转换,比如使用了函数。
select * from emp where to_char(hiredate,'yyyy')=1981;
新建针对该列的函数索引
3. 计算的时候,公式放在了等号的左边
select * from emp where sal+1000=4000 and job='MANAGER';
修改为:
select * from emp where sal=3000 and job='MANAGER';
4. 查询的时候使用不等于
select * from emp where deptno!=20;
修改为:
select * from emp where deptno<20 or deptno>20;
5. 查询的时候使用了like模糊查询
select * from emp where ename like '%LL%';
6. 空值查询
select * from emp where ename is null;
7. 组合索引没有使用第一列的列查询
8. 查询的时候,使用了非函数索引的函数
7 游标
定义:
游标是用来存储多条查询查询数据的一种数据结构,通过指针,从上而下移动,遍历每条数据。因为遍历的数据依次是保存到内存中,所以当数据量很大,容易造成内存不足而崩溃。
游标分为静态游标和动态游标。
7.1 静态游标
定义:在编译期间就声明定义了数据结果集,所以结果集是固定的。如果在静态游标打开后对表进行修改,则游标的结果集不会受到影响。
写法:
声明一个游标
cursor 游标名 is select 语句;
打开游标
open 游标名;
使用游标,获取游标当前的内容
fetch 游标名 into 变量;
关闭游标
close 游标名;
实例:(用while循环操作游标,打印出emp表所有员工信息)
DECLARE
-- 声明一个游标
CURSOR mc IS SELECT * FROM emp;
v_user emp%ROWTYPE;
BEGIN
-- 打开游标
OPEN mc;
-- 使用游标
FETCH mc INTO v_user;
WHILE mc%FOUND LOOP
DBMS_OUTPUT.PUT_LINE('员工编号' || v_user.empno ||
'的部门号是' || v_user.deptno ||
',工资是' || v_user.sal);
FETCH mc INTO v_user;
END LOOP;
-- 关闭游标
CLOSE mc;
END;
/
游标属性及其作用:
属性 |
返回值类型 |
作用 |
sql%isopen |
布尔型 |
判断游标是否 '开启' |
sql%found |
布尔型 |
判断游标是否 '获取' 到值 |
sql%notfound |
布尔型 |
判断游标是否 '没有获取' 到值(常用于 "退出循环") |
sql%rowcount |
整型 |
'当前' 成功执行的数据行数(非 "总记录数") |
例:通过sql关键字来查看,本次数据修改的范围,一共有多少行。
DECLARE
BEGIN
-- 删除工资大于等于5000的员工记录
DELETE FROM emp WHERE sal >= 5000;
DBMS_OUTPUT.PUT_LINE('删除记录数: ' || SQL%ROWCOUNT);
-- 更新工资低于2000的员工记录
UPDATE emp SET sal = 2000 WHERE sal < 2000;
DBMS_OUTPUT.PUT_LINE('更新记录数: ' || SQL%ROWCOUNT);
COMMIT; -- 提交事务
END;
/
7.2 动态游标
定义: 动态游标是在运行时定义的,用于在查询结果集可能发生更改的情况下遍历结果集。如果在动态游标打开后对表进行修改,则游标的结果集会更新。
写法上与静态游标不同,需先声明游标类型,且静态游标是在声明部分定义赋值,动态游标是在代码执行部分定义赋值的。
直接举例:(用loop循环打印出emp表中部门号20的所有员工信息)
DECLARE
-- 声明一个动态游标的类型 (ref cursor)
TYPE dongtai_c IS REF CURSOR;
-- 声明游标变量
mc dongtai_c;
-- 声明行类型变量
v_user emp%ROWTYPE;
BEGIN
-- 打开游标的同时,给游标赋值
OPEN mc FOR SELECT * FROM emp WHERE deptno = 20;
-- 使用LOOP循环读取游标
LOOP
FETCH mc INTO v_user;
EXIT WHEN mc%NOTFOUND;
DBMS_OUTPUT.PUT_LINE('员工编号' || v_user.empno ||
'的部门号是' || v_user.deptno ||
',工资是' || v_user.sal);
END LOOP;
-- 关闭游标
CLOSE mc;
END;
/
7.3 案例
建表语句:
-- 创建EMPLOYEES表
CREATE TABLE EMPLOYEES (
ID NUMBER PRIMARY KEY,
NAME VARCHAR2(50) NOT NULL,
SALARY NUMBER NOT NULL,
DEPARTMENT_ID NUMBER
);
-- 插入员工数据
INSERT INTO EMPLOYEES (ID, NAME, SALARY, DEPARTMENT_ID) VALUES (1, '张三', 10000, 101);
INSERT INTO EMPLOYEES (ID, NAME, SALARY, DEPARTMENT_ID) VALUES (2, '李四', 12000, 101);
INSERT INTO EMPLOYEES (ID, NAME, SALARY, DEPARTMENT_ID) VALUES (3, '王五', 11000, 102);
INSERT INTO EMPLOYEES (ID, NAME, SALARY, DEPARTMENT_ID) VALUES (4, '赵六', 9000, 103);
INSERT INTO EMPLOYEES (ID, NAME, SALARY, DEPARTMENT_ID) VALUES (5, '钱七', 13000, 101);
需求:使用游标打印出表中员工信息及工资总额。
输出结果:
8 视图
视图是从数据库一个或多个表中导出的虚拟表,视图本身并不包含任何数据,它只包含映射到基表的一个查询语句,当基表数据发生变化,视图数据也随之变化。
作用:是为了简化查询,也提高了数据的安全性。
视图有两种分类:普通视图和物化视图。
8.1 视图操作
8.1.1 创建视图
语句:
create view 视图名 as
select 语句
with read only;
8.1.2 删除视图
drop view 视图名;
8.1.3 修改视图
语句:
修改视图:(可以在不用删除原视图的情况下更新)
create or replace view 修改;
没有则创建。
8.1.4 更新视图
和更新表的数据一样。
update view_name set name ='开发部';
注意:
因为在使用update或delete 执行某些数据时,会出现某一行删除的情况,一般会在原创建视图中末尾加上:
WITH CHECK OPTION
8.2 物化视图
定义:这是一个真实的物理的表,它将查询语句的结果集当成一个新的表保存起来,同时会对原表的数据进行同步。
格式:
create materialized view 物化视图名字
refresh on commit | demand
start with 时间点 next 下一次更新的时间点
as
select 语句;
含义:refresh on commit:类似于实时更新,原表提交数据时更新
refresh on demand: 根据需要更新
物化视图更新的方法:
1. complete 完全刷新,整个表格全部都更新一次数据
2. fast 快速刷新,更新你变更部分的数据
3. force 默认的更新方式,默认的更新方法就是fast
4. never 不要更新表格
格式:
declare
begin
dbms_mview.refresh('物化视图的名字','更新的方法');
end;
例:
-- 创建物化视图
CREATE MATERIALIZED VIEW stu_nv_201
REFRESH ON DEMAND
AS
SELECT * FROM student_a02 WHERE ssex = '男' AND sage > 20;
-- 更新基础表数据
UPDATE student_a02 SET sname = '王小三' WHERE sno = 's021';
COMMIT;
-- 手动刷新物化视图
DECLARE
BEGIN
DBMS_MVIEW.REFRESH('STU_NV_201', 'COMPLETE');
END;
/
8.3 普通视图与物化视图的区别
1.存储不同:普通视图是虚拟的表,不占用磁盘空间。物化视图是真实的表,会占用磁盘空间。
2.作用不同:普通视图是为了简化查询。物化视图是为了保存更新的数据。
3.更新方式不同:普通视图是实时更新。物化视图有两种:按需更新、提交数据时更新。
4.查询时间不同:如果原表占用内存增大,普通视图查询时间不变,而物化视图是会减少查询时间的。
5.物化视图可以建索引,普通视图不行。
9 锁
锁是面试中经常问到的题,在工作中也经常会碰到相关的问题。
锁是用于管理并发访问数据库对象的机制。锁可以防止多个会话同时修改同一个对象,从而确保数据的一致性和完整性。也就是事务只能被一个线程占用。只有这个锁释放了,其他线程才能占用。
9.1 事务
讲锁得先理解事务,事务是什么?
事务是最小的不可再分的工作单元,有DML语句产生。它有四个特征:
原子性:事务是最小单元,不可再分
一致性:事务要求DML的语句,必须保证全部同时成功提交或者同时失败回滚。
隔离性:事务A和事务B具有隔离性。
持久性:内存的数据持久到硬盘文件中。
开启事务:任何一条DML语句(insert、update、delete)执行,标志事务的开启。
事务结束:
提交:成功的结束,将所有的DML语句操作历史记录和底层硬盘数据来一次同步。
回滚:失败的结束,将所有的DML语句操作历史记录全部清空。
9.2 锁的定义
锁从需求上分为:共享锁和排他锁。
共享锁:允许多个会话同时读取一个对象,但不允许任何会话修改该对象。这种锁适用于读取操作,可以提高并发性能。
排他锁:只允许一个会话同时对一个对象进行读取和修改操作。当一个会话持有排他锁时,其他会话无法读取或修改该对象。这种锁适用于写操作,可以确保数据的一致性。
从锁定对象上分为:表级锁和行级锁。
表级锁:当事务修改表数据时,获得表级锁,防止其他会话执行 DDL 操作,保证表结构及数据不会改变。
行锁:事务锁,发生在行修改时,保证事务期间数据不被其他事务修改。
从上锁的主动性和被动性上看分为:乐观锁和悲观锁。
乐观锁:数据库默认的。
悲观锁:需要通过for update提前占用数据的资源。
写法:select 语句 + for update;
在用户A修改之前就对返回的数据集进行上锁,防止其他用户的修改。如果用户B修改数据,那么他就会产生阻塞,需等到用户A事务结束。
悲观锁的缺点:主动加锁可能会导致时间过长,限制其他用户的访问。所以从并发性上看不推荐使用。
9.3 死锁原因及处理
死锁产生的原因:当两个用户互相希望持有对方的资源时就会发生死锁,也就是说两个用户互相等待对方释放资源时,这个时候就产生了死锁。
(举例易懂):
1:用户 1 对 A 表某行数据进行 Update,没有提交事务。
2:用户 2 对 B 表某行数据进行 Update,没有提交事务。
此时双方不存在资源共享的问题。
3:如果用户 2 此时对 A 表中那行数据作 update,则会发生阻塞,需要等到用户1的事务结束。
4:如果此时用户 1 又对 B 表的那一行数据作 update,也产生阻塞,等待用户2的事务结束。
这样双方都在等待对方释放锁资源,这就是死锁。
处理办法:只需释放掉其中一个资源即可。
--查看所有被上了锁的对象 69539
select * from v$locked_object;
--找到锁的对象,是在哪个表上面,根据自己找到的这个表的名字,回去找session_id 131
select * from dba_objects where object_id=69539;
--找锁的id的 serial# 9555
select * from v$session where sid=131;
--根据这两个编号,杀掉这个锁
alter system kill session '131,9555';
10 存储过程
存储过程的形式学习简单,就是带有名字的代码块。
什么时候会用到存储过程?
一个固定的功能的代码块,并且这个代码需要经常的反复的运行。
比如:
- 常用的业务计算。
- 数据增量、全量操作。
- 日志的操作(如每天建立新的日志表分区)。
10.1 创建存储过程
存储过程的写法有4种形式,无参、带入参、带出参、入参出参都有
1.没有参数的存储过程
create or replace procedure 存储过程名字
as
变量的声明;
begin
执行的sql语句;
end;
2.创建一个有输入参数的存储过程
create or replace procedure 存储过程名字(输入参数名字 in 数据类型)
as
变量的声明;
begin
执行的sql语句;
end;
3.有输出参数的存储过程
create or replace procedure 存储过程名字(参数名字 out 数据类型)
as
变量的声明;
begin
执行的sql语句;
end;
4.同时有输入和输出的存储过程
create or replace procedure 存储过程名字(参数名字 in 数据类型,参数名字 out 数据类型)
as
变量的声明;
begin
执行的sql语句;
end;
注:带有输出参数的存储过程,需要放入到代码块中进行调用
例:
CREATE OR REPLACE PROCEDURE pro_p4(
n1 IN NUMBER,
n2 IN NUMBER,
s OUT NUMBER
)
AS
BEGIN
s := n1 + n2;
END pro_p4;
/
DECLARE
s NUMBER;
BEGIN
pro_p4(1.5, 1.8, s);
DBMS_OUTPUT.PUT_LINE('计算结果: ' || s);
END;
/
10.2 调用存储过程
调用存储过程有两种方式:
1.call调用
CALL pro_name();
2.代码块调用
DECLARE
s NUMBER;
BEGIN
pro_p4(1.5, 1.8, s);
DBMS_OUTPUT.PUT_LINE('计算结果: ' || s);
END;
10.3 异常处理
在代码中捕获所有出现的异常和错误
create or replace procedure 过程名
as
--声明部分
begin
--执行部分
exception
when 异常的名字 then
对异常的处理
when 异常的名字 then
对异常的处理
when others then
对异常的处理
end;
others表示所有的错误。
异常有三类:系统预定义的、非预定义的、自定义的
- 系统预定义的异常:有名字有编号,大致有20来个吧。(我就不一一说明,百度吧)
- 非预定义的异常:有错误代码,但没有名字,这个名字,我们自己来取。
例:(给外键约束取名的验证)
CREATE OR REPLACE PROCEDURE p8(v_deptno IN NUMBER)
AS
-- 定义一个异常变量
fk_error EXCEPTION;
-- 将异常与Oracle错误代码绑定
PRAGMA EXCEPTION_INIT(fk_error, -2291);
BEGIN
-- 尝试更新部门编号
UPDATE emp SET deptno = v_deptno WHERE empno = 9999;
COMMIT;
EXCEPTION
WHEN fk_error THEN
DBMS_OUTPUT.PUT_LINE('违反外键约束!部门编号 ' || v_deptno || ' 不存在');
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('发生错误: ' || SQLCODE || ' - ' || SQLERRM);
END p8;
/
-- 调用存储过程(两种方式)
-- 方式1:使用CALL语句
CALL p8(40);
-- 方式2:使用匿名块(推荐)
BEGIN
p8(60);
END;
/
注: pragma exception_init:绑定错误名字和错误代码。
3.自定义异常:通过raise_application_error(错误编号, 错误提示),错误编号的范围是-20001到-20999
例:现在有人要加工资,但是规定是,每次加工资,不能超过原有工资的20%
CREATE OR REPLACE PROCEDURE p8(
v_empno IN NUMBER,
v_sal IN NUMBER
)
AS
s NUMBER;
m VARCHAR2(300);
BEGIN
-- 获取当前工资
SELECT sal INTO s FROM emp WHERE empno = v_empno;
-- 校验新工资是否超过原工资的20%
IF v_sal >= s * 1.2 THEN
RAISE_APPLICATION_ERROR(-20001, '加工资不能超过原有工资的20%'); -- 错误编号范围-20001到-20999
ELSE
-- 更新工资
UPDATE emp SET sal = v_sal WHERE empno = v_empno;
DBMS_OUTPUT.PUT_LINE(SQL%ROWCOUNT || ' 行数据被修改!');
COMMIT;
END IF;
EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE('员工编号 ' || v_empno || ' 不存在');
WHEN OTHERS THEN
m := SQLERRM;
DBMS_OUTPUT.PUT_LINE(SQLCODE || ': ' || m);
END p8;
/
-- 调用存储过程(推荐使用匿名块方式)
BEGIN
p8(7639, 10000); -- 示例调用1
p8(7369, 10000); -- 示例调用2
p8(7788, 3500); -- 示例调用3
END;
/
10.4 案例
需求:用存储过程保存错误日志
--1、新建一个错误日志表
CREATE TABLE error_log (
proce_name VARCHAR2(300), -- 存储过程名称
table_name VARCHAR2(300), -- 表名称
mcode VARCHAR2(100), -- 错误代码
merror VARCHAR2(300), -- 错误信息
mtime DATE -- 错误发生时间
);
--2、新建一个存储过程保存错误信息
CREATE OR REPLACE PROCEDURE insert_error_log(
v_proce_name IN VARCHAR2,
v_table_name IN VARCHAR2,
v_mcode IN VARCHAR2,
v_merror IN VARCHAR2
)
AS
BEGIN
-- 插入错误日志记录
INSERT INTO error_log (
proce_name,
table_name,
mcode,
merror,
mtime
) VALUES (
v_proce_name,
v_table_name,
v_mcode,
v_merror,
SYSDATE
);
-- 输出插入行数
DBMS_OUTPUT.PUT_LINE(SQL%ROWCOUNT || ' 行数据被插入!');
-- COMMIT;
END insert_error_log;
/
--3.在另一个过程里面,如果运行报错了,那么就调用存入错误日志的存储过程
CREATE OR REPLACE PROCEDURE p8(
v_empno IN NUMBER,
v_sal IN NUMBER
)
AS
s NUMBER;
m VARCHAR2(300);
BEGIN
-- 获取当前工资
SELECT sal INTO s FROM emp WHERE empno = v_empno;
-- 校验新工资是否超过原工资的20%
IF v_sal >= s * 1.2 THEN
RAISE_APPLICATION_ERROR(-20001, '加工资不能超过原有工资的20%'); -- 错误编号范围-20001到-20999
-- 调用错误日志存储过程
insert_error_log('p8', 'emp', SQLCODE, SQLERRM);
ELSE
-- 更新工资
UPDATE emp SET sal = v_sal WHERE empno = v_empno;
DBMS_OUTPUT.PUT_LINE(SQL%ROWCOUNT || ' 行数据被修改!');
END IF;
COMMIT;
EXCEPTION
WHEN OTHERS THEN
m := SQLERRM;
DBMS_OUTPUT.PUT_LINE('错误代码: ' || SQLCODE || ' 错误信息:' || m);
-- 调用错误日志存储过程
insert_error_log('p8', 'emp', SQLCODE, m);
-- 根据业务需求决定是否回滚
-- ROLLBACK;
END p8;
/
-- 通过匿名块调用存储过程
DECLARE
empno1 NUMBER := &输入员工编号;
sal1 NUMBER := &输入工资;
BEGIN
-- 调用存储过程
p8(empno1, sal1);
END;
/
-- 查询错误日志
SELECT * FROM error_log;
10.5 自定义函数
自定义函数和存储过程在用法没什么差异。主要差异有下:
- 存储过程中可以调用其他的存储过程以及一系列SQL语句。而函数有很多语句不能使用,比如DML语句。
- 存储过程是返回参数,函数是返回值,有return语句。
用法:
create or replace function 函数名(输入的参数 数据类型)
return 返回的数据类型
as
声明部分
begin
执行部分
return 返回值的变量
end;
例:
--实现一个和数据库power()相同的函数的功能,自己写一个求数字的次方的方法(简单版,暂不考虑基数为空,或者幂次为负的情况):
CREATE OR REPLACE FUNCTION cf(
n IN NUMBER, -- 基数
c IN NUMBER -- 幂次
)
RETURN NUMBER
AS
s NUMBER := 1; -- 初始化结果为1
BEGIN
-- 循环计算幂次
FOR i IN 1..c LOOP
s := s * n;
END LOOP;
-- 返回计算结果
RETURN s;
END cf;
/
--dql语句调用函数验证两个函数结果是否一致
SELECT
POWER(10, 3),
cf(10, 3),
POWER(4, 3),
cf(4, 3),
POWER(2, 10),
cf(2, 10)
FROM
dual;
11 触发器
触发器是一种数据库对象。在事先为某张表绑定一段代码,当表中的数据发生增、删、改的时候,系统会自动触发代码并执行。
作用:检查输入的数据;实时备份表格的数据;记录表格操作的日志。
注:触发器发生错误时,前面用户已经执行成功的操作会被撤销,类似事务的回滚。
触发器在业务上主要应用于数据库的备份和审计。
触发器的类型分为:前置触发器、后置触发器。
区别:
- 触发时间:前置触发器是在执行DML之前被激活;而后置触发器是在执行DML之后被激活。所以前置触发器可以在DML之前验证或修改数据;而后置触发器在DML之后对数据进行处理,常用于日志记录和数据统计。
- 功能上:前置触发器可以做增、删、改的操作;而后置触发器不能,只能做select操作。
11.1 用法
创建触发器:
CREATE OR REPLACE TRIGGER 触发器名字
BEFORE | AFTER INSERT OR UPDATE OR DELETE ON 表名
FOR EACH ROW
[WHEN (条件)]
DECLARE
-- 变量声明(可选)
BEGIN
-- 执行的SQL语句或PL/SQL代码
-- 可以使用:NEW和:OLD引用新旧值
END 触发器名字;
/
--前置触发器:before
--后置触发器:after
删除触发器:
drop trigger 触发器名称;
11.2 案例(前置触发器--验证数据)
需求:在emp表中如果要去更新用户的工资,新增的用户,工资不能超过2000元;老用户涨工资不能超过原来工资的10%;禁止删除岗位PRESIDENT。
CREATE OR REPLACE TRIGGER check_emp_sal
BEFORE INSERT OR UPDATE OR DELETE ON emp
FOR EACH ROW
BEGIN
IF INSERTING THEN
-- 新员工工资检查
IF :NEW.sal > 2000 THEN
RAISE_APPLICATION_ERROR(-20007, '新员工工资不能超过2000');
END IF;
ELSIF UPDATING THEN
-- 老员工涨薪检查
IF :NEW.sal > :OLD.sal * 1.1 THEN
RAISE_APPLICATION_ERROR(-20008, '老员工涨工资不能超过原来的10%');
END IF;
ELSE -- DELETING
-- 删除权限检查
IF :OLD.job = 'PRESIDENT' THEN
RAISE_APPLICATION_ERROR(-20006, '不能删除岗位是PRESIDENT的员工信息');
END IF;
END IF;
END check_emp_sal;
/
--这里报错:无法对sys拥有的对象创建触发器,换个普通用户,把emp表备份出来,再创建触发器
CREATE TABLE emp AS SELECT * FROM scott.emp;
-- 测试INSERT触发器(工资超过2000)
INSERT INTO emp VALUES(6666, 'ADDFD66', 'CLERK', NULL, DATE '2021-09-20', 2001, NULL, 40);
-- 测试INSERT触发器(工资合规)
INSERT INTO emp VALUES(6667, 'ADDFD67', 'CLERK', NULL, DATE '2021-09-20', 1500, NULL, 40);
-- 测试UPDATE触发器(涨幅超过10%)
UPDATE emp SET sal = 4000 WHERE empno = 7369;
-- 测试UPDATE触发器(合规调整)
UPDATE emp SET sal = 850 WHERE empno = 7369;
-- 测试DELETE触发器(删除普通员工)
DELETE FROM emp WHERE job = 'CLERK' AND sal < 2000;
-- 测试DELETE触发器(尝试删除PRESIDENT)
DELETE FROM emp WHERE job = 'PRESIDENT';
经测试,结果符合预期,满足需求。
注:
- sys的对象不能用来创建触发器,会报错。
- ":"有2种意思;一:给变量赋值, 如, names varchar2(10) :='aa'; 这是把"aa"赋值给变量names。二:表示引用,即引用表中字段所对应的值,如:emp表中name的值为“Bob”,那么 : old.name 的值就是"Bob"。表示引用的时候,只能出现在触发器里面。
- :NEW 和:OLD使用方法和意义,new 只出现在insert和update时,old只出现在update和delete时。在insert时new表示新插入的行数据,update时new表示要替换的新数据、old表示要被更改的原来的数据行,delete时old表示要被删除的数据。
11.3 案例(后置触发器--审计)
触发器的另一个常见用途是为了之后审计的目的而记录的对数据库的修改。
比如:当一个人增加或删除了某条记录的时候,我们可以把这个操作记录下来。这样就知道了谁进行了什么样的操作。
需求:对dept表进行增删改,建立记录其改动的操作的审计表。
--1.先创建审计表。
CREATE TABLE dept_audit (
user_name VARCHAR2(64), -- 操作用户名
action_type VARCHAR2(64), -- 操作类型(INSERT/UPDATE/DELETE)
action_date DATE, -- 操作时间
new_deptno NUMBER(2), -- 新部门编号
old_deptno NUMBER(2), -- 旧部门编号
new_dname VARCHAR2(16), -- 新部门名称
old_dname VARCHAR2(16), -- 旧部门名称
new_loc VARCHAR2(16), -- 新位置
old_loc VARCHAR2(16) -- 旧位置
);
--2.创建触发器:
CREATE OR REPLACE TRIGGER after_dept
AFTER INSERT OR UPDATE OR DELETE ON dept
FOR EACH ROW
BEGIN
IF INSERTING THEN
-- dept表插入数据时
INSERT INTO dept_audit (
user_name,
action_type,
action_date,
new_deptno,
old_deptno,
new_dname,
old_dname,
new_loc,
old_loc
) VALUES (
USER,
'insert',
SYSDATE,
:NEW.deptno,
NULL,
:NEW.dname,
NULL,
:NEW.loc,
NULL
);
ELSIF UPDATING THEN
-- dept表更新数据时
INSERT INTO dept_audit (
user_name,
action_type,
action_date,
new_deptno,
old_deptno,
new_dname,
old_dname,
new_loc,
old_loc
) VALUES (
USER,
'update',
SYSDATE,
:NEW.deptno,
:OLD.deptno,
:NEW.dname,
:OLD.dname,
:NEW.loc,
:OLD.loc
);
ELSE
-- dept表删除数据时
INSERT INTO dept_audit (
user_name,
action_type,
action_date,
new_deptno,
old_deptno,
new_dname,
old_dname,
new_loc,
old_loc
) VALUES (
USER,
'delete',
SYSDATE,
NULL,
:OLD.deptno,
NULL,
:OLD.dname,
NULL,
:OLD.loc
);
END IF;
END after_dept;
/
--3.验证触发器的结果
INSERT INTO dept VALUES(60, 'java', 'chongqing');
UPDATE dept SET dname = 'python' WHERE deptno = 32;
DELETE FROM dept WHERE deptno = 60;
SELECT * FROM dept_audit;
结果显示:
这里如果换个用户A登陆来对YANGFENG用户下的dept表的数据进行修改,那么user_name的数据也会变成用户A。
12 分区表
什么时候用到分区表?
当表的数据量不断增大,查询数据的速度就会变慢,性能就会下降,这时就应该考虑对表进行分区。表进行分区后,逻辑上表仍然是一张完整的表,只是将表中的数据在物理上存放到多个表空间(物理文件上),这样查询数据时,不至于每次都扫描整张表。
分区表的划分方式有4种:范围分区、hash分区、列表分区、组合分区。
12.1 范围分区
关键字:range
最常用的一种分区,范围分区将数据基于范围映射到每一个分区,这个范围是你在创建分区时指定的分区键决定的。
常用于范围分区的字段:数值范围类(比如工资区间)、时间范围类(比如一月一月的来)。
例:
--范围分区(用sal列进行范围划分)
CREATE TABLE amp (
empno NUMBER(4),
ename VARCHAR2(10),
job VARCHAR2(9),
mgr NUMBER(4),
hiredate DATE,
sal NUMBER(7,2),
comm NUMBER(7,2),
deptno NUMBER(2)
)
PARTITION BY RANGE (sal) (
PARTITION sal_0_1000 VALUES LESS THAN (1001),
PARTITION sal_1001_2000 VALUES LESS THAN (2001),
PARTITION sal_2001_3000 VALUES LESS THAN (3001),
PARTITION sal_3001_maxv VALUES LESS THAN (MAXVALUE)
);
--录入数据检验
INSERT INTO amp
SELECT * FROM emp;
SELECT * FROM amp PARTITION(sal_0_1000);
12.2 hash分区
关键字:hash
Hash分区常用于划分没有啥规律的字段。通过哈希函数均匀分布数据。
--hash分区(划分没有规律的字段),本例子划分4个区
CREATE TABLE bmp (
empno NUMBER(4),
ename VARCHAR2(10),
job VARCHAR2(9),
mgr NUMBER(4),
hiredate DATE,
sal NUMBER(7,2),
comm NUMBER(7,2),
deptno NUMBER(2)
)
PARTITION BY HASH (ename) (
PARTITION p1,
PARTITION p2,
PARTITION p3,
PARTITION p4
);
--录入数据检验,查看分区1的数据
INSERT INTO bmp SELECT * FROM emp;
SELECT * FROM bmp PARTITION(p1);
12.3 列表分区
关键字:list
该分区的特点是某列的值只有几个,并且存在一样的规律,基于这样的特点我们可以采用列表分区。
比如说性别字段,就可以分为男女两个分区。
CREATE TABLE cmp (
EMPNO NUMBER(4),
ENAME VARCHAR2(10),
JOB VARCHAR2(9),
MGR NUMBER(4),
HIREDATE DATE,
SAL NUMBER(7, 2),
COMM NUMBER(7, 2),
DEPTNO NUMBER(2)
) PARTITION BY LIST (DEPTNO) (
PARTITION dept10 VALUES (10),
PARTITION dept20 VALUES (20),
PARTITION dept30 VALUES (30),
PARTITION dept40 VALUES (40)
);
--录入数据检验
INSERT INTO cmp SELECT * FROM emp;
SELECT * FROM cmp PARTITION (dept10);
12.4 组合分区
组合分区就是将范围分区、hash分区、列表分区组合起来进行分区。
写法:
create table 表名(
列名 数据类型
)partition by 父分区类型(列名)
subpartition by 子分区类型(列名)
(
partition 父分区名字 values 分区规则(
subpartition 子分区名字 values 分区规则,
subpartition 子分区名字 values 分区规则,
...
)
);
例:--组合分区(以列表——范围为例子)
CREATE TABLE dmp (
EMPNO NUMBER(4),
ENAME VARCHAR2(10),
JOB VARCHAR2(9),
MGR NUMBER(4),
HIREDATE DATE,
SAL NUMBER(7, 2),
COMM NUMBER(7, 2),
DEPTNO NUMBER(2)
) PARTITION BY LIST (DEPTNO)
SUBPARTITION BY RANGE (SAL) (
PARTITION D10 VALUES (10) (
SUBPARTITION SAL_10_1000 VALUES LESS THAN (1001),
SUBPARTITION SAL_10_2000 VALUES LESS THAN (2001),
SUBPARTITION SAL_10_MAXV VALUES LESS THAN (MAXVALUE)
),
PARTITION D20 VALUES (20) (
SUBPARTITION SAL_20_1000 VALUES LESS THAN (1001),
SUBPARTITION SAL_20_2000 VALUES LESS THAN (2001),
SUBPARTITION SAL_20_MAXV VALUES LESS THAN (MAXVALUE)
),
PARTITION D30 VALUES (30) (
SUBPARTITION SAL_30_1000 VALUES LESS THAN (1001),
SUBPARTITION SAL_30_2000 VALUES LESS THAN (2001),
SUBPARTITION SAL_30_MAXV VALUES LESS THAN (MAXVALUE)
),
PARTITION D40 VALUES (40) (
SUBPARTITION SAL_40_1000 VALUES LESS THAN (1001),
SUBPARTITION SAL_40_2000 VALUES LESS THAN (2001),
SUBPARTITION SAL_40_MAXV VALUES LESS THAN (MAXVALUE)
)
);
--录入数据检验
INSERT INTO dmp SELECT * FROM emp;
--共4个主分区,12个子分区
SELECT * FROM cmp PARTITION(d10);
12.5 分区中分析常用语句
--增加分区
alter table 表名 modify partition 父分区名 add subpartition 子分区名;
--删除分区
alter table 表名 drop subpartition 子分区名;
select * from user_tables; --查看当前用户所有的表的信息
select * from user_tab_columns;--查看当前用户所有表的列信息
select * from user_tab_partitions; --查看用户所有的分区表信息
--主分区数据字典表
select * from user_tab_partitions;
--主分区数据字典表
SELECT * FROM user_tab_partitions WHERE TABLE_NAME='EMP_RANGE';
--子分区数据字典表
select * from user_tab_subpartitions;
--清空一个表的数据是
truncate table table_name;
--清空分区表一个分区的数据是
alter table table_name truncate partition p2;
12.6 分区中常见报错
1.分区’P11’的分区界限过高。
原因分析:出现这个情况是新建表分区时P11的下一个分区出现了问题。
一般是分区的数值与其他分区重合,造成了分区界限过高的报错。
处理办法:重新修改子分区的数值界限,值域不能交叉。容易出现在以时间范围分区中。
2.插入分区的关键字未映射到任何分区。
原因分析:出现这个报错是在录入数据时出现的,插入的数据未与分区对应到,造成一些数据无法录入。
举例这个分区是分了3个分区:10部门,20部门,30 部门。在插入的数据中还有40部门的数据,所以造成了这个报错。
解决办法:加入一个相应的子分区。
注:在进行分区操作之前,先备份数据,如果一旦有错误的分区操作可能会导致数据丢失。
12.7 分区表案例
对大型日志表按时间日期进行分区。
CREATE TABLE app_logs (
log_id NUMBER GENERATED ALWAYS AS IDENTITY, --自动生成唯一的标识列
log_time TIMESTAMP,
app_module VARCHAR2(50), --
log_level VARCHAR2(10),
message VARCHAR2(4000)
)
PARTITION BY RANGE (log_time) (
PARTITION logs_202301 VALUES LESS THAN (TO_DATE('01-FEB-2023', 'DD-MON-YYYY')),
PARTITION logs_202302 VALUES LESS THAN (TO_DATE('01-MAR-2023', 'DD-MON-YYYY')),
PARTITION logs_202303 VALUES LESS THAN (TO_DATE('01-APR-2023', 'DD-MON-YYYY')),
PARTITION logs_max VALUES LESS THAN (MAXVALUE)
)
TABLESPACE users;
-- 创建本地索引
CREATE INDEX idx_app_logs_time ON app_logs(log_time) LOCAL;
-- 创建每月维护任务,自动添加下月分区
BEGIN
DBMS_SCHEDULER.CREATE_JOB (
--任务名称
job_name => 'ADD_LOG_PARTITION',
--任务类型
job_type => 'PLSQL_BLOCK',
--任务动作
job_action => 'BEGIN
EXECUTE IMMEDIATE ''ALTER TABLE app_logs ADD PARTITION logs_''||
TO_CHAR(ADD_MONTHS(SYSDATE,1),''YYYYMM'')||
'' VALUES LESS THAN (TO_DATE(''''01-''||TO_CHAR(ADD_MONTHS(SYSDATE,2),''MON-YYYY'')||'''', ''DD-MON-YYYY'') )'';
END;',
--开始时间
start_date => SYSTIMESTAMP,
--重复间隔
repeat_interval => 'FREQ=MONTHLY; BYMONTHDAY=28',
--启用状态
enabled => TRUE,
--注释
comments => '每月添加下月日志分区');
END;
/
13 Oracle优化
本章节是Oracle学习的升华,通过Oracle各种优化的方式,来提高数据库的性能和效率(响应时间、吞吐量、负载等)。
主要从以下几个方向来进行优化:
13.1 数据库的设计优化
1、合理设计数据表结构。
合理选择建模方法(范式建模、维度建模)、字段、数据类型、约束条件、表空间大小。
(1)索引优化。
创建合适的索引,避免全表扫描,加快查询速度。要注意索引数量不能过多。
在使用索引的时候,也要尽量避免产生回表,尽管这很多时候很难避免。回表:使用普通索引的时候,如果索引里不包含全部要查找的字段,则需要回到表中查找需要的字段,这个过程也叫做回表。
(2)分区表优化。
通过合理分区减少查询的数据量,加快查询速度。
(3)计算方式优化。
通过分布式、并行计算,加快查询速度。
13.2 SQL调优
1、尽量少用子查询。子查询的结果集会占用内存,影响查询效率。
2、使用表别名。简化SQL语句,就减少了SQL解析,加快了查询速度。
3、减少以下效率低关键字的使用。
(1)union并集去重、minus差集、intersect交集的集合运算,这些关键字效率很差;
(2)用exists代替in、distinct关键字;
(3)or的效率比较低,可以使用union all去进行替换;
(4)模糊查询like;
4、使用临时表。简化SQL语句,就减少了SQL解析,加快了查询速度。
5、尽量不用排序order by。排序是相当耗费资源的。
6、避免使用select *,会导致全表扫描,造成不必要的资源开销且增加了查询时间。所以尽量只查需要的字段。
7、有大量DML语句时多使用commit,可以释放缓存,同时释放锁资源。
8、表连接的调优。
表的内部逻辑有三种不同的算法连接方式:
(1)hash join(哈希连接)
定义:等值关联,性能好,但占用内存较大。它是将被驱动表和其计算的hash值表一起放入缓存,然后在驱动表中逐条取数据匹配相应hash值,再精确匹配数据。所以,适用于大型数据集或大规模连接。
(2)nested loops(嵌套循环连接)
定义:内存占用小,但性能好。它是依次从驱动表中取一条数据,遍历被驱动表,将匹配的数据放入缓存中。所以适用于两表的数据差异大得情况。
(3)merge join(排序归并)
定义:不等关联,将关联的a表跟b表分别进行排序,生成临时的两张表后,随机取一张表逐条抽取记录与另一张表匹配。
以上的3中连接方式Oracle的优化器会评估查询的条件、表大小、索引使用等因素,并选择最适合的表连接算法来执行查询。当然也可以采用hints优化器强制改变连接方式:
改变表连接的逻辑:
/*+use_hash(a b) */ 强制使用哈希连接
/*+use_merge(a b)*/ 强制使用排序归并
/*+use_nl(a b) */ 强制使用嵌套连接
/*+leading(a b) */ a一定要是小表(驱动表),b一定是大表(匹配表)
/*+ full(a) */ 强制全表扫描
/*+ index(表名 索引名) */ 强制使用索引
/*+ parallel(8) */ 强制使用并行的资源,来执行这个sql语句
13.3 参数配置优化
合理调整数据库的参数配置,如SGA和PGA的大小调整、日志和缓存的配置、并发连接数的设置等。主要参数有:SGA_TARGET(SGA)、DB_BLOCK_SIZE(块)、UNDO_RETENTION、SORT_AREA_SIZE(排序时内存)、LOG_BUFFER(日志缓存)。
13.4 硬件优化
一般是增加内存、磁盘。还可以采用集群
13.5 优化常用工具
SQL调优时常用的几个监控和诊断工具:
1、执行计划。
可以通过PLSQL或终端查看执行计划SQL语句执行顺序、CPU耗费、IO耗费等。(PLSQL中在编译计划窗口打开SQL即可查看)
2、 SQL Trace。
可以跟踪SQL语句的执行情况,包括执行时间、IO操作、锁等信息。
打开方法:
用管理员用户打开session的trace:
ALTER SESSION SET SQL_TRACE = TRUE;
查看trace文件的存储位置:
SELECT value FROM V$DIAG_INFO WHERE name= 'Diag Trace';
在该目录下打开文件即可。
3、AWR报告。
通过AWR报告查看数据库的性能指标资源使用情况,比如CPU使用率、内存使用率、IO等待时间等;以及top10的SQL分析。
打开方法:
1.终端下登陆dba用户:(验证是否连接)
sqlplus / as sysdba
2.然后输入下面这句:
@?/rdbms/admin/awrrpt.sql
3.输入 report_type 的值:
html或text
4.输入 num_days 的值:(1就是今天,2就是昨天和今天,依次排列)
1
5.输入 begin_snap 的值:(开始的snap id)
按照实际输入
6.输入 end_snap 的值:
按照实际输入
7.输入 report_name 的值:
文件名.html
8.然后去相应的目录找到该文件用网页打开即可
具体更多的指标解读分析请查询相关的知识。推荐:
https://blog.csdn.net/hezuijiudexiaobai/article/details/120472266
13.6 优化思路
对于Oracle已存在的SQL语句运行很慢的问题的处理:
- 先通过执行计划或AWR报告获取该语句影响运行存在的问题,制定合适的方案。
- 数据非常大,那么建分区,只查询数据所在的分区表,避免整张大表的查询。
- 查看有没有导致索引失效的语句。
- 给需查询的字段创建索引。
- 查找SQL语句中有无低效率的关键字。
- SQL语句频繁的对硬盘进行了读写,可以把语句写成代码块。
- 使用并行或分布式计算。
- 有内存的原因则增加内存。
14 数据仓库
数据仓库是一个面向主题的、集成的、相对稳定的、反映历史变化的数据集合,用于支持管理决策。数据仓库从多个源系统中提取、转换和加载数据(ETL流程),通过数据清洗、整合、转换等生成目标的过程。
按照数据的流向,可以将数据仓库分为3层(常见),也可以分为更多层(根据实际情况分层):
- 贴源层(ODS):数据来源于现有业务库,与源表的数据结构保持一致,一般不做改动,为其他逻辑层提供数据来源。
- 数据仓库层(DW):数据来源于ODS层,对ODS的数据进行规范化(编码转换、清洗、统一格式、脱敏等),再进行各表数据的关联整合,输出主题宽表。
- 集市层(DM):数据来源可以是ODS层,也可以是DW层,主要是面向业务需求进行开发。数据应用于前端报表展示或输出到项目库中。
总体来说,数仓像是一个逻辑性的概念,是为了帮助开发更好去管理数据,为业务提供数据支撑而延伸的概念过程。
14.1 数据建模
数据建模就是基于对业务的理解,将各种数据进行整合关联。在数据仓库DW和DM逻辑层需要分析数据,通常我们的中间层宽表就基于维度建模-星型模型来实现的。
数据建模方式有两类:范式建模、维度建模。
14.1.1 范式建模
范式建模的主要作用是减少数据冗余提高更新数据的效率,同时保证数据完整性,但是这样存在一个问题,划分的表会很多,表连接的查询越多就会影响性能。
范式建模主要有3种常用:第一范式、第二范式、第三范式。
1.第一范式
表中的每一列都是不可拆分的原子项,只能存在一个值。(属性不可再分)
2.第二范式
第二范式要同时满足下面两个条件:
(1)满足第一范式。
(2)没有部分依赖。(表中的非主键列都必须依赖于主键列)
3.第三范式
第三范式要同时满足下面两个条件:
(1)满足第二范式。
(2)没有传递依赖。(表中非主键列关系重复,能互相推导出来)
比如说:emp表再加一个部门名称的字段,那么:
部门编号依赖于员工编号,部门名称依赖于部门编号,部门名称间接依赖于员工编号,则产生了传递依赖。
在表的设计上采用ER模型(实体关系图):
1.一对一关系
外键列设置在任意一张表中,都是可以的。
2.一对多关系
外键列要设置在多的一方。
3.多对多关系
假设是A表和B表,这种情况下,需要设计第三张表(桥表),桥表中设置俩个外键,分别引用A表的主键和B表的主键。
14.1.2 维度建模
维度建模是通过维度和指标来进行设计,它是面向分析的,目的是提高查询性能,快速完成需求分析且对于复杂查询及时响应。相应的缺点就是会造成数据冗余,可能会违反范式要求。
维度建模常用的有3种:星型模型、雪花模型、星座模型。
(1)星型模型
星形模型中有一张事实表和多个维度表,事实表与维度表通过主键外键相关联,维度表之间没有关联。当所有维表都直接连接到事实表上时,整个图就像星星一样,故将该模型称为星型模型。
(2)雪花模型
当有一个或多个维度表没有直接连接到事实表上,而是通过其他维表连接到事实表上时,其图解就像多个雪花连接在一起,故称雪花模型。
(3)星座模型
星座模型是由星型模型延伸而来,星型模型是基于一张事实表而星座模式是基于多张事实表,并且共享维度表信息,这种模型往往应用于数据关系比星型模型和雪花模型更复杂的场合。星座模型需要多个事实表共享维度表,因而可以视为星形模型的集合。
14.1.3 建模工具
常用的有Navicat、PowerDesigner
14.2 常见表类型
14.2.1 拉链表
一些表的数据不是静态的,而是会随着时间而缓慢地变化,这种随着时间发生变化的维度称之为缓慢变化维。例如用户修改了自己的住址、商品的描述信息更改等。
有时候的某些需求需要查看或统计某一个时间点或者时间段的历史快照信息。这个时候就需要拉链表。
拉链表定义:记录数据在某一时间内的状态以及数据在某一时点上的变化的数据存储方式。
算法:
新增数据 ==> 开链
删除数据 ==> 关链
修改数据 ==> 先关链,在开新的拉链
start_time
表示该条记录的生命周期开始时间——周期快照时的状态
end_time
该条记录的生命周期结束时间
end_time= ‘9999-12-31’ 表示该条记录目前处于有效状态
以emp表为原表举例:(阐述拉链表的过程,分为原表的更新和新增)
原表为更新时:
第一步:先以emp表为原表将结构及数据批量导入到新创建的拉链表empb中,同时在拉链表中新建两个字段,起始创建时间和结束时间。
create table empb as select emp.*,date'2023-03-21' starttime,date'9999-12-31' endtime from emp;
第二步:对原表某个值进行更新,这里选的是姓名为king的工资为5500。
update emp set sal=5500 where lower(ename)='king';
第三步:对拉链表进行更新,只更新结束时间,(结束时间与该条数据的下条新增的起始创建时间一致。因为原数据只有先结束标记为失效状态,再新增下条数据,标记为有效状态),然后再将原表emp的更新数据插入到拉链表empb中。
update empb set endtime=date'2023-03-22' where lower(ename)='king';
insert into empb select emp.*,date'2023-03-22',date'9999-12-31' from emp where lower(ename)='king';
原表为插入数据时:
第一步:与更新数据一致。
第二步:对原表插入数据。
第三步:不用对拉链表的日期更新,直接根据原表的数据插入即可(只需要第三步中的第二步)。
拉链表能反应出某个时间的所有信息的有效情况,相当于快照表。比如说:
已知拉链表中:用户A002有两条数据,分别是:
用户 起始时间 结束时间 状态
A002 2016-01-01 2017-01-03 失效
A002 2017-01-03 9999-12-31 有效
要求查询出在2016-06-01的用户情况:
select * from empb where start_time <= date‘2016-06-01’ and end_time >= date‘2016-06-01’;
此SQL语句会查询出这天所有用户的有效信息。
14.2.2 全量表
全量表就是记录所有的数据,一般使用它时都会清空目标表。用于数据量不大的表。
14.2.3 增量表
增量表只记录更新周期内的新增数据,就是基于原表,记录每次变化的数据。
特殊增量表:da表,每天的分区就是当天的数据,其数据特点就是数据产生后就不会发生变化,如日志表
14.2.4 流水表
对于表的每一个修改都会记录,可以用于反映实际记录的变更。
与拉链表的区别:流水表是只新增,不对原来的结束时间做出更新,且流水表只有创建时间,没有结束时间。流水表就是字面意思,直接罗列出来即可,无论原表是新增还是更新,流水表都是新增。
更多推荐
所有评论(0)