第一课:客户端
1. Sql
Plus(客户端),命令行直接输入:sqlplus,然后按提示输入用户名,密码。
2. 从开始程序运行:sqlplus,是图形版的sqlplus.
3. http://localhost:5560/isqlplus
Oracle Directory
Manager:管理目录服务
OLE:Microsoft自己应用程序开发的时候订立的一套标准,用VC和VB开发时需要了解它
SQL*plus:Oracle的一个客户端
Oracle客户端:toad、pl/sql
Developer
Oracle管理时toad用的很多
isql*plus不需要在客户端装任何东西,只需要一个浏览器就够了
第二课:更改用户
sql语言(Structured query
language,结构化查询语言)是在数据库底下进行操作的专门的语言,是第四代语言,本身是一种标准语言,是一个国际标准;到目前为止有两套标准,第一套标准是1992年订立的,叫做sql1992,第二套标准叫做sql1999,1999年订立的新的标准,目前大多数的数据库系统既支持旧的也支持新的,很多的系统仍然是使用旧的sql语言来写程序的。
在大多数的数据库系统上,sql语言都通用;只不过每一种不同的数据库,对于这种sql语言都有一点轻微的改变
1. sqlplus sys/bjsxt as sysdba
sqlplus
用户名/密码 as 角色
2. alter user scott account unlock;(解锁)
第三课:table structure
了解用户下面表的结构是什么样的:
1. 描述某一张表:desc 表名
2. select * from 表名
第四课:select 语句:
1.计算数据可以用空表:比如:.select 2*3 from dual
查询当前日期:select sysdate from
dual;
2.select ename,sal*12 annual_sal from emp;与select
ename,sal*12 "annual sal" from emp;区别,加双引号保持原大小写。不加全变大写。
别名里有特殊字符,如空格、中文,需加双引号
任何含有空值的数学表达式,最后的结果都是空值
3. select ename || "abcd"
如果连接字符串中含有单引号,用两个单引号代替一个单引号。
第五课:distinct
消掉重复值
select deptno from emp;
select distinct deptno from emp;
select distinct deptno from emp;
select distinct deptno ,job from emp
去掉deptno,job两者组合的重复。更多的项,就是这么多项的组合的不重复组合。
第六课:Where
select * from emp where deptno =10;
select * from emp where deptno
<>10;不等于10
select * from emp where ename ='bike';
select ename,sal from emp where sal between 800
and 1500 (>=800 and <=1500)
空值处理:
select ename,sal,comm from emp where comm is
(not) null;
select ename,sal,comm from emp where ename (
not)in ('smith','king','abc');
变量>'日期',日期格式不知道可以用sysdate查询
select ename from emp where ename like
'_A%';_代表一个字母,%代表0个或多个字母. 如果查询%
可用转义字符.\%. 还可以用escape '$'比如:select ename from
emp where ename like '%$a%' escape '$';
第七课: orderby
插入数据的顺序查询出来就是什么顺序
select * from dept;
select * from dept order by
dept desc;(默认:asc)
select ename,sal,deptno from
emp order by deptno asc,ename desc;
第八课: sql function1:
select ename,sal*12 annual_sal from emp
where ename not like '_A%' and sal>800
order by sal desc;
select lower(ename) from emp;
转换成小写
upper:全部转换成大写
select ename from emp
where lower(ename) like '_a%';等同于
select ename from emp where ename like '_a%' or
ename like '_A%';
select substr(ename,2,3) from
emp;从第二字符截,一共截三个字符.
select chr(65) from dual 结果为:A
select ascii('a') from dual 结果为:65
select round(23.652,1) from dual; 结果为:
23.7
select round(23.652,-1) from dual; 20
to_char:把数字或者是日期转换成为某种特定的格式
to_char(纯数字或者纯日期,格式)
select to_char(sal,'$99,999.9999') from emp;
9代表的是一位数字,这一位上没有数字不显示,小数点后面的强制显示;如果把9改成0,这一位上没有数字则显示0
select to_char(sal,'$99_999_999') from
emp;
select to_char(sal,'L99_999_999') from
emp;人民币符号,L:代表本地符号
这个需要掌握牢:
select birthdate from emp;
显示为:
BIRTHDATE
----------------
17-12月-80
----------------
改为:
select to_char(birthdate,'YYYY-MM-DD HH:MI:SS')
from emp;
显示:
BIRTHDATE
-------------------
1980-12-17 12:00:00
-------------------
select to_char(sysdate,'YYYY-MM-DD HH24:MI:SS')
from dual; //也可以改为:HH12
TO_CHAR(SYSDATE,'YY
-------------------
2007-02-25 14:46:14
改成HH12或者HH直接显示2007-02-25 02:46:01
to_date函数:
select ename,birthdate from emp where birthdate
> to_date('1981-2-20 12:34:56','YYYY-MM-DD HH24:MI:SS');
如果直接写 birthdate>'1981-2-20
12:34:56'会出现格式不匹配,因为表中的格式为: DD-MM月-YY.
select sal from emp where sal>888.88
无错.但
select sal from emp where
sal>$1,250,00;
会出现无效字符错误.
改为:
select sal from emp where
sal>to_number('$1.250.00','$9,999.99');
把空值改为0
select ename,sal*12+nvl(comm,0) from emp;
如果comm里面的值是空值,用0替代它;如果不是空值,就是comm的值
这样可以防止comm为空时,sal*12相加也为空的情况.
第九课: Group function 组函数
把好多条记录作为输入,最后产生一个输出
max,min,avg ,count,sum函数
count:求表里面有多少条记录
select to_char(avg(sal),'99999999,99') from
emp;
select round(avg(sal),2) from emp;
结果:2073.21
select count(*) from emp where deptno=10;
select count(ename) from emp where deptno=10;
count某个字段,如果这个字段不为空就算一个.
select count(distinct deptno) from emp;
select sum(sal) from emp;
第十课: Group by语句
需求:现在想求,求每个部门的平均薪水.
select avg(sal) from emp group by deptno;
select deptno avg(sal) from emp group by
deptno;
select deptno,job,max(sal) from emp group by
deptno,job;
求薪水值最高的人的名字.
select ename,max(sal) from
emp;出错,因为max只有一个值,但等于max值的人可能好几个,不能匹配.
应如下求:
select ename from emp where sal=(select max(sal)
from emp);
Group by语句应注意,
出现在select中的字段,如果没出现在组函数中,必须出现在Group by语句中.
select deptno,max(sal) from emp group by
deptno;
在使用group
by时,出现在select列表中的字段,如果没有出现在组函数里,必须出现在group by子句里
第十一课: Having 对分组结果筛选
Where是对单条纪录进行筛选,Having是对分组结果进行筛选.
select avg(sal),deptno from emp
group by deptno
having avg(sal)>2000;
查询工资大于1200雇员,按部门编号进行分组,分组后平均薪水大于1500,按工薪倒充排列.
select * from emp
where sal>1200
group by deptno
having avg(sal)>1500
order by avg(sal) desc;
第十二课:字查询
谁挣的钱最多(谁:这个人的名字, 钱最多)
select 语句中嵌套select 语句,可以在where,from后.
问那些人工资,在平均工资之上.
select ename,sal from emp where sal>(select
avg(sal) from emp);
查找每个部门挣钱最多的那个人的名字.
select ename ,deptno from emp where sal in(select
max(sal) from ename group by deptno) 查询会多值.
应该如下:
select max(sal),deptno from emp group by
deptno;当成一个表.语句如下:
select ename, sal from emp join(select max(sal)
max_sal,deptno from emp group
by deptno) t on(emp.sal=t.max_sal and
emp.deptno=t.deptno);
每个部门的平均薪水的等级.
分析:首先求平均薪水(当成表),把平均薪水和另外一张表连接.
第十四课:self_table_connection
把某个人的名字以及他的经理人的名字求出来(经理人及这个人在表中同处一行)
分析:首先求出这个人的名字,取他的编号,然后从另一张表与其相对应编号,然后找到经理的名字.
select e1.ename ,e2.ename from emp e1,emp e2
where e1.mgr= e2.empno.
empno编号和MGR都是编号.
第十五课: SQL1999_table_connections
select ename,dname,grade from emp e,dept d, sqlgrade s
where
e.deptno = d.deptno and e.sql between s.losal and s.hisal and
job<>'CLERK';
有没有办法把过滤条件和连接条件分开来? 出于这样考虑,Sql1999标准推出来了.有许多人用的还是
旧的语法,所以得看懂这种语句.
select ename,dname from emp,dept;(旧标准).
select ename,dname from emp cross join dept;(1999标准)
select ename,dname from emp,dept where emp.deptno=dept.deptno
(旧)
select ename,dname from emp join dept on(emp.deptno = dept.deptno);
1999标准.没有Where语句.
select ename,dname from emp join dept
using(deptno);等同上句,但不推荐使用.
select ename,grade from emp e join salgrade s on(e.sal between
s.losal and s.hisal);
join
连接语句, on过滤条件。连接,条件一眼分开。如果用Where语句较长时,连接语句和过滤语句混在一起。
三张表连接:
slect
ename,dname, grade from
emp e
join dept d on(e.deptno=d.deptno)
join
salgrade s on(e.sal between s.losal and s.hisal)
where
ename not like '_A%';
把每张表连接 条件不混在一起,然后数据过滤条件全部区分开来。读起来更清晰,更容易懂一点。
select e1.ename,e2.ename from emp e1 join emp e2 on(e1.mgr =
e2.emptno);
左外连接:会把左边这张表多余数据显示出来。
select e1.ename,e2,ename from emp e1 left join emp e2 on(e1.mgr
=e2.empno);left 后可加outer
右外连接:
select ename,dname from
emp e right outer join dept d on(e.deptno =d.deptno);
outer可以取掉。
即把左边多余数据,也把右边多余数据拿出来,全外连接。
select ename,dname from emp e full join dept d on(e.deptno
=d.deptno);
16-23 课:求部门平均薪水的等级
A.求部门平均薪水的等级。
select deptno,avg_sal,grade from
(select deptno,avg(sal) avg_sal from emp group by
deptno)t
join salgrade s on(t.avg_sal between s.losal and
s.hisal)
B.求部门平均的薪水等级
select deptno,avg(grade) from
(select deptno,ename, grade from emp
join salgrade s on(emp.sal between s.losal and
s.hisal)) t
group by deptno
C.那些人是经理
select ename from emp where empno in(select mgr
from emp);
select ename from emp where empno in(select
distinct mgr from emp);
D.不准用组函数,求薪水的最高值(面试题)
select distinct sal from emp where sal not
in(
select distinct e1.sal from emp e1 join emp e2 on
(e1.sal
E.平均薪水最高的部门编号
select deptno,avg_sal from
(select avg(sal)avg_sal,deptno from emp group by
deptno)
where avg_sal=
(select max(avg_sal)from
(select avg(sal) avg_sal,deptno from emp group by
deptno)
)
F.平均薪水最高的部门名称
select dname from dept where deptno=
(
select deptno from
(select avg(sal)avg_sal,deptno from emp group by
deptno)
where avg_sal=
(select max(avg_sal)from
(select avg(sal) avg_sal,deptno from emp group
by deptno)
)
)
G.求平均薪水的等级最低的部门的部门名称
组函数嵌套
如:平均薪水最高的部门编号,可以E.更简单的方法如下:
select deptno,avg_sal from
(select avg(sal) avg_sal,deptno from emp group
by deptno)
where avg_sal =
(select max(avg(sal)) from emp group by
deptno)
组函数最多嵌套两层
分析:
首先求
1.平均薪水: select avg(sal) from group by
deptno;
2.平均薪水等级: 把平均薪水当做一张表,需要和另外一张表连接salgrade
select deptno,grade avg_sal from
( select deptno,avg(sal)
avg_sal from emp group by deptno) t
join salgrade s on(t.avg_sal between s.losal and
s.hisal)
上面结果又可当成一张表。
DEPTNO GRADE AVG_SAL
-------- ------- ----------
30 3 1566.66667
20 4 2175
10 4 2916.66667
3.求上表平均等级最低值
select min(grade) from
(
select deptno,grade,avg_sal
from
(select
deptno,avg(sal) avg_sal from emp group by deptno)t
join salgrade s on(t.avg_sal
between s.losal and s.hisa)
)
4.把最低值对应的2结果的那张表的对应那张表的deptno,
然后把2对应的表和另外一张表做连接。
select dname
,t1.deptno,grade,avg_sal from
(
select deptno,grade,avg_sal
from
(select deptno,avg(sal) avg_sal from emp group
by deptno)t
join salgrade s on(t.avg_sal between s.losal and
s.hisal)
) t1
join dept
on (t1.deptno = dept.deptno)
where
t1.grade =
(
select deptno,grade,avg_sal from
(select deptno,avg(sal)
avg_sal from emp group by deptno) t
join salgrade s on(t.avg_sal
between s.losal and s.hisal)
)
)
结果如下:
DNAME DEPTNO GRADE AVG_SAL
-------- ------- -------- --------
SALES 30 3 1566.6667
H: 视图(视图就是一张表,一个字查询)
视图名通常以v$_开头
G中语句有重复,可以用视图来简化。
conn sys/bjsxt as sysdba;
grant create table,create view to scott;
conn scott/tiger
创建视图:
create view v$_dept_avg-sal_info as
select deptno,grade,avg_sal from
( select deptno,avg(sal) avg_sal from emp group
by deptno)t
join salgrade s on 9t.avg_sal between s.losal and
s.hisal)
然后
select * from v$_dept_avg-sal_info
结果如下:
DEPTNO GRADE AVG_SAL
-------- ------- ----------
30 3 1566.66667
20 4 2175
10 4 2916.66667
然后G中查询可以简化成:
select dname,t1.deptno,grade,avg_sal from
v$_dept_avg-sal_info t1
join dept on9t1.deptno =dept.deptno)
where t1.grade=
(
select min(grade) from v$_dept_avg-sal_info
t1
)
第二十四课
求比普通员工最高薪水还要高的经理人的名称
先求普通员工的最高薪水
select max(sal) from emp
where empno not in
(select distinct mgr from emp where mgr is not
null);
select ename from emp
where empno in
(select distinct mgr from emp where mgr is not
null)
and sal >
(
select
max(sal) from emp where empno not in
(select distinct mgr from emp where mgr is not
null);
)
第二十五课 25_create_new_user_and_insert
oracle的逻辑结构:
安装oracle的时候首先安装了一个全局数据库,名称自己起,然后为这个数据库创建了一系列的管理这个数据库的进程,SID名自己起。
在oracle一个大数据库里面又逻辑上区分为不同的表空间,表放在不同的表空间里面,users是oracle已经建好的表空间。用户的表可以放在不同的表空间里面。
一个用户可以使用一个或多个表空间,一个表空间也可以供多个用户使用。用户和表空间没有隶属关系,表空是一个用来管理数据存储逻辑概念,表空间只是和数据文件发生关系,数据文件是物理的,一个表空间可以包含多个数据文件,而一个数据文件只能隶属一个表空间。
SQL>conn sys/bjsxt as
sysdba; 用超级管理员登录
SQL>drop user liuchao
cascade; 删除用户
备份与恢复
backup scott
把scott相关的表等内容备份出来
DOS环境下:exp导出,imp导入
exp命令实际上是执行了
oracle底下的一个export.exe文件
--drop user yugang
cascade;
--cd
--cd temp
=-delete *.*
--exp
SQL> create user
yugang indentified by yugang default tablespace users quota 10M on
users 在users这个表空间上给它分配10M的空间,存数据超过10M就不能再存了
--grant create
session,create table,create view to yugang
create session:登录权限,用户可以登录到服务器上来了
--imp the data
--exp
第一次输入的用户名密码为:yugang/yugang
第二次输入的用户名密码为:导出数据的用户的用户名和密码
SQL>conn
liuchao/liuchao
SQL>conn
scott/tiger
SQL>insert into dept
values (50,'game','bj');
SQL>rollback
SQL>create table emp2
as select * from emp; 创建与emp表一样的emp2表
SQL>create table
dept2 as select * from dept;
SQL>create table
salgrade2 as select * from salgrade;
SQL>create table emp3
as select * from emp;
SQL>insert into dept2
values (50,'game','bj');
SQL>insert into
dept2(deptno,dname) values (60,'game2');
不插的字段默认为空值
SQL>insert into dept2
select * from dept;
把dept中的记录都插入到dept2里面,表结构要相同
第二十五课 25_rownum
没有排序的话默认是先导入的先显示出来,后导入的后显示出来
rownum是在Oracle中在表的后面加的一个尾字段,并且只能使用诸如rownum<=n这样的
形式,不能与>或>=相连接
1.求薪水值最高的前5条记录.
select ename,sal from
emp where rownum <= 5 order by sal desc;
先取前五个再排序
select ename,sal from
(select ename,sal from emp order by sal desc) where rownum <=
5;
2.求薪水值最高的第6~10条记录.
select ename,sal
from
(
select ename,sal,rownum r from
(select ename,sal from emp
order by sal desc)
)
where r >=6 and r
<= 10;
第二十六课:homework_dml_transaction
SQL面试题
有三张表S、C、SC
S(SNO、SNAME)代表(学号、姓名)
C(CNO、CNAME、CTEACHER)代表(课号、课名、老师)
SC(SNO、CNO、SCGRADE)代表(学号、课号、成绩)
1.求出没选过郭富城老师的所有学生姓名
2.列出2门以上(含2门)不及格的学生姓名及平均成绩
3.既学过1号课程又学过2号课程的所有学生姓名
1.select sname from s
join sc on (s.sno = sc.sno) join c on (c.cno = sc.cno)
where c.cteacher
<> '郭富城';
2.select sname from s
where sno in
(
select sno from sc where scgrade < 60 group by
sno having count(*) >= 2
)
3.select sname from s
where sno in
(
select sno from sc where cno=1 and sno in
(select distinct sno from sc
where cno=2)
)
SQL> update emp2 set
sal=sal*2, ename=ename||'-' where deptn=10;
SQL> select ename,
sal from emp2 where deptno=10;
SQL> delete from
emp2;
SQL> delete from
dept2 where deptno < 25;
SQL> rollback;
SQL> drop table t;
删除表
事务transaction代表一组不可分割的操作,要么全部执行,要么全部不执行,
transaction起始于一条DML语句,结束于commit;语句,或者是DCL、DDL语句,在事务未提交前可以通过rollback回滚事务,正常退出事务会自动提交,非正常退出事务会自动回滚。
rollback,回到最原始的状态,也算transaction完成了
transaction什么时候结束?
1、用户显式地执行commit语句提交操作或者是rollback语句回退;
2、执行DDL或者DCL语句时事务自动提交;
3、当用户正常断开连接的时候,比如exit退出sqlplus;非正常断开包括直接点右上角关闭按钮关闭sqlplus窗口,直接关机等,这时事务自动回滚。
第二十七课:create table
create table
student
(
id number(6),
name varchar2(20),
sex number(1),
age number(3),
sdate date,
grade number(2) default
1,
class number(4),
email varchar2(50)
);
第二十八~三十课 constraint
五个约束条件:非空、唯一、主键、外键、check
not
null,unique(当某字段有unique约束时,可以插入空值,空值之间不重复)、主键、外键、 check约束
约束条件本身在数据库里面也是一个对象,可以为约束条件起一个名字,不起名系统默认给起名
唯一约束unique:在这个字段里面所有的约束不能取重复的值,每个值必须是唯一的
check约束:本身用得少,写数据库系统和MIS系统的时候对于数据的检验在java中就校验过了
create table
student
(
id number(6),
name varchar2(20)
constraint stu_name_nn not null,
sex number(1),
age number(3),
sdate date,
grade number(2) default
1,
class number(4),
email varchar2(50)
unique
)
/
字段级/行级约束(放在字段后面)与表级约束(加在表后面):
字段级约束有一些不能完成的功能,比如要求name和email的组合不能重复
当一个字段取值是唯一的时候,可以给它往里插空值,而且两个空值不认为是重复的
create table
student
(
id number(6),
name varchar2(20)
constraint stu_name_nn not null,
sex number(1),
age number(3),
sdate date,
grade number(2) default
1,
class number(4),
email
varchar2(50),
constraint
stu_email_name_uni unique(name,email)
)
/
主键约束
主键primary
key:可以唯一标识整条记录;建一个主键的时候,会随着这个主键建立一个索引;从语法上来说主键相当于非空和唯一的组合,在逻辑意义上通常代表着单独的每一条记录
create table
student
(
id number(6) primary
key,
name varchar2(20)
constraint stu_name_nn not null,
sex number(1),
age number(3),
sdate date,
grade number(2) default
1,
class number(4),
email
varchar2(50),
constraint
stu_email_name_uni unique(name,email)
)
/
create table
student
(
id number(6),
name varchar2(20)
constraint stu_name_nn not null,
sex number(1),
age number(3),
sdate date,
grade number(2) default
1,
class number(4),
email
varchar2(50),
constraint stu_id_pk
primary key(id),
constraint
stu_email_name_uni unique(name,email)
)
/
外键约束
外键约束被参考的字段必须是主键。
如果被其它表的字段参考,则该记录不能删除
create table class
(
id number(4) primary
key,
name varchar2(20) not
null
)
/
create table
student
(
id number(6),
name varchar2(20)
constraint stu_name_nn not null,
sex number(1),
age number(3),
sdate date,
grade number(2) default
1,
class number(4)
references class(id),
email
varchar2(50),
constraint stu_id_pk
primary key(id),
constraint
stu_email_name_uni unique(name,email)
)
/
create table
student
(
id number(6),
name varchar2(20)
constraint stu_name_nn not null,
sex number(1),
age number(3),
sdate date,
grade number(2) default
1,
class number(4),
email
varchar2(50),
constraint stu_id_pk
primary key(id),
constraint stu_class_fk
foreign key (class) references class(id),
constraint
stu_email_name_uni unique(name,email)
)
/
第三十一课:alter table drop table
修改表结构
alter table student
add(addr varchar2(100));
alter table student
drop(addr);
alter table student
modify(addr varchar2(50));
修改之后的新的精度必须能容纳原有的数据
若addr字段中有的记录长度大于50,则修改不成功。
alter table student drop
constraint stu_class_fk;
alter table student add
constraint stu_class_fk foreign key (class)
references class
(id);
drop table
class;
第32课:Oracle dictionaries
数据字典表:记录了当前用户下有哪些表、约束、视图等等,也是一张表,包括user_tables、user_views、user_constraints等
desc user_tables;
select table_name from
user_tables;
select view_name from
user_views;
select constraint_name
from user_constraints;
select
constraint_name,table_name from user_constraints;
desc dictionary;
dictionary:记录了存储多少张数据字典表,TABLE_NAME:表的名字,COMMENTS:对于这张表的描述
select table_name from
dictionary;
select table_name from
dictionary where table_name like 'USER%';
select table_name from
dictionary where table_name like 'USER%' order by table_name;
第33课:indexes_and_views
索引:新的数据库对象
索引可以提高读数据的效率,但会降低修改、写数据的效率,索引还会占用一定的存储空间
create index
idx_stu_email on student(email);
create index
idx_stu_email on student(email,class);
为两个字段的组合建一个索引
drop index
idx_stu_email;
select index_name from
user_indexes;
当给这张表的某个字段加了主键约束、唯一约束,oracle会建立对应的这个字段的索引
视图就是一个子查询,有时能简化查询
视图会带来一些维护的代价,比如表结构改了,那么视图也得跟着改变。视图还可以用来保
护私有数据。
视图是可以更新数据的,只要不影响其它的表,但很少这么用,
select view_name from
user_views;
第34课:sequences and review
sequences:oracle里面特有的东西(sql server里用identity,my
sql里用auto_increment),产生一个唯一的、不间断的数字序列,一般是用来做主键
序列可以自动增长,在sql
server中有identity,mysql中有auto_increment
create table
article
(
id number,
title
varchar2(1024),
cont long
)
/
如何保证插入数据时id不重复?
select max(id) from
article;这样做也不行,如果有多个线程同时访问数据,则可能会出现
数据不一致的现象,比方说第一个用户查询出最大id值是100,第二个用户也查询出最大id是
100,然后第二个用户新插入一条记录:id是101,然后第一个用户也插入了一条记录:id也是
101
可以用Oracle的sequence,内部已经做了线程同步
create sequence
seq_article_id;
多个表之问可以共用一个序列,但是一般情况下为每个字段分别建立一个序列
sequence有两个属性CurrVal、NextVal
start with 1:从1开始
increment by
1:每次递增1
select seq.nextvalue
from dual;
insert into article
values(seq.nextval,'y','yy');
insert into article
values(seq.nextval,'x','xx');
insert into article
values(seq.nextval,'z','zz');
insert into article
values(seq.nextval,'q','qq');
drop sequence
seq_article_id; 删除一个序列
数据库的常用对象:表(table)、约束(constraint):依附在表的上面、索引(index)、视图(view)、序列(sequence):产生一个独一无二的序列,可以用在其它表里面
第35课:三范式
范式:数据库设计的一些规则,设计数据库时所遵循的一些规则,追求不存在冗余数据这个原则,就是同样的数据不存第二遍
第一范式:
1.每个表都要有主键
2.列不可分,比如:
create table stu
(
id number,
name varchar2(20),
age number
)
/
insert into stu
values(1,'yu',21);
create table stu2
(
inf long
)
/
insert into stu2
values('1_yu_21');
虽然表stu2可以字符串解析后可以达到和表stu一样的效果,但是显然第一种方法更好,查询数
据更加方便,而表stu2违反了第一范式的列不可分原则。
列也不能重复
第二范式:
多对多关系这种表设计的时候,当一张表里面有多个字段作为主键的时候,非主键的字段不能够依赖于部分主键;即不能存在部分依赖;不是主键的字段不能部分依赖于主键
当有两个以上字段作主键时,非主键字段不能部分依赖于主键字段,如有一个需求,一门老师教
多名学生,一名学生可以选多个老师的课。然后设计了一张表,有以下字段(老师编号、学生编
号、老师姓名、学生姓名等),其中以老师编号和学生编号作联合主键,则些表就存在部分依赖
,老师姓名部分依赖于老师编号,不满足第二范式,有数据冗余。要解决这个问题可以设计三张表。
第三范式:
不能存在传递依赖,就是说除了主键以外的任何其它字段必须直接依赖于主键
不能存在传递依赖,如有一张表有以下字段:(学号、姓名、班级号、班级名称、班级位置),
其中学号为主键,则班级号依赖于学号,每个学生都有相应的班级号,但是班级名称、班级位置是依赖于班级号,即它们通过班级号传递依赖于学号,不满足第三范式。
第三十六~三十七课 BBS
第38课:PL_SQL_1
PL_SQL:在oracle里面的一种编程语言,oracle内部使用的编程语言,这种编程语言通常在oracle内部写一些存储过程,写一些oracle内部的程序,写一些触发器,写一些函数等等,基本上每一种数据库都有这样一种语言,sql
server的叫transact sql(T-SQL),是在sql server内部实现同样功能的语言
PL(procedure
language)语言是为了补充sql语言的,是带有分支和循环的语言,sql语言没有分支、循环的概念,
PL语言很简单,分四块,
第一块:declare(可选):做变量的声明,声明各种变量,游标。
第二块:begin 程序从这开始执行
第三块:exception
:catch到任何exception时执行的
第四块:end;
结束,要有分号
set serveroutput
on;
begin
dbms_output.put_line('Hello World!!');
end;
必须先执行 set serveroutput
on; 命令(因为serveroutput(内部环境变量)默认为off),再执行上面的程序就能打印出结果
第39课:PL_SQL_2
declare
v_name varchar2(20);
变量名一般用v_开头
begin
v_name:='xiaoyu';
dbms_output.put_line(v_name);
end;
再来认识一下整个语句块的构成:
declare
v_num number:=0;
begin
v_num:=2/v_num;
dbms_output.put_line(v_num);
exception
when others then
dbms_output.put_line('error');
end;
第40课:PL_SQL_3
--变量声明的规则
1.变量名不能使用保留字,如from、select等
2.第一个字符必须是字母
3.变量名最多包含30个字符
4.不要与数据库的表或者列同名
5.每一行只能声明一个变量
--常用变量类型
1.binary_integer:整数,主要用来计数而不是用来表示字段类型。当一个数用来记数的时候使用binary_interger效率要高一些,除了记数还用来做数组的下标
2.number:数字类型,既包含整数又包含小数
3.char:定长字符串
4.varchar2:变长字符串
5.date:日期
6.long:长字符串,最长2GB
7.boolean:布尔类型,可以取值true、false和null值,默认为null,在PL_SQL中使用boolean类型时,要给它一个初值,另外boolean类型值不可直接打印,会报错;只能是if(变量=true/false)
来使用
Oracle
里面的put_line()不能打印boolean类型的值
dbms_output.put_line('v_temp value:' || v_valid); //报错
变量经常是用来存储某一张表某一个字段的值,如v_sal来存储emp表中sal字段的值
查看当前用户:show
user
--变量的声明,使用%type属性
declare
v_empno number(4);
v_empno2
emp.empno%type;//表示类型和emp这张表的empno字段类型相同,当empno类型改动时,不需要再到这里改动v_empno2,会自动变换
v_empno3 v_empno2%type;
//%type属性,可以用于变量身上 ,是变量的一种属性,指的是它的类型
begin
dbms_output.put_line('Test');
end;
/
//使用%type属性,可以使变量的声明根据表字段的类型自动变换,省去了维护的麻烦,而且% type属性,可以用于变量身上
PL/SQL
里面--表示注释掉一行,注释多行
41_PL_SQL_4.avi
复杂的变量:
复合变量有两种:
第一种:table 相当于java里面的数组Array[]
在PL/SQL里面要声明数组,也要指定它的类型,PL/SQL里比较复杂,
必须重新定义新的类型,这里先声明类型,再用类型声明变量,下标允许有负值
--Table变量类型
declare
首先声明一个类型,再用类型声明一个变量
type
type_table_emp_empno is table of emp.empno%type index by
binary_integer;
--声明数组类型,type表示定义了一种新的类型;type_table_emp_empno是类型的名字,起名规则:type说明是一种新定义的类型,table说明是定义的哪种类型,emp表名,empno表中的类型;is
table of表示是一张表;emp.empno表示里面装的是emp这张表的empno类型,index by
binary_integer表示是由binary_integer来索引,说明下标类型是binary_integer类型
v_empnos type_table
type_table_empno; 用上面定义的类型定义新的变量
begin
v_empnos(0) := 7369;
下标值为0的位置
v_empnos(0) :=
7839;
v_empnos(-1) :=9999;
下标值可以取负值
dbms_output.put_line(v_empnos(-1));
end;
/
第二种:record 相当于java里面的类
42_PL_SQL_3.avi
Record变量类型 相当于java里面的类
PL/SQL程序大多数情况下操作的对象都是表,表里面有字段,有记录,一条记录分了好几个部分,包括姓名、年龄、性别、介绍等等,分了四个部分,record这种变量内部也分好几块,一下子把这样一条记录都放在这样一个变量里面,用起来会方便很多,
例子:
declare
type type_record_dept is
record
(
deptno
dept.deptno%type,
dname
dept.dname%type,
loc
dept.loc%type
);
v_temp
type_record_dept;
begin
v_temp.deptno :=
50;
v_temp.dname :=
'aaaa';
v_temp.loc :=
'bj';
dbms_output.put_line(v_temp.deptno || ' ' || v_temp.dname);
end;
/
record的缺陷:当dept这张表结构变化时,增加或删除一个字段时,type里面也需要改动。因此可以使用
%rowtype声明record变量,代表和表的字段同时变动
--使用%rowtype声明record变量
declare
v_temp
dept%rowtype;
begin
v_temp.deptno :=
50;
v_temp.dname :=
'aaaa';
v_temp.loc :=
'bj';
dbms_output.put_line(v_temp.deptno || ' ' || v_temp.dname);
end;
/
43_PL_SQL_5.avi
PL/SQL里面--SQL语句的运用
用select语句,必须返回一条记录,并且只能返回一条记录。如果返回太多,变量装不了
PL/SQL里面的select语句如果不用游标的话,必须和into语句一块用并且有且只有一条记录,没有不行,多了也不行。select语句必须能后返回一条记录。
例子1:
--SQL语句的运用
declare
v_ename
emp.ename%type;
v_sal
emp.sal%type;
begin
select ename, sal into
v_ename, v_sal from emp where empno = 7369;
--pl/sql的select语句里,必须有into,不用游标就必须用into
dbms_output.put_line(v_ename || ' ' || v_sal);
end;
/
例子2:select语句的应用(record)
declare
v_emp emp%rowtype;
begin
select * into v_emp from
emp where empno = 7369;
必须保证能返回记录,否则报错未找到数据;返回多条记录也会报错
dbms_output.put_line(v_emp.ename);
end;
/
其他语句的运用:
例子3:
declare
v_deptno
dept.deptno%type := 50;
v_dname dept.dname%type
:= 'aaaa';
v_loc dept.loc%type :=
'bj';
begin
insert into dept2 values
(v_deptno, v_dname, v_loc);
commit;
end;
例子4:
declare
v_deptno
emp2.deptno%type := 10;
v_count number;
begin
--update emp2 set sal =
sal/2 where deptno = v_deptno;
--select deptno into
v_deptno from emp2 where empno = 7369;
select count(*) into
v_count from emp2;
dbms_output.put_line
(sql%rowcount || '条记录被影响');
commit;
end;
注:sql%rowcount 统计上一条sql语句更新的记录条数
44_PL_SQL_6.avi
PL/SQL 里面的DDL语句,需要加execute immediate
例子1:
begin
execute immediate
'create table T (nnn varchar2(20) default ''aaa'')';
--不能直接写,单引号里面用两个单引号代表一个单引号
end;
命令提示符下:sqlplus 用户名/密码
drop table T;
select ename,sal from emp2;
desc T;
--if语句:
--取出7369的薪水,如果<1200, 则输出'low',如果 <2000 则输出 'middle',
否则'high'
declare
v_sal
emp.sal%type;
begin
select sal into v_sal
from emp
where empno = 7369;
if(v_sal < 1200)
then
dbms_output.put_line('low');
elsif(v_sal < 2000)
then
dbms_output.put_line('middle');
else
dbms_output.put_line('high');
end if;
end;
45_PL_SQL_7.avi
--循环 1
declare
i binary_integer :=
1;
begin
loop
dbms_output.put_line(i);
i := i+1;
exit when ( i>=11);
end loop;
end;
上面的循环相当于java里的 do-while 循环
--循环 2
declare
j binary_integer :=
1;
begin
while j < 11
loop
dbms_output.put_line(j);、
j := j + 1;
end loop;
end;
以上循环相当于 java 里的 while 循环
--循环 3
begin
for k in 1..10
loop
dbms_output.put_line(k);
end loop;
for k in reverse 1..10
loop //reverse表示逆序
dbms_output.put_line(k);
end loop;
end;
以上循环相当于 java 里的 增强 for 循环
46_PL_SQL_8.avi
--错误处理,在pl/sql里面每一种exception都有一个名字
例子1:
declare
v_temp number(4);
begin
select empno into v_temp
from emp where deptno = 10; --这条记录会返回很多值,所以
产生异常
exception
when too_many_rows
then
dbms_output.put_line('太多记录了');
when others then
dbms_output.put_line('error');
end;
没有找着数据的异常:
例子:
declare
v_temp number(4);
begin
select empno into v_temp
from emp where empno = 2222;
exception
when no_data_found
then
dbms_output.put_line('没数据');
end;
DBA经常使用的一种用来记录错误的做法:
大多数人做记录都是写在日志文件里面,对于数据库里面的记录可以记录在表里面
例子:
创建一张表用来记录出错的信息
create table errorlog
(
id number primary
key,
errcode number,
errmsg
varchar2(1024),
errdate date
);
创建序列sequence
create sequence seq_errorlog_id start with 1 increment by
1;
--使id自动递增
写你的程序
declare
v_deptno
dept.deptno%type := 10;
v_errcode number;
v_errmsg
varchar2(1024);
begin
delete from dept where
deptno = v_deptno;
//删不了,因为被引用了
commit;
exception
when others then
rollback;
v_errcode := SQLCODE;
//SQLCODE是关键字,代表出错的代码
v_errmsg := SQLERRM;
//SQLERRM是关键字,代表出错的信息
insert into errorlog
values (seq_errorlog_id.nextval, v_errcode, v_errmsg,
sysdate);
commit;
end;
执行
select * from errorlog;
报错误;
delete from dept where detpno = 10;
报错误
查看具体的出错时间:
select to_char (errdate, 'YYYY-MM-DD HH24:MI:SS') from
errorlog;
47_cursor1.avi
PL/SQL里面的重点-->游标
游标是什么意思呢?
请你挨着排的把emp那张表的每个人他的一些相关信息拿出来(注意:每个人都拿出来),拿出来之
后根据他不同的薪水值,来设定他新的薪水值 <1200 的 *2 , >1200 的 /2 等等
select
语句可以产生一个结果集,游标是指在结果集脑袋顶上的一个指针,游标跟java里面的迭代器(iterator)差不多,所以游标指的就是一个指针,它指在一个结果集上,所以有了这个游标之后,你就可以对这个结果进行遍历,循环,先拿第一条,拿出来之后接下来next一下,再拿第二条,游标是我们的重点,一定要理解。
例子1:
declare
cursor c is
--声明游标
--cursor
是游标的意思,相当于一个指针,cursor的本意就是我们鼠标上的小指针
select * from emp;
//取出了一个结果集
v_emp c%rowtype;
--v_emp用来存储我们这个游标所指向的结果集的记录
begin
open c; --打开游标 注意:cursor
c 叫做声明游标,声明游标的时
候PL/SQL不会真正的取数据,只有打开游标的时候PL/SQL才会真真正正的取数据执行select语句
fetch c into v_temp;
--把当前游标指向的内容拿出来放到v_emp变量里面去
--fetch拿出来之后游标自动的往下移一格
dbms_output.put_line(v_emp.ename);
close c; --用完游标之后要close
c,把内存清掉
end;
没有游标的话,甚至我说让你把emp那张表里面,每一个记录都取出来,恐怕你都不知道怎么样取?
如果取好多条怎么办?
应该和循环一起用,用一个东西取探测这个游标什么时候到底了。
下面来看看用do-while循环,也就是简单循环来遍历游标
例子2:
declare
cursor c is
select * from emp;
v_emp c%rowtype;
begin
open c;
loop
fetch c into v_emp; --这个时候就把第一条记录放进去了
exit when (c%notfound);
--用notfound这个属性和简单循环配合来遍历这个游标指向最后一条的时候能fetch到记录
dbms_output.put_line(v_emp.ename);
end loop;
close c;
end;
cursor 有四个属性:
isopen,布尔型,游标是否打开
notfound,布尔型,最近的fetch语句没有返回一条记录,找不到记录就返回ture
,指向最后一条的时候能fetch到记录
found,布尔型,找着记录了就返回ture
rowcount,数字型,当前已经fetch到了多少条记录,总数是多少,返回已从游标中读取的记录数
多问一句:
我把dbms_output.put_line(v_emp.ename);放到exit when (c%notfound);
上面,最后输出的 结果会是什么样子?
当你到最后一条记录的时候fetch 它,没有找到,那你还打印它的值,就把上一条记录的值再
打印一遍,所以这三条语句顺序是不能颠倒的。
48_cursor2.avi
另外两种循环,一种是while循环,一种是for循环
例子1:
declare
cursor c is
select * from emp;
v_emp emp%rowtype;
begin
open c;
fetch c into v_emp;
--首先fetch一下
while(c%found) loop
--如果找到了 loop循环
dbms_output.put_line(v_emp.ename);
fetch c into v_emp; --接下来再fetch
end loop;
close c;
end;
while循环的写法:如果找到了,循环,打印一行,然后再fetch,这是while循环的写法
问大家一句:如果把fetch c into v_emp;放到
dbms_output.put_line(v_emp.ename)前面什么效果?
---->第一句没打,最后一句打了两遍
为什么打印两遍,最后一条找到了,找到了之后你还找,最下面的没了,找就再找一
次上面的。所以,最后一条记录打了两遍。
for循环:
declare
cursor c is
select * from emp;
begin
for v_emp in c loop
--上面不用声明变量v_emp,for开始的时候自动声明v_emp
-- c不需要打开,for开始的时候自动打开,结束的时候自动关闭
-- 不需要每次都fetch,它自动帮你fetch完了进入下一条记录
-- 它挨着排的循环每条记录,然后挨着排的放到v_emp里面
dbms_output.put_line(v_emp.ename);
end loop;
end;
--看这三种循环,for循环恐怕是最简单,并且它很不容易出错,前后语句不容易颠倒,你也不
需要根据cursor的属性来判断cursor是不是结束,所以for循环是我们平常用的最多的循环
--带参数的游标
例子:
declare
cursor c(v_deptno
emp.deptno%type, v_job emp.job%type)
is --这个游标的语句是什么 -->
is
select ename, sal from emp where deptno =
v_deptno and job = v_job;
--v_temp c%rowtype;
--v_temp 不需要声明,用的for循环
begin
for v_temp in c (30,
'CLERK') loop
dbms_output.put_line(v_temp.ename);
end loop;
end;
--可更新的游标 -->不是重点,大多数游标是用来做遍历的 ,用来读的,只读的
declare
cursor c
is
select * from emp2 for
update; --为了更新才使用的游标
--v_temp c%type;
begin
for v_temp in c
loop
if(v_temp.sal < 2000) then
update
emp2 set sal = sal * 2 where current of c; --当前游标指到哪条记录上就更新哪
条游标,这个时候不需要知道游标到哪条记录了,游标到哪条记录就更新哪条记录
elsif (v_temp.sal = 5000) then --这个要小心,这里用 = 来判断
,赋值用的是:=
delete
from emp2 where current of c;
end if;
end loop;
commit;
end;
49_produre_1.avi
这个时候我们可以随便写PL/SQL程序了
例子:
declare
cursor c
is
select * from emp2 for update;
--v_temp
c%rowtype;
begin
for v_temp in c
loop
if(v_temp.sal < 2000) then
update
emp2 set sal = sal * 2 where current of c;
elsif(v_temp.sal = 5000) then
delete
from emp2 where current of c;
end if;
end loop;
commit;
end;
/
如果你下次登录进来还想执行这个程序你怎么办?有没有一个办法,比如给这个程序起一个名
字,以后调用的时候直接调用这个名字就行了?
-> 有,这个办法就是创建存储过程(Stored
Procedure),每种数据库都会提供编写数据库存储语言,实际上它就是 带有名字的PL/SQL程序块
最简单的存储过程的例子:
create or replace procedure p --如果有就把它替换,没有就创建
is
--除了以上语句替换了declare以外,剩下的语句跟PL/SQL语句一模一样
cursor c is
select * from emp2 from update;
begin
for v_emp in c
loop
if(v_emp.deptno = 10) then
update
emp2 set sal = sal + 10 where current of c;
elsif(v_emp.deptno = 20) then
update
emp2 set sal = sal + 20 where current of c;
else
update
emp2 set sal = sal + 50 where current of c;
end if;
end loop;
commit;
end;
/ --执行后,说这个存储过程已经创建,不等于这个存储过程已经执行了
你要执行这个存储过程有两个办法,
第一个:exec p;
select sal from emp2; --执行这个语句会发现,sal已经改变
第二个:
begin
p;
end;
/ --执行这个块,它说PL/SQL过程成功完成。
select sal from emp2; --测试执行后的结果
总结:存储过程就是一个块,只是这个块带有名字,执行的时候只要执行名字就可以了
--带参数的存储过程(这个稍微复杂一些)
create or replace procedure p
(v_a in number, v_b number, v_ret out number, v_temp in out
number)
--在存储过程里面,参数可以分为不同的类型
-- in 叫做传入参数->谁调用这个存储过程谁负责给v_a赋值,从调用这个环境往我们这个存储
过程里头传的参数
-- out 叫做传出参数:存储过程是没有返回值的,它就借助于传出参数,由存储过程把这个值传
出到调用过程(调用的环境)里面去
-- out是负责往外传的,in是负责接收的
-- v_b 中间什么都没写,默认是in,是接收参数用的
-- v_temp 你还可以既可以接收,又可以传出
is
begin
if(v_a > v_b)
then
v_ret := v_a;
else
v_ret := v_b; --给v_ret赋了值
end if;
v_temp := v_temp + 1;
--既用了原来的值,又给它了新的值
end;
/ --创建好这个过程怎么用这个过程?
调用过程:
declare
v_a number := 3;
v_b number := 4;
v_ret number; --
传一个空篮子,让它往里面装东西,装完了给我用
v_temp number :=
5;
begin
p(v_a, v_b, v_ret,
v_temp); -- 调用过程,传入参数进去
dbms_output.put_line(v_ret);
dbms_output.put_line(v_temp);
end;
注意:存储过程语法错误的时候,它只告诉你:创建的过程带有编译错误。但是不告诉你哪错
了,如果你想知道哪里错了?
show error
接下来会告诉你哪里错误,即便是语法错误,过程依然会创建
50_produre_2.avi
删除procedure
--函数
create or replace function sal_tax
//计算个人所得税
(v_sal number)
return number
is
begin
if (v_sal < 2000)
then
return 0.10;
elsif (v_sal < 2750)
then
return 0.15;
else
return 0.20;
end if;
end;
select lower(ename),sal_tax(sal) from emp;
--触发器
create table emp2_log
(
uname
varchar2(20),
action
varchar(10),
atime date
)
-----------
create or replace trigger trig
after insert or update
or delete on emp2 (for each row)
--after是插入完成之后触发触发器,before是在插入之前触发触发器
--USER是关键字,代表的是当前用户是谁
--for each
row表示每更新一行就会触发一次操作
begin
if inserting then
insert into emp2_log values (USER, 'insert',
sysdate);
elsif updating
then
insert into emp2_log values (USER, 'update',
sysdate);
elsif deleting
then
insert into emp2_log values (USER, 'delete',
sysdate);
end if;
end;
ORACLE提供的如下条件谓词。
1)INSERTING:当触发事件是INSERT时,取值为TRUE,否则为FALSE。
2)UPDATING [(column_1,column_2,…,column_x)]:当触发事件是UPDATE
时,如果修改了column_x列,则取值为TRUE,否则为FALSE。其中column_x是可选的。
3)DELETING:当触发事件是DELETE时,则取值为TRUE,否则为FALSE。
----------
update emp2 set sal = sal * 2 where deptno = 30;
select * from emp2_log;
update dept set deptno=99 where deptno=10;
drop trigger trig;
--------
create or replace trigger trig
after insert or update
or delete on emp2 for each row
begin
if inserting then
insert into emp2_log values (USER, 'insert',
sysdate);
elsif updating
then
insert into emp2_log values (USER, 'update',
sysdate);
elsif deleting
then
insert into emp2_log values (USER, 'delete',
sysdate);
end if;
end;
-------------
第五十一课51_trigger
SQL>update dept set deptno=99 where deptno=10;
违反约束条件。由于deptno被人参考了,所以不能这么写;除非在这个值改成99的同时也要把那些参考了10的那些参考的值改成99,两边的值同步的变
--不提倡使用,很少这么用
create or replace trigger trig
after update on dept for
each row
begin
update emp2 set deptno =
:NEW.deptno where deptno = :OLD.deptno;
end;
----------------------
update一句话是先检查约束条件,同时也会触发触发器,是先触发触发器还是先检查约束条件?
先触发触发器。
第五十二课52_recursion
--树状结构的存储与展现
BBS帖子本身就是一个树状结构;这个树状结构的存储:帖子有一个id号,后面跟着一个pid,pid就是它父id是谁,这两个字段有了树状结构就存储下来了。
如何把这个树状结构展现出来:存一个id存一个pid,但只有这两个字段在用程序做展现的时候非常的麻烦;用递归方式存储树状结构,往往会增加一些冗余字段
drop table article;
create table article
(
id number primary key,
--id号不一定是连续的
cont
varchar2(4000),
pid number,
isleaf number(1),
--0代表非叶子节点,1代表叶子节点
alevel number(2)
--alevel表示级别是哪个级别
)
-------------
insert into article values (1, '蚂蚁大战大象', 0, 0, 0);
insert into article values (2, '大象被打趴下了', 1, 0, 1);
insert into article values (3, '蚂蚁也不好过', 2, 1, 2);
insert into article values (4, '瞎说', 2, 0, 2);
insert into article values (5, '没有瞎说', 4, 1, 3);
insert into article values (6, '怎么可能', 1, 0, 1);
insert into article values (7, '怎么没可能', 6, 1, 2);
insert into article values (8, '可能性是很大的', 6, 1, 2);
insert into article values (9, '大象进医院了', 2, 0, 2);
insert into article values (10, '护士是蚂蚁', 9, 1, 3);
commit;
---------
蚂蚁大战大象
大象被打趴下了
蚂蚁也不好过
瞎说
没有瞎说
大象进医院了
护士是蚂蚁
怎么可能
怎么没有可能
可能性是很大的
第五十三课53_recursion2
使用一个存储过程,把这些数据插到表里面,然后使用一个存储过程展现成上面的形式
create or replace procedure p(v_pid article.pid%type) is
cursor c is select *
from article where pid=v_pid;
begin
for v_article in c
loop
dbms_output.put_line(v_article.cont);
if(v_article.isleaf=0) then
p(v_article.id);
end if;
end loop;
end;
SQL>set serveroutput on;
SQL>exec p(0);
怎么产生缩进?
create or replace procedure p(v_pid article.pid%type,v_level
binary_integer) is
cursor c is select *
from article where pid=v_pid;
v_preStr
varchar2(1024):=''; --前导字符串
begin
for i in 1..v_level
loop
v_preStr:=v_preStr||'
';
end loop;
for v_article in c
loop
dbms_output.put_line(v_preStr||v_article.cont);
if(v_article.isleaf=0) then
p(v_article.id,v_level+1);
end if;
end loop;
end;
SQL>exec p(0,0);
//会把空格自动去掉,改成*就显示
考虑一下怎么样才能把emp那张表的树状结构展现出来?