Oracle数据库

一、数据库类型

a.层次型数据库
b.网状型数据库
c.关系型数据库

二、数据库操作

如何操作数据库:

1.运行cmd打开控制台窗口–>输入sqlplus–>如果是普通用户,输入“用户名/密码@服务名”,如果是sys用户用dba权限进入的话,输入“用户名/密码@服务名 as sysdba”

2.使用第三方工具来操作oracle数据库: PLSQL developer Toad DBVisual
表空间:在oracle中可以建立多个表空间,一个表空间可以关联多个数据文件,一个数据文件只能和一个表空间关联,默认系统的表空间是SYSTEM
创建表空间:
create tablespace 表空间名
datafile ‘url’ //表空间文件保存路径
size 表空间大小
autoextend on //表空间容量不足自动扩容
例如:

create tablespace buaa
datafile 'D:\app\lenovo\oradata\orcl\buaa.dbf'
size 100M
autoextend on

创建用户:
create user 用户名
identitied by 密码
default tablespace 默认表空间名
temporary tablespace 临时表空间名
例如:

create user Scott
identified by tiger
default tablespace buaa //默认表空间名和创建表空间名保持一致
temporary tablespace TEMP

赋予用户权限:
grant 权限1,权限2,…… to 用户名
例如

grant connect,dba,resource to scott //注意不是connection和source

基本数据类型:
常用的:char varchar2 number date timestamp
创建表:
Create table 表名(
字段1 数据类型[(长度)],
字段2 数据类型[(长度)],
。。。。。。。。。

查询数据:
select column1,column2,…from 表名
例如:

select * from t_user    //最好是写成字段名。如:
select uid,uname,...from t_user

修改表结构,基本SQL语句
rowid:表示一条记录在内存中地址,能够唯一的表示一条记录。
rownum:行号,可以限制查询上来的记录数

注意:只能写rownum<数,不识别大于号,要想实现用rownum实现>来限制查询,可以利用子查询,把rownum 当以要普通的查询的列名在来限制查询

修改表结构:
增加列:在原来表基础上增加列
alter table 表名 add(column1 数据类型,column2 数据类型…….)
例如

alter table student add(chinese number, english number)

修改列://在原来的标的列的基础上进行修改,一般用于改数据类型
alter table 表名 modify(列名1 数据类型,列名2 数据类型……)
例如:

alter table student modify(name varchar2(50))

删除列:
alter table 表名 drop column 列名//注意不仅有drop还有column,是drop column 这个整体
例如:

alter table t_student drop column English

删除表:
drop table 表名
主键(primary key):能唯一表示身份的,只标识一条记录,主键不能重复,一张表中只能有一个主键
设置主键:
alter table 表名 add constraint 键名 primary key(列名)
例如:

alter table t_user add constraint pk_uid primary key(id)

外键(foreign key):用来做表之间的关联,一个表中的外键一定是其他表中的主键
设置外键:
alter table 表名 add constraint 键名 foreign key(列名) references 表名(列名)
例如:

alter table t_student add constraint fk_classid foreign key(classid) references class(id)

删除键:
alter table 表名 drop constraint 键名
例如:

alter table t_student drop constraint fk_classid

增加数据SQL语句:
Oracle中的字符串都是用单引号括起来的。
Sysdate表达当前日期
Insert into 表名(列1, 列2,…) values(值1,值2,…)
例:

insert into student(id, name, birth, math, classid) values('106', '张辽 ', sysdate, 89, 4)

如果要插入的是该表中的所有列的数据
insert into 表名 values(值1,值2.。。。。)
例:)

insert into student values('107', '张龙', sysdate, 78, 69, 78, 4

查询语句:可以在select语句表名的后面使用where语句来限制查询的条件
例:查询班级号为3的学生

select * from student where classid='3'

在oracle中用“!=”或“<>”来表示不等于
例:查询班级号不为3的学生

select * from student where classid<>'3'

还可以使用逻辑去处符来限制查询条件(and or not)
例:查询数学成绩在60到80之间的学生

select * from student where math>=60 and math<=80

上述内容还可以使用between ….and….来实现

select * from student where math between 60 and 80

使用is null 和 is not null 来判断值是否为空
例:查询没有语文成绩的学生

select * from student where chinese is null

例:查询英语成绩为空,并且语文成绩不为空的学生

select * from student where english is null and chinese is not null

还可以使用in关键字来限制查询范围
例:

select * from student where math in (95, 65, 25)

通常用dual表来进行一些函数以及数据的测试

select sysdate from dual
select sys_guid() from dual

模糊查询:
通配符:% _
%:0-n个字符
_:一个字符

例:查询student表中姓张的同学

select * from student where name like '张%'

例:查询student表中姓张的,并且姓名是两个字的同学

select * from student where name like '张_'

删除数据:
delete from 表名 [where 条件]
例:delete from student –不建议这样写,除非你想删除全表数据
删除id号为101的记录

delete from student where id='101'

更新语句:
update 表名 set 列名=值, 列名=值。。。。。。[where 条件]
建议写where条件,否则做的是全表更新
例:姓张的同学的,并且姓名是两个字的,将他们的数学成绩提高到90分

update student set math=90 where name like '张_'

rowid:表示一条记录在内存中的地址,能够唯一标识一条记录。
rownum:行号,可以限制查询所得到的记录数。
注意:在写查询语句时注意空格,在写表名,字段名时,可以空一格写别名
如:select t.name,t.age from teacher t

修改表结构:

  1. 增加列
    alter table 表名 add(列名1 数据类型,列名2 数据类型,…….)
    如:
 alter table teacher add(english number,math number)

2.修改列
alter table 表名 modify(列名1 数据类型,列名2 数据类型,…….)
如:

alter table teacher modify(name varchar2(30))

3.删除列
alter table 表名 drop column 列名
如:

 alter table teacher teacher drop column english

4.删除表
drop table 表名

增加删除主键,附上表结构

create table t_member(
       tid varchar2(100),
       tname varchar2(10),
       tage number,
       tbirth date,
       cid number
)
create table t_class(
       cid number,
       cname varchar2(10),
       cage number,
       cbirth date
)
alter table t_member add constraint pk_tid primary key(tid)

alter table t_class add constraint pk_cid primary key(cid)
alter table t_member add constraint fk_classid foreign key(cid) references t_class(cid)

alter table t_member drop constraint pk_tid
alter table t_member drop constraint fk_classid

**主键(**primary key):能够唯一表示一条记录,主键不能重复,并且一张表中只能有一个主键,具有唯一性。
添加主键:alter table 表名 add constriant 键名 primary key(列名)
如:

alter table teacher add constriant pk_tid primary key(id)

外键:(foreign key):用来做表之间关联作用的,一个表中的外键一定是另一个表的主键。
添加外键:Alter table 表名 add constraint 键名 foreign key(列名) refrences 表名(列名)
如;

alter table teacher add constriant fk_classid foreign key(classid) references class(id)

删除键: alter table 表名 drop constriant 键名
如:

alter table teacher drop constriant fk_classid

增加数据SQL:
oracle中的字符串都是使用单引号括起来的。
sysdate表示当前系统时间。
插入数据:
insert into 表名(column1,column2,….) values(值1,值2,……)
如:

insert into teacher(id,name,age,birth,classid) values ('10','tom',25,sysdate,2)

插入所有列数据可以直接写成:
insert into 表名 values(值1,值2.。。。。)
例:

insert into teacher values('107', '张龙', sysdate, 78, 69, 78, 4)

限制查询语句:使用where以达到限制查询条件
例如:查询班级号为3的学生

select * from student where classid='3'

在oracle中用“!=”“<>”来表示不等于
例:查询班级号不为3的学生

select * from student where classid<>'3'

还可以使用逻辑去处符来限制查询条件(and or not
例:查询数学成绩在60到80之间的学生

select * from student where math>=60 and math<=80

上述内容还可以使用between ….and….来实现

select * from student where math between 60 and 80

使用is nullis not null 来判断值是否为空
例:查询没有语文成绩的学生

select * from student where chinese is null

例:查询英语成绩为空,并且语文成绩不为空的学生

select * from student where english is null and chinese is not null

还可以使用in关键字来限制查询范围
例:
select * from student where math in (95, 65, 25)

通常用dual表来进行一些函数以及数据的测试

select sysdate from dual
select sys_guid() from dual

模糊查询:
通配符:% %:0-n个字符 :一个字符

例:查询student表中姓张的同学

select * from student where name like '张%'

例:查询student表中姓张的,并且姓名是两个字的同学

select * from student where name like '张_'

删除数据:
delete from 表名 [where 条件]
例:delete from student –不建议这样写,除非你想删除全表数据
删除id号为101的记录

delete from student where id='101'

更新语句:
update 表名 set 列名=值, 列名=值。。。。。。[where 条件]
建议写where条件,否则做的是全表更新
例:姓张的同学的,并且姓名是两个字的,将他们的数学成绩提高到90分

update student set math=90 where name like '张_'

三、单行函数

单行函数的功能非常的简单就是完成某一个功能,例如:现在希望将字母变为大写,这样的功能就可以通过单行函数完成,在 Oracle 之中提供的单行函数非常的多,今天主要是看核心的几个。
单行函数分类为:字符串函数数字函数日期函数转换函数通用函数。所有的单行函数在进行记忆的时候都要求清楚的记下单行函数的名称、返回值类型、接收的参数数据。

1、字符串函数

字符串函数的主要功能是进行字符串处理,在oracle内部提供了一个dual的虚拟表,可以用于测试。
1.大小写转换函数

  • 转大写:字符串 UPPER(字符串 | 数据列)
  • 转小写:字符串 LOWER(字符串 | 数据列)
    例如:
select upper('hello'),lower('HELLO') from dual;
SELECT LOWER(ename) FROM emp ;

2.首字母大写,其余字母小写:字符串 initcap(字符串 | 列)
例如:

select initcap(ename) from emp;

3.取得字符串长度,数字 length (字符串 | 列)
例如:

select length('hello world') from daul
SELECT ename,LENGTH(ename) FROM emp ;
--查询出所有雇员姓名的长度为 5 的全部雇员信息
SELECT ename,LENGTH(ename)FROM emp WHERE LENGTH(ename)=5 ;

4.替换指定字符串的内容:字符串 REPLACE(字符串 | 列,要替换的内容,新的内容)

--替换字符串数据
SELECT REPLACE('hello world','l','_') FROM dual ;
--替换数据列
SELECT ename,REPLACE(ename,'A','_') FROM emp ;

5.字符串自取操作
· 由指定位置截取到结尾:字符串 substr(字符串 | 数据列,截取开始索
引);
· 指定截取的开始和结束位置:字符串 substr(字符串 | 数据列,截取开
始索引,截取结束索引);

--字符串截取,由指定位置截取到结尾
SELECT SUBSTR('hello world',7) FROM dual ;
--字符串截取,部分截取
SELECT SUBSTR('hello world',0,5) FROM dual ;
SELECT SUBSTR('hello world',1,5) FROM dual ;

注意:实际上在 Oracle 之中,字符串的索引下标是从 1 开始的(程序是从 0 开始的),但是为了考虑到程序人员的使用习惯,所以即使设置了 0,那么也表示从1 开始,这一点会由 Oracle 自己进行处理。

--截取雇员姓名前三位
SELECT ename,SUBSTR(ename,1,3) FROM emp ;
--截取雇员姓名后三位
SELECT ename,SUBSTR(ename,-3) FROM emp ;

Tip:请问 Oracle 之中的 SUBSTR()函数进行截取的时候下标是从 0 还是从 1开始?
· 实际上从 0 还是从 1 开始没有任何的区别, Oracle 之中的字符串是从 1 开始的,但是即使设置了 0,也表示从 1 开始,同时 SUBSTR()函数还可以设置为负数,表示由后的指定位置开始。

6.去掉左右空格函数(功能类似Java中提供的trim方法):字符串 trim (字符串 | 列)

SELECT ' hello world ',TRIM(' hello world ') FROM dual ;

2、数字函数

1.四舍五入函数:数字 round (数字 | 列[,保留小数位])

--使用dual表验证round()函数
SELECT ROUND(9615.7652) ,ROUND(9615.7652,2) ,ROUND(9615.7652,-2) ,ROUND(-15.5) FROM dual

2.截取小数函数:数字 trunc (数字 | 列 [,保留小数位])

--使用dual表验证trunc()函数
SELECT TRUNC(9615.7652) ,TRUNC(9615.7652,2) ,TRUNC(9615.7652,-2) ,TRUNC(-15.5) FROM dual

3.求模:数字 mod(数字1 | 列1,数字2 | 列2)

SELECT MOD(10,3) FROM dual 

3、日期函数

如果要想操作日期函数实际上有一个首要的问题需要解决。那么必须要首
先取得当前的系统日期时间,为此在 Oracle 里面提供有两个伪列(是不存在表中的列,但是却可以直接使用的列):SYSDATESYSTIMESTAMP

SELECT SYSDATE FROM dual ;
SELECT SYSTIMESTAMP FROM dual ;

简单的日期处理:
· 日期 + 数字 = 日期,表示若干天之后的日期;
· 日期 – 数字 = 日期,表示若干天之前的日期;
· 日期 – 日期 = 数字,表示两个日期之间的间隔天数。

--计算若干天之后的日期
SELECT SYSDATE + 3 , SYSDATE + 120 FROM dual ;
--计算若干天之前的日期
SELECT SYSDATE - 120 FROM dual ;
--统计雇员的受雇天数
ELECT ename, SYSDATE - hiredate FROM emp ;

这种计算时间的方式不是很准确,若果要得到准确的时间,需要利用提供的日期函数
1.计算两个日期之间经历的月数:数字 months_between(日期1 | ;列1,日期2 | 列2)

--计算每一个雇员到现在为止受雇的年数(年数计算最准确的是通过月份计算的)
SELECT ename,MONTHS_BETWEEN(SYSDATE,hiredate) ,MONTHS_BETWEEN(SYSDATE,hiredate)/12 FROM emp ;

SELECT ename,MONTHS_BETWEEN(SYSDATE,hiredate) ,TRUNC(MONTHS_BETWEEN(SYSDATE,hiredate)/12) FROM emp ;
--查询出所有已经工作了十年以上的员工
SELECT * FROM emp WHERE MONTHS_BETWEEN(SYSDATE,hiredate)/12>10 ;

2.加上指定月之后的日期:日期 add_months(日期 | 列,月数)

--计算若干月之后的日期
SELECT SYSDATE+30,ADD_MONTHS(SYSDATE,12),ADD_MONTHS(SYSDATE,100) FROM dual ;

Tips:在进行计算的时候,数字的大小是不受限制的。
复杂日期求解题:要求计算出每一位雇员到今天为止雇佣的年限,Tom 的雇佣日期是:1991 年 01月 01 日

--第一步:计算出每一位雇员到今天为止的雇佣年份,直接利用月数除 12 即可
SELECT ename,hiredate, TRUNC(MONTHS_BETWEEN(SYSDATE,hiredate)/12) year FROM emp ;

--第二步:计算月,在进行年计算的时候发现存在有小数,这些小数就包含了月
SELECT ename,hiredate, TRUNC(MONTHS_BETWEEN(SYSDATE,hiredate)/12) year ,TRUNC(MOD(MONTHS_BETWEEN(SYSDATE,hiredate),12)) months FROM emp ;

--第三步:计算天数,对于天数的计算实际上只学会了一个公式“日期 1 – 日期 2 =天数”,于是--分析日期:
--· 日期 1:应该是当前日期“SYSDATE”;
--· 日期 2:应该是已经剔除掉了经过月之后的日期。
SELECT ename,hiredate,TRUNC(MONTHS_BETWEEN(SYSDATE,hiredate)/12) year ,TRUNC(MOD(MONTHS_BETWEEN(SYSDATE,hiredate),12)) months ,TRUNC(SYSDATE -ADD_MONTHS(hiredate,MONTHS_BETWEEN(SYSDATE,hiredate))) day FROM emp ;

3.求解指定下周星期的日期:日期 next_day(日期 | 列,一周时间数)

--计算下一个星期五
SELECT NEXT_DAY(SYSDATE,'星期五') FROM dual ;

4.求出指定日期所在月的最后一天日期:日期 last_day(日期 | 列)

--求出本月最后一天日期
SELECT LAST_DAY(SYSDATE) FROM dual ;
--查询出在每个雇员受雇月的倒数第二天雇佣的雇员信息
SELECT ename,hiredate,LAST_DAY(hiredate)-2 FROM emp WHERE LAST_DAY(hiredate)-2=hiredate ;

4、转换函数

1、 转字符串数据:字符串 TO_CHAR(数字 | 日期 | 列,转换格式)
在进行转换格式设置的时候要根据不同的数据类型进行格式标记的定义:
· 日期:年(yyyy)、月(mm)、日(dd)、时(HH、HH24)、分
(mi)、秒(ss);
· 数字:一位任意数字(9)、本地货币(L)。

--将日期格式化为字符串
SELECT TO_CHAR(SYSDATE,'yyyy-mm-dd') FROM dual ; 
SELECT TO_CHAR(SYSDATE,'fmyyyy-mm-dd') FROM dual ; 
--在 Oracle 的 SYSDATE 里面是包含有时间数据的,也可以继续显示时间
SELECT TO_CHAR(SYSDATE,'yyyy-mm-dd hh24:mi:ss') FROM dual ;

那么实际上用户也可以利用 TO_CHAR()函数进行年、月、日数据的拆分。

--拆分日期数据
SELECT TO_CHAR(SYSDATE,'yyyy') year ,TO_CHAR(SYSDATE,'mm') months ,TO_CHAR(SYSDATE,'dd') day FROM dual ;
--查询出所有在 2 月份雇佣的雇员
SELECT * FROM emp WHERE TO_CHAR(hiredate,'mm')='02' ;
--使用 TO_CHAR()还可以格式化数字显示。
--格式化数字显示
SELECT TO_CHAR(234789234798,'L999,999,999,999,999') FROM dual ;

2、 转日期数据:日期 TO_DATE(字符串,转换格式)

--将字符串变为日期
SELECT '1981-09-15',TO_DATE('1981-09-15','yyyy-mm-dd') FROM dual ;

但是在 Oracle 之中提供有自动的转换方式,如果字符串按照日期的格式编写,那么可以自动由字符串变为日期。

3、 转数字类型:数字 TO_NUMBER(字符串,转换格式)

--将字符串变为数字
SELECT TO_NUMBER('1') + TO_NUMBER('2') FROM dual ;
SELECT '1' + '2' FROM dual ;

在 Oracle 里面这些自动的数据类型转换功能是非常方便的,包括日期和字符串、字符串和数字。

5、通用函数

通用函数主要是 Oracle 的特色函数,有两个:NVL()、DECODE()。
1 处理 null 数据:nvl(字符串1, 字符串2) 当字符串1为空的时候,用字符串2代替

--要求计算每一位雇员的年薪
SELECT ename,sal,comm,(sal+comm)*12 FROM emp ;
select name, nvl(math, 0) + 1 from student;

这个时候发现有一部分人是没有年薪的,而这部分人都是没有佣金的,因
为在数据库之中,null 与任何的数据进行数学计算其结果都是 null,那么如果遇到了 null 应该按照数字 0 来处理,所以就可以使用 NVL()函数。

SELECT ename,sal,comm,(sal+NVL(comm,0))*12 FROM emp ;

nvl2(字符串1,字符串2,字符串3) 如果字符串1不为空,返回字符串2,否则返回字符串3

select name, nvl2(math, 1, 0) from student

nullif(字符串1, 字符串2) 如果两个字符串相同返回null,否则返回字符串1

select nullif('a', 'a') from dual

2 多数据判断:数据 DECODE(字段 | 数据,判断数据 1,显示数据 1,判断数据 2,显示数据 2,… [默认显示])

--将所有雇员的职位数据变为中文
SELECT ename,job ,DECODE(job,'CLERK','办事员','SALESMAN','销售','MANAGER','经理','ANALYST','分析员','PRESIDENT','总裁','未处理') FROM emp ;

四、多表查询

1、多表查询基本操作

多表查询的语法如下:
SELECT [DISTINCT] * | 列名称 [别名] , 列名称 [别名] ,… FROM 数据表 [别名] , 数据 [别名] ,… [WHERE 条件(s)] [ORDER BY 字段 [ASC | DESC] , 字段 [ASC | DESC] ,…]

使用count()函数统计表中的数据记录

--统计emp表中的数据量
select count(*) from emp;
--统计dept表中的数据量
select count(*) from dept;
--emp与dept实现多表查询
select * from emp,dept;

以上实现的多表查询会出现“笛卡尔积”的查询结果,比如emp有8条记录,dept有10条记录,会产生8*10种记录,出现大量重复数据。如何消除“笛卡尔积”?唯一的方案就是使用关联字段。

--解决方案
select * from emp,dept where emp.deptno=dept.deptno;

对于复杂表名的查询可以设置别名进行查询。

--使用别名
select * from emp e,dept d where e.deptno=d.deptno;

范例:要求查询出每个雇员的编号、姓名、职位、工资、部门名称、部门位置
· 确定要使用的数据表:
|- emp 表:雇员的编号、姓名、职位、工资;
|- dept 表:部门名称、部门位置;
· 确定已知的关联条件:
|- 雇员和部门:emp.deptno = dept.deptno。

--查询每个雇员的编号,姓名,职位,工资
select e.empno,e.ename,e.job,e.sal from emp e;
--加入部门表之后进行查询
select e.empno,e.ename,e.job,e.sal,d.dname,d.loc from emp e,dept d where e.deptno=d.deptno;

范例:查询出每个雇员的编号、姓名、职位、工资、雇佣日期、工资等级
· 确定要使用的数据表:
|- emp 表:雇员的编号、姓名、职位、工资、雇佣日期;
|- salgrade 表:工资等级;
· 确定已知的关联条件:
|- 雇员与工资等级:emp.sal BETWEEN salgrade.losal AND
salgrade.hisal;

--查询雇员数据
select e.empno,e.ename,e.job,e.sal,e.hiredate from emp e;
--加入工资等级表,使用范围查询
select e.empno,e.ename,e.job,e.sal,e.hiredate,s.grade from emp e,salgrade s
where e.sal between s.losal and s.hisal ;

范例:查询每个雇员的编号、姓名、职位、雇佣日期、工资、工资等级、所在部门名称及位置
· 确定要使用的数据表:
|- emp 表:雇员的编号、姓名、职位、雇佣日期、工资;
|- salgrade 表:工资等级;
|- dept 表:部门名称及位置。
· 确定已知的关联条件:只要是消除笛卡尔积的条件之间都使用 AND 连接
|- 雇员和工资等级:emp.sal BETWEEN salgrade.losal AND
salgrade.hisal;
|- 雇员和部门:emp.deptno = dept.deptno。

--查询每个雇员的所有信息
selecte.empno,e.ename,e.job,e.sal,e.hiredate from emp;
--增加工资等级判断
select e.empno,e.ename,e.job,e.sal,e.hiredate,s.grade from emp e,salgrade s where e.sal between s.losal and s.hisal ;
--增加部门表信息
select e.empno,e.ename,e.job,e.sal,e.hiredate,s.grade,d.name,d.loc from emp e,salgrade,s,dept,d where e.sal between s.losal and s.hisal and e.deptno=d.deptno;

Tips: 关于数据量庞大的两个说明
· 说明一:在使用 NOT IN 查询的时候里面不能有 NULL
如果说现在拿到一张数据表(选择的 costs 表,这张表第一次接触)。如果是一位菜鸟使用此表,往往会直接发出如下的一行指令:
SELECT * FROM costs ;
于是此时就悲哀了,如果此表之中的数据量较大,那么最轻的后果是你查
看不方便,而严重的后果是死机(中国银行的数据库死机一秒种会有一群人被干掉的)。但 是如果是有经验的开发者,往往都先使用 COUNT()函数确定一下表中的数据量。如果数据量较大,那么使用特定的操作取出特定的几行数据,如果数据量小, 则随便操作。
SELECT COUNT(*) FROM costs ;
那么回到 NOT IN 之中不能出现 null 的问题,如果 NOT IN 里面有了 null 则表示不为空,那么有些字段的数据是永恒不能为空的,那么就表示查询全部了。如果表中的数据量庞大了,那么直接造成死机。
· 说明二:以上的查询只是消除了显示中的笛卡尔积,但是笛卡尔积仍然存
在,下面来验证一下笛卡尔积影响。
SELECT COUNT(*) FROM costs ;
SELECT COUNT(*) FROM sales ;
SELECT COUNT(*)
FROM costs c,sales s
WHERE c.prod_id=s.prod_id ;
虽然最终的操作已经成功的消除掉了显示的笛卡尔积,但是遗憾的是整个
运算执行过程非常的缓慢,所以在实际的工作之中,尤其是在你进行数据表设计的时候应该尽可能的避免查询有可能出现的笛卡尔积问题。
结论:在数据量大的时候绝对不要采用多表查询,而且就算是数据量小,
也别用多表查询。

2、连接方式

对于多表查询来讲其有两种表的连接方式:
· 内连接:也被称为等值连接,在之前的所有查询都属于内连接;
· 外连接:左外连接、右外连接、全外连接。

--增加一个没有部门的数据
insert into emp(empno,ename,job,hiredate,sal) values(1001,'公子扶苏','clerk',sysdate,108);
此时新增员工所在部门为null

范例:

select*from emp e,dept,d where e.deptno=d.deptno;

如果要想让没有部门的雇员或者是没有雇员的部门数据显示,则可以采用
如下的方式实现外连接:
· 左外连接:字段 = 字段(+),“(+)”在等号右边表示左外连接;
· 右外连接:字段(+) = 字段,“(+)”在等号左边表示右外连接;

--左外连接
select*from emp e,dept,d where e.deptno=d.deptno(+);
--右外连接
select*from emp e,dept,d where e.deptno(+)=d.deptno;

一般而言,如果现在需要查询的数据没有出现,才会使用到外连接,没有
必要去强制性的去分清楚到底是左还是右。

范例:查询每个雇员的编号、姓名、职位、领导姓名
实际上在 emp 表之中存在有一个 mgr 的字段,这个字段保存的是每一位雇员对应的领导编号。
· 确定所需要的数据表:
|- emp 表:雇员的编号、姓名、职位;
|- emp 表:领导姓名;
· 确定已知的关联字段:
|- 雇员和领导:emp.mgr = memp.empno(雇员的领导编号 = 雇员编
号)。

--查询emp表
select e.empno,e.ename,e.job from emp e ;
--查询出领导的姓名使用内连接,等值连接,有 null 的不显示
select e.empno,e.ename,e.job,m.ename from emp e ,emp m where e.mgr=m.empno;
--加入外连接控制,让所有的雇员数据显示
select e.empno,e.ename,e.job,m.ename from emp e ,emp m where e.mgr=m.empno(+);

外链接在实际操作中非常重要,以上查询是自关联查询。。。。

3、SQL:1999 语法支持

SQL 语法标准实际上一直在进行更新,从 1999 年之后对于数据表的关联查询给了一个标准的操作语法(因为“(+)”符号只有 Oracle 可以使,那么其它的数据库不支持此类符号,只能够使用 SQL:1999 语法完成),此类语法定义如下:

SELECT * | 字段 [别名]
FROM 表 [CROSS JOIN 表] [NATURAL JOIN 表]
[JOIN] [USING(字段)] [ON(条件)]
[LEFT | RIGTH | FULL | OUTER JOIN 表] ;

1、 交叉连接,其主要的目的是为了产生笛卡尔积
语法:
SELECT * | 字段 [别名] FROM 表 CROSS JOIN 表 ;
范例:

SELECT * FROM emp CROSS JOIN dept ;

2、 自然连接,主要是消除掉笛卡尔积(内连接)
语法:
SELECT * | 字段 [别名] FROM 表 NATURAL JOIN 表 ;
范例:

SELECT * FROM emp NATURAL JOIN dept ;

3、 使用 USING 子句,用户指定关联字段
语法:
SELECT * | 字段 [别名] FROM 表 JOIN 表 USING(字段);
范例:

SELECT * FROM emp JOIN dept USING(deptno) ;

4、 使用 ON 子句指定关联条件
语法:
SELECT * | 字段 [别名] FROM 表 JOIN 表 ON(条件);
范例:

SELECT * FROM emp e JOIN dept d ON(e.deptno=d.deptno) ;

5、 外连接
语法:
SELECT * | 字段 [别名] FROM 表 LEFT | RIGTH | FULL OUTER JOIN 表];
范例:

SELECT * FROM emp e LEFT OUTER JOIN dept d ON (e.deptno=d.deptno);
SELECT * FROM emp e RIGHT OUTER JOIN dept d ON (e.deptno=d.deptno);
SELECT * FROM emp e FULL OUTER JOIN dept d ON (e.deptno=d.deptno);

4、查询结果连接

在数学之中存在交集、并集、差集的概念,那么此概念在 SQL 语句之中也同样存在。在 SQL 语法之中提供了:UNION、UNION ALL、INTERSECT、MINUS 几个符号实现结合操作,这几个符合可以连接多个 SQL,使用的基本语法如下:

SELECT [DISTINCT] * | 列名称 [别名] , 列名称 [别名] ,…
FROM 数据表 [别名] , 数据表 [别名] ,…
[WHERE 条件(s)]
[ORDER BY 字段 [ASC | DESC] , 字段 [ASC | DESC] ,…]
[UNION | UNION ALL | INTERSECT | MINUS]
SELECT [DISTINCT] * | 列名称 [别名] , 列名称 [别名] ,…
FROM 数据表 [别名] , 数据表 [别名] ,…
[WHERE 条件(s)]
[ORDER BY 字段 [ASC | DESC] , 字段 [ASC | DESC] ,…]
[UNION | UNION ALL | INTERSECT | MINUS]
SELECT [DISTINCT] * | 列名称 [别名] , 列名称 [别名] ,…
FROM 数据表 [别名] , 数据表 [别名] ,…
[WHERE 条件(s)]
[ORDER BY 字段 [ASC | DESC] , 字段 [ASC | DESC] ,…]
….

范例:验证 UNION 操作,不显示重复记录

SELECT * FROM emp WHERE deptno=10
UNION
SELECT * FROM emp ;

范例:验证 UNION ALL 操作,显示所有数据,包含重复数据

SELECT * FROM emp WHERE deptno=10
UNION ALL
SELECT * FROM emp ;

范例:验证 INTERSECT 操作,返回相同的部分,交集

SELECT * FROM emp WHERE deptno=10
INTERSECT
SELECT * FROM emp ;

范例:验证 MINUS 操作,返回不同的部分,是一个差集


SELECT * FROM emp
MINUS
SELECT * FROM emp WHERE deptno=10 ;

在工作之中有部分的查询会利用以上的方式以简化查询的复杂度。

五、分组统计查询

1、组函数

常用的统计函数有:
个数count()、求和sum()、平均avg()、最大值max()、最少值min()

--统计所有雇员的人数,支付的总工资,平均工资,最高工资,最低工资
select count(*),sum(sal),avg(sal),max(sal),min(sal) from emp;
--统计公司支付的总年薪和平均薪资
select sum((sal+nvl(comm,0))*12),avg((sal+nvl(comm,0))*12) from emp;
--求出公司最早雇佣雇员的日期和最晚雇佣雇员的日期
select max(hiredate),min(hiredate) from emp;

面试题:
请解释“count(*)”、“count(字段)”、“count(distinct 字段)”的区别?

select count(*),count(ename),count(comm),count(distinct job) from emp;

输出结果

COUNT(*) COUNT(ENAME) COUNT(COMM) COUNT(DISTINCTJOB)
---------- ------------ ----------- ------------------
        19           19          13                  8

· COUNT(*):会实际的统计出表中的数据量;

· COUNT(字段):
|- 如果统计的字段上不包含有 null,那么与 COUNT(*)结果相同;
|- 如果统计字段上包含有了 null,null 不参与统计;

· COUNT(DISTINCT 字段):消除掉重复数据后的统计。

2、分组统计

实际上所谓的分组,指的某些群体具备共同的特征。现在回到 emp 表中,可以发现 job 和 deptno 两个字段之中都存在有重复的数据,那么就证明这两个字段上都可以进行分组。

SELECT [DISTINCT] * | 列名称 [别名] , 列名称 [别名] ,… | 统计函数
FROM 数据表 [别名] , 数据表 [别名] ,…
[WHERE 条件(s)]
[GROUP BY 分组字段, 分组字段, …]
[ORDER BY 字段 [ASC | DESC] , 字段 [ASC |
DESC] ,…]

--按职位分组,统计每个职位的平均工资、最高和最低工资、人数
select job,avg(sal),max(sal),min(sal),count(*) from emp group by job;
--按照部门编号跟组,统计每个部门的个数、平均工资、平均服务年限
--select deptno,count(empno),avg(sal),avg(months_between(sysdate,hiredate)/12) from emp group by deptno;

提示:关于统计查询的几个重要说明。
以上的代码只是根据基础语法实现了统计的操作,但是在整个操作之中还
会存在有三个限制:
· 限制一:统计函数单独使用时(没有 GROUP BY 子句)只能够出现
统计函数,不能够出现其它字段。
正确的代码:

SELECT COUNT(empno) FROM emp ; 

错误的代码:

SELECT COUNT(empno),ename FROM emp ;

· 限制二:使用统计查询时(存在 GROUP BY 子句),SELECT 子句之中只允许出
现统计函数与分组字段,其它的任何字段都不允许出现。
正确的代码:

SELECT deptno,COUNT(empno)
FROM emp
GROUP BY deptno ;

错误的代码:

SELECT deptno,COUNT(empno),ename
FROM emp
GROUP BY deptno ;

· 限制三:统计函数在分组之中可以嵌套使用,但是嵌套之后的统计查询之中,
SELECT 子句不允许再出现任何的字段,包括分组字段。
正确的代码:

SELECT MAX(COUNT(empno))
FROM emp
GROUP BY deptno ;

错误的代码:

SELECT deptno,MAX(COUNT(empno))
FROM emp
GROUP BY deptno ;

在整个分组操作之中,以上的三个限制可以说是最难理解的。一定要搞清
楚。
以上的操作都是针对於单张表一个字段的分组,那么通过之前给出的语法
格式可以发现,实际上分组也可以同时设置多个字段,那么这就要求这多个字段必须同时重复。
1.查询出每个部门名称、部门人数、平均工资
第一步:先不考虑分组问题,换个角度,实现查询,查询出每个部门名称、雇员编号、工资

SELECT d.dname,e.empno,e.sal FROM emp e,dept d WHERE e.deptno=d.deptno ;

第二步:在以上的查询结果之中发现了部门名称的数据是重复的,但是这个时候并不是直接针对于一张实体表进行的分组操作, 而是针对于一张临时查询结果进行分组操作,由于 GROUP BY 在 WHERE 之后执行,所以直接分组。

SELECT d.dname,COUNT(e.empno),AVG(e.sal) FROM emp e,dept d WHERE e.deptno=d.deptno GROUP BY d.dname ;

第三步:现在既然已经引入了 dept 表,那么就表示存在有 4 个部门数据,而此时只有三个部门信息,所以需要继续使用外连接进行 40 部门数据的显示。

SELECT d.dname,COUNT(e.empno),AVG(e.sal) FROM emp e,dept d WHERE e.deptno(+)=d.deptno GROUP BY d.dname ;

2.查询出每个部门的编号、名称、位置、部门人数、平均工资
· 确定所需要的数据表
|- dept 表:找到部门编号、名称、位置;
|- emp 表:统计部门人数、平均工资。
第一步:首先先不去考虑分组,先实现多表查询,查询部门编号、名称、位置、雇员编号、工资

SELECT d.deptno,d.dname,d.loc,e.empno,e.ename FROM emp e,dept d WHERE e.deptno=d.deptno ;

第二步:现在发现部门的数据三个字段都是在整体重复着,那么直接针对于三个字段实现分组

SELECT d.deptno,d.dname,d.loc,COUNT(e.empno),AVG(e.sal) FROM emp e,dept d WHERE e.deptno(+)=d.deptno GROUP BY d.deptno,d.dname,d.loc ;

3.按照职位分组,统计每个职位的平均工资,要求显示的是平均工资高于
2000 的职位信息

SELECT job,AVG(sal) FROM emp WHERE AVG(sal)>2000 GROUP BY job ;
运行结果:
WHERE AVG(sal)>2000
*
第 3 行出现错误:
ORA-00934: 此处不允许使用分组函数

但是在执行之后发现了错误的提示信息,意思是 WHERE 子句里面不能够使用统计函数,而且通过之前的分析也应该知道,WHERE 是在 GROUP BY 之前使用的, 而此时的条件明显是在 GROUP BY 之后的过滤, 所以此时只能够使用 HAVING子句完成,语法如下:

SELECT [DISTINCT] * | 列名称 [别名] , 列名称 [别名] ,… | 统计函数
FROM 数据表 [别名] , 数据表 [别名] ,…
[WHERE 条件(s)]
[GROUP BY 分组字段, 分组字段, …]
[HAVING 分组后过滤]
[ORDER BY 字段 [ASC | DESC] , 字段 [ASC |
DESC] ,…]

--使用having修改程序
select job,avg(sal) from emp group by job having avg(sal)>2000;

关于 WHERE 和 HAVING 的区别?

· WHERE 子句:是在分组前使用,而且不能够使用统计函数进行验证,经过 WHERE 筛选后的数据才可以分组;

· HAVING 子句:必须结合 GROUP BY 子句一起出现,是在分组后的过滤,可以使用统计函数。

思考题一:统计公司每个工资等级的人数、平均工资
· 确定要使用的数据表:
|- salgrade 表:工资等级;
|- emp 表:统计人数、平均工资;
· 确定已知的关联字段:
|- 工资与工资等级:emp.sal BETWEEN salgrade.losal AND
salgrade.hisal。
第一步:查询出工资等级,以及每个雇员的编号、工资,直接实现多表查询

SELECT s.grade,e.empno,e.sal FROM emp e,salgrade s WHERE e.sal BETWEEN s.losal AND s.hisal ;

第二步:针对于临时数据表之中的数据进行分组

SELECT s.grade,COUNT(e.empno),AVG(e.sal) FROM emp e,salgrade s WHERE e.sal BETWEEN s.losal AND s.hisal GROUP BY s.grade ;

思考题:统计出公司领取佣金和不领取佣金的人数、平均工资
现在很容易联想到,直接针对于 comm 字段分组,于是写出了如下的语句。

SELECT comm,COUNT(empno),AVG(sal) FROM emp GROUP BY comm ;

所以此时这个查询结果发现并不理想,那么不如换个思路。
第一步:统计出所有领取佣金的人数、平均工资

SELECT '领取佣金',COUNT(empno),AVG(sal) FROM emp WHERE comm IS NOT NULL ;

第二步:统计出所有不领取佣金的人数、平均工资

SELECT '不领取佣金',COUNT(empno),AVG(sal) FROM emp WHERE comm IS NULL ;

第三步:以上两个查询返回的结构完全相同,那么就将其变为一个结果,使用UNION

SELECT '领取佣金',COUNT(empno),AVG(sal) FROM emp WHERE comm IS NOT NULL
UNION
SELECT '不领取佣金',COUNT(empno),AVG(sal) FROM emp WHERE comm IS NULL ;

很多时候都可以直接进行分组,但是在进行分组的时候,必须保证数据列
上有重复,但是如果是针对于一些条件,那么只能够利用以上的方式完成。

发布了46 篇原创文章 · 获赞 48 · 访问量 17万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章