Oracle学习

文章目录

  • Oracle学习
    • 安装
      • 安装步骤
      • 服务配置
      • 测试
    • 卸载
    • 学习
      • sqlplus命令
      • SQL简介/数据表分析
        • 使用scott用户查看存在的表:
      • 查询
        • 常见查询操作
        • DISTINCT使用
        • WHERE限定查询:
        • 范围运算:
        • 空判断:
        • IN操作符:
        • 模糊查询:
        • 查询排序:
      • 单行函数
        • 字符串函数
        • 数值函数
        • 日期函数
        • 转换函数
        • 通用函数
      • 多表查询
        • 笛卡尔积
        • 多表查询的效率问题
        • 多表查询案例
        • 表的连接
        • 标准SQL语法
        • 集合
      • 分组统计查询
        • 统计函数
        • 分组统计
        • 多表查询与分组统计
        • Having语句
        • 分组案例
      • 子查询
        • WHERE使用子查询
        • HAVING子句使用子查询
        • SELECT子句使用子查询
        • FROM子句中的子查询
      • 复杂查询案例
      • 数据更新
        • 增加数据
        • 修改
        • 删除数据
      • 事务处理
      • 数据伪列
        • ROWNUM
        • ROWID
      • 表的创建与管理
        • 常用的数据类型
        • 创建数据表
        • 复制表
        • 截断表
        • 表重命名
        • 数据表的删除、闪回技术
        • 修改表结构
      • 约束管理
        • 非空约束
        • 唯一约束
        • 主键约束
        • 检查约束
        • 外键约束
        • 修改约束
      • DML、DDL实战
        • 数据查询实战
        • 数据更新实战
      • 常用数据对象
        • 定义序列
      • 视图
        • 视图的创建和使用
      • 其他数据库对象
        • 同义词
        • 索引
        • 用户管理
      • 数据库的备份
        • 数据导出:
        • 数据库的冷备份
      • 数据库设计
        • 第一范式
        • 第二个范式
        • 第三范式
        • 范式的解释

Oracle学习

安装

这里安装的是11gR2,推荐win7安装,不推荐使用linux,

安装步骤

  1. 配置安全更新:去勾选接受安全更新邮件,电子邮件
  2. 安装选项:创建和配置数据库
  3. 系统类:服务器类
  4. 网络安全选项: 单实例数据安装
  5. 安装类型:选高级配置(配置编码,同时需要样本数据)
  6. 产品语言默认
  7. 数据库版本:企业版
  8. 配置类型默认
  9. 数据库标识改成mldn(SID是服务ID,用于网络连接使用,一般数据库名称和SID一致)
  10. 配置选项:字符集改成Unicode;示例方案勾选创建具有示例方案的数据库
  11. 管理选项默认
  12. 数据库存储默认
  13. 备份和恢复默认
  14. 方案口令:选对所有的账户使用相同的口令
  15. 先决条件检查默认,点击完成
  16. 进度完成后出现配置助手,点击口令管理,需要配置4个用户,配置完后完成
    1. 超级管理员: sys/change_on_install
    2. 普通管理员: system/manager
    3. 普通用户:scott/tiger
    4. 大数据用户:sh/sh

服务配置

打开计算的管理,查看服务,建议改成手动启动服务。这些服务中有两个关键的服务:

OracleOraDb11g_home1TNSListener:留给客户端访问本机时使用

OracleServiceMLDN:oracle数据实例服务。oracle支持通过Database Configuration Assistant配置多个实例数据库,每一个数据库都会有一个OracleServiceSID服务

测试

通过sqlpuls 命令测试:

SQL*Plus: Release 11.2.0.1.0 Production on 星期一 2月 18 14:41:25 2019

Copyright © 1982, 2010, Oracle. All rights reserved.

请输入用户名: scott
输入口令:

连接到:
Oracle Database 11g Enterprise Edition Release 11.2.0.1.0 - 64bit Production
With the Partitioning, OLAP, Data Mining and Real Application Testing options

SQL>

也可以通过cmd命令进入sqlplus:

C:\Users\g00425293>sqlplus scott/tiger

SQL*Plus: Release 11.2.0.1.0 Production on 星期一 2月 18 14:43:45 2019

Copyright © 1982, 2010, Oracle. All rights reserved.

连接到:
Oracle Database 11g Enterprise Edition Release 11.2.0.1.0 - 64bit Production
With the Partitioning, OLAP, Data Mining and Real Application Testing options

SQL>

卸载

找到安装目录product下的目录:

product\11.2.0\dbhome_1

单击deinstall文件进行卸载,按照提示进行操作

学习

sqlplus命令

sqlplus /nolog; #不使用用户登录
SET LINESIZE 300; # 设置每行显示的长度
SET PAGESIZE 30; # 分页显示的大小
COL job FOR A10; # 设置列的长度,A后面是每列的长度
ed hello; # 在用户目录下创建一个hello.sql文件, 可以编写sql,然后通过@hello来执行该文件中的命令,注意的是利用@命令也可以调用磁盘中的文件(@e:data),默认后缀是.sql
show user; #显示当前用户
CONN 用户名/密码; #切换用户
sqlplus sys/change_on_install AS SYSDBA; #sys登录需要添加AS描述,同样在nolog情况下,需要使用CONN sys/change_on_install AS SYSDBA进行连接

回退操作:

//事务的回退,当前多个操作都会回退
rollback

需要注意的是,emp表在sys用户下查询不出来,如果访问其他用户的表,需要添加Scheme:

SELECT * FORM scott.emp;

sqlplus充分考虑用户可能使用到系统命令,所以提供一个HOST指令,即HOST之后调用本机的程序执行

HOST COPY e:\data.sql e:\hello.sql;#在sqlplus中拷贝文件

SQL简介/数据表分析

DML:数组操作语言,数据的更新与查询

DDL:数据定义语言,数据表,索引,同义词,约束等

DCL:数据控制语言,权限控制

使用scott用户查看存在的表:

CONN scott/tiger
SET LINESIZE 300;
SET PAGESIZE 30;
SELECT * FROM tab;  # 查看表,总共有4张
DESC dept; # 查看部门表的列
SELECT * FROM dept; # 查看部门的数据

总共有四个表,分别是:

TNAME                TABTYPE
-------------------- --------------
BONUS                TABLE
DEPT                 TABLE
EMP                  TABLE
SALGRADE             TABLE

各个表的结构

DEPT:

名称 是否为空 类型 描述
DEPTINO NOT NULL NUMBER(2) 部门编号
DNAME VARCHAR(14) 部门名称
LOC VARCHAR(13) 部门位置

表里面的数据:

DEPTNO DNAME                        LOC
------ ---------------------------- --------------------------
    10 ACCOUNTING                   NEW YORK
    20 RESEARCH                     DALLAS
    30 SALES                        CHICAGO
    40 OPERATIONS                   BOSTON

EMP表:

名称 是否为空 类型 描述
EMPNO NOT NULL NUMBER(4) 雇员编号
ENAME VARCHAR(10) 雇员名称
JOB VARCHER(9) 工作
MGR NUMBER(4) 领导编号
HIREDATE DATE 雇佣
SAL NUMBER(7,2) 工资
COMM NUMBER(7,2) 佣金,只有销售人员有
DETPNO NUMBER(2) 部门编号

该表数据:

EMPNO ENAME JOB        MGR HIREDATE     SAL   COMM  DEPTNO
----- -------------------- ---------------- ------ -------
 7369 SMITH CLERK     7902 17-12月-80  800          20
 7499 ALLEN SALESMAN  7698 20-2月 -81 1600    300   30
 7521 WARD  SALESMAN  7698 22-2月 -81 1250    500   30
 7566 JONES MANAGER   7839 02-4月 -81 2975          20
 7654 MARTINSALESMAN  7698 28-9月 -81 1250   1400   30
 7698 BLAKE MANAGER   7839 01-5月 -81 2850          30
 7782 CLARK MANAGER   7839 09-6月 -81 2450          10
 7788 SCOTT ANALYST   7566 19-4月 -87 3000          20
 7839 KING  PRESIDENT      17-11月-81 5000          10
 7844 TURNERSALESMAN  7698 08-9月 -81 1500      0   30
 7876 ADAMS CLERK     7788 23-5月 -87 1100          20
 7900 JAMES CLERK     7698 03-12月-81  950          30
 7902 FORD  ANALYST   7566 03-12月-81 3000          20
 7934 MILLERCLERK     7782 23-1月 -82 1300          10

SALAGRADE: 此表字段都可为空

名称 类型 描述
GRADE NUMBER 等级
LOSAL NUMBER 此等级的最低工资
HISAL NUMBER 此等级的最高工资

表中的数据:

 GRADE      LOSAL      HISAL
------ ---------- ----------
     1        700       1200
     2       1201       1400
     3       1401       2000
     4       2001       3000
     5       3001       9999

BONUS: 该表没有数据,且表字段可为空

名称 类型 描述
ENAME VARCHAR(10) 雇员姓名
JOB VARCHAR(9) 雇员职位
SAL NUMBER 工资
COMM NUMBER 佣金

查询

常见查询操作

//注意先执行FROM再执行SELECT
SELECT [DISTINCT] * | 列[别名],列[别名],... FROM 表名称[别名];
//简单查询之中也支持四则运算
SELECT empno,ename,sal*12 FROM emp;
//名称别名,方便显示,也支持中文
SELECT empno,ename,sal*12 income FROM emp;
//查询支持数据的连接操作,显示的列名称就是EMPNO||ENAME,内容是两个列连接后的内容
SELECT empno||ename FROM emp;
//显示更人性化的连接
SELECT 'number:  '|| empno || ', name: ' || ename FROM emp;
//如果是数组,则直接写
SELECT nname||1 FROM emp;

DISTINCT使用

SELECT DISTINCT job FROM emp;

WHERE限定查询:

//关系运算:>,=,<
SELECT * FROM emp WHERE sal<1200;
//=号也可以用于字符串,注意区分大小写
SELECT * FROM emp WHERE ENAME='SMITH';
范围运算:BETWEEN   AND
空判断:IS NULL, IS NOT NULL
IN判断:IN, NOT IN, exists(复杂查询)
模糊查询:LIKE,NOT LIKE
//逻辑运算:AND,OR,NOT
SELECT * FROM emp WHERE sal<1200;
SELECT * FROM emp WHERE job<>'CLERK' AND sal<3000;
SELECT * FROM emp WHERE job='CLERK' OR sal<1200;
SELECT * FROM emp WHERE NOT sal>1200;

范围运算:

BETWEEN AND是一个运算符,效率相对更高

SELECT * FROM emp WHERE sal BETWEEN 1500 AND 3000;

oracle的所有运算符都不受数据类型的控制,因此也用于其他类型(日期,等级等):

SELECT * FROM emp WHERE hiredate BETWEEN '01-1月-81' AND '31-12月-1981';

空判断:

null表示位置的数据,一个数字与null进行计算,仍然是null

SELECT NULL+1 FROM emp;//select出来的东西为空,且行数和该表的行数一致

在某些数据列上允许有null值的,但是对于null不能够使用关系运算,比如使用=判断,应该要使用IS NULL 或者IS NOT NULL来进行判断:

SELECT * FROM emp WHERE comm IS NOT NULL;

IN操作符:

需要注意的是,如果使用NOT IN 的范围中有null,则返回的结果为空

SELECT * FROM emp WHERE empno in (7369,7566,7788);
SELECT * FROM emp WHERE empno NOT IN (7369,7566,7788,NULL);

模糊查询:

LIKE,可以在任意数据类型上使用

_ :匹配任意的一个符号
% :匹配一个或多个字符
SELECT * FROM emp WHERE ename LIKE 'A%';

查询排序:

语法ORDER BY 字段 [ASC|DESC], 排序字段 [ASC|DESC] 默认是升序

这里我们先说明一下关键字的执行顺序

FROM

WHERE

SELECT

ORDER BY

因此ORDER BY 可以支持SELECT定义的别名

SELECT * FROM emp ORDER BY sal DESC; //按照工资排序
SELECT * FROM emp ORDER BY hiredate; //按照雇佣日期排序
SELECT * FROM emp ORDER BY sal DESC,hiredate; //组合排序

单行函数

使用结构:

返回值 函数名称(列|数据)

字符串函数

UPPER(),LOWER(),REPLACE(),INITCAP(),LENGTH(),SUBSTR()

为了验证,可以使用oracle的dual表

SELECT LOWER('Hello') FROM dual;
SELECT LOWER('Hello'),UPPER('hello') FROM dual;

oracle支持替代变量,效果如下:

SQL> SELECTR * FROM emp WHERE ename='&inputname';
SP2-0734: 未知的命令开头 "SELECTR * ..." - 忽略了剩余的行。
SQL> SELECT * FROM emp WHERE ename='&inputname';
输入 inputname 的值:  JAMES
原值    1: SELECT * FROM emp WHERE ename='&inputname'
新值    1: SELECT * FROM emp WHERE ename='JAMES'

因此可以利用UPPER来对数据进行处理:

SELECT * FROM emp WHERE ename=UPPER('&inputname');

首字母大写

SELECT INITCAP('helloworld') from dual;

计算长度:

SELECT ename,LENGTH(ename) FROM emp;

字符串替换:利用该函数可以消除空格数据

SELECT REPLACE(ename,UPPER('a'),'__') FROM emp;

字符串截取:有两种用法,注意下标识从1开始,即使从0开始,也是从1开始。

注意索引也支持负数,表示截取后几位,需要注意的是,只有oracle支持负数索引

SUBSTR(列|数据,开始点)
SUBSTR(列|数据,开始点,长度)

SELECT SUBSTR('HELLOWORLDNIHAO',11) FROM dual;
SELECT SUBSTR('HELLOWORLDNIHAO',6,5) FROM dual;

数值函数

ROUND(),TRUNC(),MOD()

ROUND(列|数字 [,保留小数位])
保留的小数位负数,表示不足的数据抹去置为0,也即是去掉零头

SQL> SELECT ROUND(1.23),ROUND(1555.885,-2),ROUND(-12.12231),ROUND(-12.76564,2) FROM dual;

ROUND(1.23) ROUND(1555.8852122,-2) ROUND(-12.12231) ROUND(-12.76564,2)
----------- ---------------------- ---------------- ------------------
          1                   1600              -12             -12.77
TRUNC(列|数字 [,小数位])
SELECT TRUNC(1.23),TRUNC(1555.885,-2),TRUNC(-12.12231),TRUNC(-12.76564,2) FROM dual;

SQL> SELECT TRUNC(1.23),TRUNC(1555.885,-2),TRUNC(-12.12231),TRUNC(-12.76564,2) FROM dual;

TRUNC(1.23) TRUNC(1555.885,-2) TRUNC(-12.12231) TRUNC(-12.76564,2)
----------- ------------------ ---------------- ------------------
          1               1500              -12             -12.76
MOD(列1|数字1,列2|数字2)

SELECT MOD(10,3) FROM dual;

日期函数

日期函数式oracle的特色,Oracle专门提供一个数据伪列,这个列不存在于表中,但是支持查询:SYSDATE

SELECT hiredate, SYSDATE FROM emp;


SQL> SELECT SYSDATE,SYSTIMESTAMP FROM dual;

SYSDATE        SYSTIMESTAMP
-------------- ---------------------------------------
20-2月 -19     20-2月 -19 11.05.38.101000 下午 +08:00

日期提供的计算模式:

日期+数字=日期 若干天之后
日期-数字=日期 若干天之前
日期-日期=数字 天数差

SQL> SELECT SYSDATE+10,SYSDATE+120 FROM dual;

SYSDATE+10     SYSDATE+120
-------------- --------------
02-3月 -19     20-6月 -19

使用日期函数进行计算:

MONTHS_BETWEEN,ADD_MONTHS,LAST_DAY,NEXT_DAY

计算两个日期间的月数
SQL> SELECT ename,hiredate,MONTHS_BETWEEN(SYSDATE,hiredate) FROM emp

ENAME                HIREDATE       MONTHS_BETWEEN(SYSDATE,HIREDATE)
-------------------- -------------- --------------------------------
SMITH                17-12月-80                           458.128792
ALLEN                20-2月 -81                                  456
...

计算大致年限:
SQL> SELECT ename,hiredate,MONTHS_BETWEEN(SYSDATE,hiredate)/12 years FROM emp;

ENAME                HIREDATE            YEARS
-------------------- -------------- ----------
SMITH                17-12月-80     38.1774026
ALLEN                20-2月 -81             38
WARD                 22-2月 -81     37.9972951

增加如果月数
SELECT ADD_MONTHS(SYSDATE,4) FROM dual;

计算指定日期所在月的最后一天
SELECT LAST_DAY(SYSDATE) FROM dual;
//找出雇佣天数是倒数第二天
SELECT ename,hiredate,LAST_DAY(hiredate),LAST_DAY(hiredate)-2 FROM emp WHERE LAST_DAY(hiredate)-2=hiredate;

//计算下一个周二
SELECT NEXT_DAY(SYSDATE,'星期二') FROM dual;
//计算雇员到现在跨越的年月日
SELECT empno,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; 

转换函数

可以实现各数据类型间的转换:TO_CHAR(),TO_DATE(),TO_NUMBER()

SELECT TO_CHAR(SYSDATE,'yyyy-mm-dd') output FROM dual;

SQL> SELECT TO_CHAR(SYSDATE,'yyyy-mm-dd hh24:mi:ss') output FROM dual;

OUTPUT
--------------------------------------
2019-02-21 21:52:07

SELECT * FROM emp WHERE TO_CHAR(hiredate,'mm')='02';
//ORACLE提供类型的自动转换,因为可以去掉引号
SELECT * FROM emp WHERE TO_CHAR(hiredate,'mm')=2;

//这里的999表示格式
SELECT TO_CHAR(1212121212,'999,999,999,999') FROM dual;
//添加金钱符号
SQL> SELECT TO_CHAR(1212121212,'L999,999,999,999') money FROM dual;
MONEY
----------------------------------------------------
          ¥1,212,121,212

//转日期函数
SQL> SELECT TO_DATE('1191-02-28','yyyy-mm-dd') FROM dual;
TO_DATE('1191-
--------------
28-2月 -91

//转数字函数
SELECT TO_NUMBER('1')+TO_NUMBER('2') FROM dual;
//实际不用显式转换
SELECT '1'+'2' FROM dual;

通用函数

NVL(),DECODE()

NVL():处理null,属于oracle自己的函数
//先看一个问题:在comm为空的行,计算出来的结果也是空,这显然不是我们想的
SELECT ename, (sal+comm)*12 FROM emp;
//正确的写法,这里的0表示默认值
SELECT ename, (sal+NVL(comm,0))*12 FROM emp;

//多数值判断:根据不同的结果对输出进行转换,注意最后一个是默认值
SELECT ename,job,DECODE(job,'CLERK','办事员','SALESMAN','销售','暂无信息') FROM emp;

多表查询

笛卡尔积

首先我们简单实现dept和emp的多表查询

SELECT * FROM emp,dept;

显式的结果会查询出56条记录,每个雇员出现了4次,4是dept表的数量,实际查出来的是笛卡尔积,如果要消除一些积,必须要有关联字段,没有关联关系没法多表查询

SELECT *
FROM emp, dept
WHERE emp.deptno = dept.deptno;

可以引入别名方便书写:

SELECT *
FROM emp e, dept d
WHERE e.deptno = d.deptno;

多表查询的效率问题

继续来看下关联查询,我们可以登录下大数据用户sh的账号,看下COSTS和SALES表的数据数量如下,这两张表是通过PROD_ID来关联的

SQL> SELECT COUNT(*) FROM COSTS;

  COUNT(*)
----------
     82112

SQL> SELECT COUNT(*) FROM SALES;

  COUNT(*)
----------
    918843

然后我们使用关联查询:

SQL> SELECT COUNT(*) FROM COSTS c, SALES s WHERE c.prod_id = s.prod_id;

  COUNT(*)
----------
1165337550

增加条件虽然消除了显式的笛卡尔积,但是它是一只存在的,永远无法消除,如果数据量大,多表查询数据会比变慢

多表查询案例

查询雇员的部门和基本信息

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

查出雇员的编号,姓名,工资等级,基本工资等:

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

表的连接

实际上多表查询消除笛卡尔积主要是依靠的连接模式处理的

内连接:只有满足条件的数据才会显示,不满足不显示,前面的多表查询实际就是内连接

外连接:分为三种,左外连接,右外连接,全外连接

为了看下内连接和连接的区别,我们首先插入一条数据:

INSERT INTO emp(empno,ename,deptno) VALUES(8989,'HELLO',null);

这样我们就有了一条没有部门的员工,同时,我们可以查看下emp表的数据,可以发现40部门是没有员工的,因此也存在一条没有员工的部门。

内连接的效果

内连接总共有14条数据,既没有HELLO,也没有出现40部门

SQL> SELECT e.empno,e.ename,d.deptno,d.dname
  2  FROM emp e, dept d
  3  WHERE e.deptno = d.deptno;

     EMPNO ENAME                    DEPTNO DNAME
---------- -------------------- ---------- ----------
      7839 KING                         10 ACCOUNTING
      7934 MILLER                       10 ACCOUNTING
      7782 CLARK                        10 ACCOUNTING
      7369 SMITH                        20 RESEARCH
      7902 FORD                         20 RESEARCH
      7876 ADAMS                        20 RESEARCH
      7788 SCOTT                        20 RESEARCH
      7566 JONES                        20 RESEARCH
      7900 JAMES                        30 SALES
      7499 ALLEN                        30 SALES
      7698 BLAKE                        30 SALES
      7654 MARTIN                       30 SALES
      7844 TURNER                       30 SALES
      7521 WARD                         30 SALES

左外连接

这里在右侧使用了+号,这是ORACLE特有的符号,用来表示左外连接

可以看到,HELLO显式出来了

SQL> SELECT e.empno,e.ename,d.deptno,d.dname
  2  FROM emp e, dept d
  3  WHERE e.deptno=d.deptno(+);

     EMPNO ENAME                    DEPTNO DNAME
---------- -------------------- ---------- ----------
      7934 MILLER                       10 ACCOUNTING
      7839 KING                         10 ACCOUNTING
      7782 CLARK                        10 ACCOUNTING
      7902 FORD                         20 RESEARCH
      7876 ADAMS                        20 RESEARCH
      7788 SCOTT                        20 RESEARCH
      7566 JONES                        20 RESEARCH
      7369 SMITH                        20 RESEARCH
      7900 JAMES                        30 SALES
      7844 TURNER                       30 SALES
      7698 BLAKE                        30 SALES
      7654 MARTIN                       30 SALES
      7521 WARD                         30 SALES
      7499 ALLEN                        30 SALES
      8989 HELLO

右外连接

可以看到,40的部门也显示了

SQL> SELECT e.empno,e.ename,d.deptno,d.dname
  2  FROM emp e, dept d
  3  WHERE e.deptno(+)=d.deptno;

     EMPNO ENAME                    DEPTNO DNAME
---------- -------------------- ---------- ----------
      7839 KING                         10 ACCOUNTING
      7934 MILLER                       10 ACCOUNTING
      7782 CLARK                        10 ACCOUNTING
      7369 SMITH                        20 RESEARCH
      7902 FORD                         20 RESEARCH
      7876 ADAMS                        20 RESEARCH
      7788 SCOTT                        20 RESEARCH
      7566 JONES                        20 RESEARCH
      7900 JAMES                        30 SALES
      7499 ALLEN                        30 SALES
      7698 BLAKE                        30 SALES
      7654 MARTIN                       30 SALES
      7844 TURNER                       30 SALES
      7521 WARD                         30 SALES
                                        40 OPERATIONS

为什么要使用外连接

假设我们要查询雇员的以及雇员领导,我们可以写成如下的形式:

但是我们发现这里会出现显式补全的问题,这时候就需要外连接来帮忙

SQL> SELECT e.empno,e.ename,e.job,m.ename,m.job
  2  FROM emp e, emp m
  3  WHERE e.mgr=m.empno;

     EMPNO ENAME                JOB                ENAME                JOB
---------- -------------------- ------------------ -------------------- ---------
      7902 FORD                 ANALYST            JONES                MANAGER
      7788 SCOTT                ANALYST            JONES                MANAGER
      7844 TURNER               SALESMAN           BLAKE                MANAGER
      7499 ALLEN                SALESMAN           BLAKE                MANAGER
      7521 WARD                 SALESMAN           BLAKE                MANAGER
      7900 JAMES                CLERK              BLAKE                MANAGER
      7654 MARTIN               SALESMAN           BLAKE                MANAGER
      7934 MILLER               CLERK              CLARK                MANAGER
      7876 ADAMS                CLERK              SCOTT                ANALYST
      7698 BLAKE                MANAGER            KING                 PRESIDENT
      7566 JONES                MANAGER            KING                 PRESIDENT
      7782 CLARK                MANAGER            KING                 PRESIDENT
      7369 SMITH                CLERK              FORD                 ANALYST
      
 SQL> SELECT e.empno,e.ename,e.job,m.ename,m.job
  2  FROM emp e, emp m
  3  WHERE e.mgr=m.empno(+);

     EMPNO ENAME                JOB                ENAME                JOB
---------- -------------------- ------------------ -------------------- ---------
      7902 FORD                 ANALYST            JONES                MANAGER
      7788 SCOTT                ANALYST            JONES                MANAGER
      7900 JAMES                CLERK              BLAKE                MANAGER
      7844 TURNER               SALESMAN           BLAKE                MANAGER
      7654 MARTIN               SALESMAN           BLAKE                MANAGER
      7521 WARD                 SALESMAN           BLAKE                MANAGER
      7499 ALLEN                SALESMAN           BLAKE                MANAGER
      7934 MILLER               CLERK              CLARK                MANAGER
      7876 ADAMS                CLERK              SCOTT                ANALYST
      7782 CLARK                MANAGER            KING                 PRESIDENT
      7698 BLAKE                MANAGER            KING                 PRESIDENT
      7566 JONES                MANAGER            KING                 PRESIDENT
      7369 SMITH                CLERK              FORD                 ANALYST
      7839 KING                 PRESIDENT
      8989 HELLO

标准SQL语法

SELECT *
FROM 表1 [别名]
	[CROSS JOIN 表2 [别名]]
	[NATURAL JOIN 表2 [别名]]、
	[JOIN 表2 [别名] ON (条件)|USING(关联字段)]
	[LEFT|RIGHT|FULL OUTER JOIN 表2 ON(条件)]

使用:

SELECT * FROM emp CROSS JOIN dept; //交叉连接,产生笛卡尔积
SELECT * FROM emp NATURAL JOIN dept; //自然连接,实际就是内连接,需要连接的字段相同,因此有了以下语句
SELECT * FROM emp JOIN dept USING(deptno); //设置关联字段
SELECT * FROM emp e JOIN dept d ON(e.deptno=d.deptno); //设置关联条件
//左外连接和右外连接
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);

集合

多个查询进行结合操作

UNION|UNION ALL|INTERSECT|MINUS

注意UNION需要使用要有相同的表结构

SELECT * FROM EMP UNION SELECT * FROM EMP WHERE deptno=10;//合并结果,排除了重复的数据,还是15条
//未排除重复数据,有18条
//返回交集,实际就是右面查询的结果
SELECT * FROM EMP UNION ALL SELECT * FROM EMP WHERE deptno=10;
SELECT * FROM EMP INTERSECT SELECT * FROM EMP WHERE deptno=10;
//排除右面的结果,左边的数据比右面的数据要多
SELECT * FROM EMP MINUS SELECT * FROM EMP WHERE deptno=10;

分组统计查询

统计函数

COUNT():统计个数

SUM():求和

AVG():平均值

MIN():最小值

MAX():最大值

使用:

SELECT COUNT(*) 人数,AVG(sal) 员工平均工资,SUM(sal) 每月总支出,MAX(sal) 最高工资,MIN(sal) 最低工资 
FROM emp;

 人数 员工平均工资 每月总支出   最高工资   最低工资
----- ------------ ---------- ---------- ----------
   15   2073.21429      29025       5000        800

这些函数可以互相嵌套:

//计算最早和最晚雇佣的员工
SELECT MAX(hiredate) 最晚,MIN(hiredate) 最早 FROM emp;

最晚           最早
-------------- ----------
23-5月 -87     17-12月-80

以上几个函数,在表中没有数据的时候,只有COUNT函数会返回结果,其他会返回null。

COUNT的三种使用形式:

COUNT(*):可以准去返回全部记录数
COUNT(字段):统计不为null的全部记录数
COUNT(DISTINCT 字段):消除重复数据后的结果

SELECT COUNT(*),COUNT(empno) FROM emp;//没有区别
  COUNT(*) COUNT(EMPNO)
---------- ------------
        15           15
SELECT COUNT(*),COUNT(empno),COUNT(comm) FROM emp;//comm为空的不统计
 COUNT(*) COUNT(EMPNO) COUNT(COMM)
--------- ------------ -----------
       15           15           4
SELECT COUNT(DISTINCT job) FROM emp;//返回去重的结果

COUNT(DISTINCTJOB)
------------------
                 5

分组统计

分组的前提是存在重复,但是允许一行记录进行分组

GROUP BY

执行顺序:

FROM

WHERE

GROUP BY

SELECT

ORDER BY

//根据部门编号分组,查询每个部门的编号,人数,平均工资
SELECT deptno,COUNT(*),AVG(sal)
FROM emp
GROUP BY deptno;

使用GROUP BY进行分组的时候有一些约定:

如果查询不使用GROUP BY子句,那么SELECT子句中出现统计函数,其他任何字段不允许出现

//正确的代码
SELECT COUNT(*) FROM emp;
//错误的代码
SELECT empno,COUNT(*) FROM emp;

如果使用了GROUP BY子句,那么SELECT子句中只允许出现分组字段、统计函数,其他字段不允许出现

SELECT ename,empno,COUNT(*) FROM emp GROUP BY job;//出错

统计函数允许嵌套,但是嵌套后的SELECT子句只允许出现嵌套函数,不允许出现其他字段,包括分组字段

SELECT deptno,MAX(AVG(sal)) FROM emp GROUP BY deptno;

多表查询与分组统计

查询出每个部门的名称、部门人数、平均工资

首先我们先写一下简单的查询:

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

DNAME                             EMPNO        SAL
---------------------------- ---------- ----------
ACCOUNTING                         7782       2450
ACCOUNTING                         7839       5000
ACCOUNTING                         7934       1300
RESEARCH                           7566       2975
RESEARCH                           7902       3000
RESEARCH                           7876       1100
RESEARCH                           7369        800
RESEARCH                           7788       3000
SALES                              7521       1250
SALES                              7844       1500
SALES                              7499       1600
SALES                              7900        950
SALES                              7698       2850
SALES                              7654       1250

可以看到了名称重复了,这时候可以进行分组,注意这里SELECT中只能包含分组名称以及统计函数

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

DNAME                        COUNT(E.EMPNO) AVG(E.SAL)
---------------------------- -------------- ----------
ACCOUNTING                                3 2916.66667
RESEARCH                                  5       2175
SALES                                     6 1566.66667

但是我们可以看到上面显示的结果只有三个名称,但我们需要全部的部门名称,因此可以改成:

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

DNAME                        COUNT(E.EMPNO) AVG(E.SAL)
---------------------------- -------------- ----------
ACCOUNTING                                3 2916.66667
OPERATIONS                                0
RESEARCH                                  5       2175
SALES                                     6 1566.66667

查询每个部门的编号、名称、位置、部门人数、平均工资

这里使用了多个字段的GROUP BY(需要多个字段都重复)

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;
//需要注意的是,SELECT中不能出现 不在GROUP BY的字段
DEPTNO DNAME                        LOC                        COUNT(E.EMPNO) AVG(E.SAL)
------ ---------------------------- -------------------------- -------------- ----------
    20 RESEARCH                     DALLAS                                  5       2175
    40 OPERATIONS                   BOSTON                                  0
    10 ACCOUNTING                   NEW YORK                                3 2916.66667
    30 SALES                        CHICAGO                                 6 1566.66667

Having语句

查询每个职位的名称,职位的平均工资,但是要求显示职位的平均工资高于2000职位

首先是常见的错误案例

SELECT job,AVG(sal)
FROM emp
WHERE AVG(sal)>2000
GROUP BY job;//WHERE子句上不能使用分组函数,原因是,WHERE在GROUP BY之前运行

因此必须说过另外的子句完成,即HAVING,首先明确HAVING的执行顺序

HAVING在GROUP BY之后执行

SELECT job,AVG(sal)
FROM emp
GROUP BY job
HAVING AVG(sal)>2000;

JOB                  AVG(SAL)
------------------ ----------
PRESIDENT                5000
MANAGER            2758.33333
ANALYST                  3000

WHERE子句是在GROUP BY分组之前进行筛选,指的是选出哪些可以参与分组的数据,并不允许使用统计函数

HAVING语句在GROUP BY分组之后记性筛选

分组案例

显示非销售人员的工作名称以及从事同一工作的雇员的月工资的总和,并且满足要求从事同一工作雇员工资的合计大于5000,显示的结果按照月工资合计的升序排列

//首先找到非销售的雇员
//需要注意的是ORDER BY是可以使用别名的
SELECT e.job,SUM(e.sal) sum
FROM emp e
WHERE e.job<>'SALESMAN'
GROUP BY e.job
HAVING SUM(e.sal)>5000
ORDER BY sum ASC;

JOB                SUM(E.SAL)
------------------ ----------
ANALYST                  6000
MANAGER                  8275

统计所有销售人员和不领取佣金的人数、平均工资

首先你可能想到的写法:

SELECT comm,COUNT(*),AVG(SAL)
FROM emp e
GROUP BY comm;

COMM   COUNT(*)   AVG(SAL)
---- ---------- ----------
             10     2342.5
1400          1       1250
 500          1       1250
 300          1       1600
   0          1       1500

这里的问题在于会把每一个种子值当做一个分组,所以此时不用GROUP BY

//查询领取佣金的
SELECT COUNT(*),AVG(sal)
FROM emp
WHERE comm IS NOT NULL;

//查询不领取佣金的
SELECT COUNT(*),AVG(sal)
FROM emp
WHERE comm IS NULL;

//可以看出来两者的结构一直,因此直接使用UNION
SELECT COUNT(*),AVG(sal) FROM emp WHERE comm IS NOT NULL UNION
SELECT COUNT(*),AVG(sal) FROM emp WHERE comm IS NULL;

子查询

前面说的基于关键字的子查询并不具备特殊的语法,这里我们所讲的子查询都是通过"()"声明的子查询,实际上就是查询嵌套,查询语句的任意位置都可以使用子查询,出现最多的是WHERE和FROM:

WHER子查询:返回单行单列,单行多列,多行单列

HAVING子句:子查询返回单行单列,而且要使用统计函数过滤

FROM子查询:多行多列

SELECT子查询:一般返回单行多列,并且需要某些查询的时候使用

WHERE使用子查询

返回单行单列:

统计公司的最低工资的员工:

SELECT MIN(sal) FROM emp;//返回单行单列,也就是一个数值
SELECT * FROM emp WHERE sal=(SELECT MIN(sal) FROM emp);//使用子查询

查找出公司雇佣最早的雇员

SELECT * FROM emp WHERE hiredate=(SELECT MIN(hiredate) FROM emp);//

返回单行多列

使用较少,比如查询与SCOTT工资相同,职位相同的所有雇员信息:

SELECT sal,job FROM emp WHERE ename='SCOTT';//返回单行两列
SELECT * FROM emp WHERE (sal,job)=(SELECT sal,job FROM emp WHERE ename='SCOTT' AND ename<>'SCOTT');//多列的子查询

子查询返回多行单列

返回的实际就是数据的操作范围,在WHERE子句里面提供了三个主要的范围运算符:IN、ANY、ALL

SELECT sal FROM emp WHERE job='MANAGER';//返回多个
SELECT * FROM emp WHERE sal IN(SELECT sal FROM emp WHERE job='MANAGER');
//如果使用NOT IN,要保证子查询里面不能有空
SELECT * FROM emp WHERE comm NOT IN(SELECT comm FROM emp);//出错

ANY:

=ANY:功能上与IN没有区别
SELECT * FROM emp WHERE sal>ANY(SELECT sal FROM emp WHERE job='MANAGER');

>ANY: 比子查询返回最小的要大
SELECT * FROM emp WHERE sal>ANY(SELECT sal FROM emp WHERE job='MANAGER');

ANY(SELECT sal FROM emp WHERE job='MANAGER');

ALL:

>ALL: 比子查询最大的都大

exists():只关心子查询里面是否有返回行

//此时子查询没有返回任何的数据行,所以exists()就认为数据不存在
//如果数据存在,就会执行查询,也可以使用NOT EXISTS
SELECT * FROM emp WHERE EXISTS(SELECT * FROM emp WHERE deptno=99); 

HAVING子句使用子查询

要使用HAVING必须结合GROUP BY子句,也就是要分组

要求统计出所有高于公司平均工资的部门编号、平均工资、部门人数

SELECT deptno, COUNT(*),AVG(sal) 
FROM emp
GROUP BY deptno
HAVING AVG(sal)>(SELECT AVG(sal) FROM emp);

SELECT子句使用子查询

了解,性能不高

查询每个雇员的编号、姓名、工作、部门名称

一般会选择使用多表查询,也可以使用子查询

//多表查询
SELECT e.empno,e.ename,e.job,d.dname
FROM emp e, dept d
WHERE e.deptno=d.deptno;

//SELECT子查询
SELECT e.empno,e.ename,e.job,(SELECT dname FROM dept WHERE deptno=e.deptno)
FROM emp e;

FROM子句中的子查询

查询出每个部门的编号,名称,地址以及员工数,平均工资

//采用多表查询的方式
SELECT d.deptno,d.dname,d.loc,COUNT(*),AVG(sal)
FROM emp e,dept d
WHERE e.deptno(+)=d.deptno
GROUP BY d.deptno,d.dname,d.loc;
//通过FROM子句进行,这里FROM子句返回的是多行多列,我们可以当成临时表
SELECT d.deptno,d.dname,d.loc,temp.count,temp.avg
FROM dept d,(SELECT deptno,COUNT(empno) count,AVG(sal) avg FROM emp GROUP BY deptno) temp
WHERE d.deptno=temp.deptno(+);

效率分析:

多表查询,由于两张表都没过滤,笛卡尔积产生的数据行较多,因此子查询效率更高

复杂查询案例

列出薪金高于在部门30工作的所有员工的薪金的员工姓名和薪金、部门名称、部门人数

//30工作的所有员工的薪金,返回的是多行单列,可以使用三种判断符
SELECT sal FROM emp WHERE deptno=30; 
//找到符合条件的员工
SELECT e.ename,e.sal,d.dname
FROM emp e,dept d
WHERE e.sal>ALL(SELECT sal FROM emp WHERE deptno=30) AND e.deptno=d.deptno;
//统计部门人数,这里通过FROM子查询构建临时表,然后在进行多表查询
SELECT e.ename,e.sal,d.dname,temp.cemp
FROM emp e,dept d,(SELECT deptno dno,COUNT(empno) cemp FROM emp GROUP BY deptno) temp
WHERE e.sal>ALL(SELECT sal FROM emp WHERE deptno=30) AND e.deptno=d.deptno
AND temp.dno=e.deptno;

列出与SOCTT从事相同工作的所有员工以及部门名称,部门人数,领导姓名

//先找到SCOTT的工作,返回单行单列,因此可以放在WHERE子句中
SELECT job
FROM emp e
WHERE e.ename='SCOTT';
//再找到从事该工作的员工
SELECT e.empno,e.ename,e.job
FROM emp e
WHERE e.job=(SELECT job
FROM emp e
WHERE e.ename='SCOTT') AND e.ename<>'SCOTT';
//统计部门人数
SELECT e.empno,e.ename,e.job,d.dname,temp.count
FROM emp e,dept d,(SELECT deptno dno,COUNT(empno) count FROM emp GROUP BY deptno) temp
WHERE 
job=(SELECT job FROM emp WHERE ename='SCOTT') 
AND e.ename<>'SCOTT'
AND e.deptno=d.deptno
AND d.deptno=temp.dno;
//找到领导,注意这里有两个job,所以要明确使用e.job
SELECT e.empno,e.ename,e.job,d.dname,temp.count,m.ename
FROM emp e,dept d,(SELECT deptno dno,COUNT(empno) count FROM emp GROUP BY deptno) temp,emp m
WHERE 
e.job=(SELECT job FROM emp WHERE ename='SCOTT') 
AND e.ename<>'SCOTT'
AND e.deptno=d.deptno
AND d.deptno=temp.dno
AND e.mgr = m.empno;

列出薪金比SMITH或ALLEN多的所有员工的编号、姓名、部门名称、其领导姓名,部门人均工资以及最高最低工资

//找到比SMITH和ALLEN的薪金多的员工
SELECT e.sal,e.empno,e.ename
FROM emp e
WHERE e.sal>ANY(SELECT sal FROM emp WHERE ename IN('SMITH','ALLEN'))
AND e.ename NOT IN('SMITH','ALLEN');
//找到部门信息
SELECT e.empno,e.ename,e.sal,d.dname
FROM emp e,dept d
WHERE e.sal>ANY(SELECT sal FROM emp WHERE ename IN('SMITH','ALLEN'))
AND e.ename NOT IN('SMITH','ALLEN')
AND e.deptno=d.deptno;
//找到领导
SELECT e.empno,e.ename,e.sal,d.dname,m.ename
FROM emp e,dept d,emp m
WHERE e.sal>ANY(SELECT sal FROM emp WHERE ename IN('SMITH','ALLEN'))
AND e.ename NOT IN('SMITH','ALLEN')
AND e.deptno=d.deptno
AND e.mgr=m.empno(+);

//统计信息
SELECT e.empno,e.ename,e.sal,d.dname,m.ename,temp.avg,temp.max,temp.min
FROM emp e,dept d,emp m,
(SELECT deptno dno,COUNT(empno) count, AVG(sal) avg,MAX(sal) max,MIN(sal) min FROM emp GROUP BY deptno) temp
WHERE e.sal>ANY(SELECT sal FROM emp WHERE ename IN('SMITH','ALLEN'))
AND e.ename NOT IN('SMITH','ALLEN')
AND e.deptno=d.deptno
AND e.mgr=m.empno(+)
AND e.deptno=temp.dno;

列出受雇日期早于其直接上级的所有员工的编号、姓名、部门名称、部门位置、部门人数

SELECT e.empno,e.ename,d.dname,d.loc,temp.count
FROM emp e,emp m,dept d,
(SELECT deptno dno, COUNT(empno) count FROM emp GROUP BY deptno) temp
WHERE e.mgr=m.empno(+)
AND e.hiredate

列出所有办事员的姓名以及部门名称,部门的人数,工资等级

SELECT e.ename,d.dname,temp.count
FROM emp e,dept d,
(SELECT deptno,COUNT(empno) count FROM emp GROUP BY deptno) temp
WHERE e.deptno=d.deptno
AND e.deptno=temp.deptno;

//找到工资等级
SELECT e.sal,s.grade FROM emp e, salgrade s
WHERE e.sal>s.losal AND e.sal

数据更新

增加数据

备份emp表

CREATE TABLE myemp AS SELECT * FROM emp;

增加数据

INSERT INT 表名称[(字段名称,字段名称)] VALUES(数据,数据);

但是对于数据的增加操作,关于数据的定义问题:

字符串:单引号''
数值:直接写
日期:SYSDATE、根据日期结构保存字符串、使用TO_DATE
//完整的语法
INSERT INTO myemp(empno,ename,job,mgr,hiredate,sal,comm,deptno) VALUES(8000,'老王','清洁工',7839,TO_DATE('1988-10-10','yyyy-mm-dd'),2000,100,40);
//简化的语法,没有字段名称,默认按照原有的字段顺序,不推荐使用
INSERT INTO myemp(empno,ename,job,mgr,hiredate,sal,comm,deptno) VALUES(8001,'老李','清洁工',7839,TO_DATE('1978-10-10','yyyy-mm-dd'),2000,100,40);

修改

UPDATE 表名称 SET 字段=内容,字段=内容,[WHERE 条件]
//案例
UPDATE myemp SET sal=1500 WHERE empno=8000;
//将工资最低的员工改为平均工资
UPDATE myemp SET sal=(SELECT AVG(sal) FROM myemp) WHERE sal=(SELECT MIN(sal) FROM myemp);
//将所有81年雇员的雇佣日期修改为今天,工资增长20%
SELECT * FROM myemp WHERE TO_CHAR(hiredate,'yyyy')='1981'

UPDATE myemp SET hiredate=SYSDATE,sal=sal*1.2
WHERE hiredate BETWEEN '01-1月-1981' AND '31-12月-1981';

//如果不加更新条件,会更新所有的数据
UPDATE myemp WHERE comm=nul

删除数据

DELETE FROM 表名称 [WHERE 条件]

DELETE FROM myemp WHERE empno=8000;
//删除若干个数据
DELETE FROM myemp WHERE empno IN(...)
//结合子查询,比如删除工资最高的雇员
DELETE FROM myemp WHERE sal=(SELECT MAX(sal) FROM myemp);
//删除所有的数据
DELETE FROM myemp;

事务处理

事务是保证数据完整性的一种是后端,事务具备ACID原则。对于Oracle而言,每一个客户端都是独立的,都是用一个session,每个session都是独立的事务,操作会在缓冲区存储,可以回滚,提交数据后(commit)才会写入数据库。

事务锁

如果两个session进行同一条数据操作,会出现什么情况

UPDATE myemp SET sal=5000 WHERE empno=7566;//第一个session更新
UPDATE myemp SET comm=900 WHERE empno=7556;//第二个session更新操作无法执行,要一致等待第一个操作结束

第一个session没有提交或回滚前,第二个session无法执行操作,这也是数据的隔离性。虽然数据处理很方便,但是这种锁定很麻烦,比如如果执行了批量更新操作,其他的针对此表的操作无法执行。

数据伪列

之前学习过SYSDATE伪列,伪列是指本身不存在但可以使用的列,有两个非常重要的伪列:ROWNUM,ROWID

ROWNUM

如果开发中使用了ROWNUM,表示会自动生成行号:

SQL> SELECT ROWNUM,empno,ename,job FROM emp;

    ROWNUM      EMPNO ENAME                JOB
---------- ---------- -------------------- ---------
         1       7369 SMITH                CLERK
         2       7499 ALLEN                SALESMAN
         3       7521 WARD                 SALESMAN
         4       7566 JONES                MANAGER
         5       7654 MARTIN               SALESMAN
         6       7698 BLAKE                MANAGER
         7       7782 CLARK                MANAGER
         8       7788 SCOTT                ANALYST
         9       7839 KING                 PRESIDENT
        10       7844 TURNER               SALESMAN
        11       7876 ADAMS                CLERK
        12       7900 JAMES                CLERK
        13       7902 FORD                 ANALYST
        14       7934 MILLER               CLERK

ROWNUM不是固定的,而是计算出来的,同时ROWNUM支持放在where语句中

SQL> SELECT ROWNUM,empno,ename,job FROM emp WHERE deptno=10 AND ROWNUM=1;

    ROWNUM      EMPNO ENAME                JOB
---------- ---------- -------------------- ------------------
         1       7782 CLARK                MANAGER

需要注意的是这里的判断条件不能写成ROWNUM=2,只支持对第一行处理

为什么NOT IN里面不能null
NOT IN(null),如果某一列的数据没有null,那么就表示查询全部

对ROWUN可以取得前N行记录:

SELECT * FROM emp WHERE ROWNUM<=5;

取得6-10行数据:

注意ROWNUM不能够使用BETWEEN过滤范围

//临时表中的数据是固定的,这时候可以使用判断
SELECT *
FROM (SELECT ROWNUM rn,empno,ename FROM emp WHERE ROWNUM<=10) temp
WHERE temp.rn>5;

分页的是先可以使用上面的语句:

SELECT *
FROM(SELECT ROWNUM rn,列,...FROM 表名称
WHERE ROWNUM<=currentPage*lineSize) temp
WHERE tmep.rn>(currentPage-1)*lineSize;

ROWID

实际开发用的不多,它是针对于每行数据提供的物理地址

SELECT ROWID,empno,deptno,ename FROM emp;

ROWID                   EMPNO     DEPTNO ENAME
------------------ ---------- ---------- ------
AAAR3sAAEAAAACXAAA       7369         20 SMITH
AAAR3sAAEAAAACXAAB       7499         30 ALLEN
AAAR3sAAEAAAACXAAC       7521         30 WARD
AAAR3sAAEAAAACXAAD       7566         20 JONES

ROWID组成结构:

数据对象编号:AAAR3s
数据文件编号:AAE
数据保存的块号:AAAACX
数据保存的行号:AAA

相关问题:表中有许多完全重复的数,要求将重复的数据删掉,只保留最早的一个:

//先查询需要保留的数据,ROWID最小的是最早的
SELECT deptno,dname,loc,MIN(ROWID)
FROM dept
GROUP BY deptno,dname,loc;
//然后删除
DELETE FROM dept 
WHERE ROWID NOT IN(SELECT deptno,dname,loc,MIN(ROWID) FROM dept GROUP BY deptno,dname,loc;)

表的创建与管理

常用的数据类型

字符串:使用VARCHAR2描述(其他数据库是VARCAHR)

数字:使用NUMBER,如果是小数使用NUMBER(m,n)来标识,其中n为小数位,m为整数位。

​ 整数也可以使用INT

​ 小数:使用FLOAT

日期:使用DATE是最常用的做法,但是在Oracle里面DATE可能只是日期,DATETIME才表示日期时间

大文本数据:使用CLOB描述,最多可以保存4G文字

大对象数据:使用BLOG,保存图片、音乐、电影等等,最多可以保存4G

创建数据表

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

默认值是没有字段设置的时候才生效,置为null不会使用默认值

复制表

实际不是复制的操作,而是将一个子查询的结果返回一个表而已

CREATE TABLE 表名称 AS 子查询

CREATE TABLE emp30 AS SELECT * FROM emp WHERE deptno=30;

也可以复制表结构不复制表内容,只需要设置一个不满足的条件就行

CREATE TABLE empnul
AS
SELECT * FROM emp WHERE 1=2;

截断表

事务处理是保证数据完整性的一种手段,在用户未提交操作时,如果发生DDL操作,那么所有的事务操作都会提交。事务只对DML起作用。由于事务的作用,我们使用DELETE删除表不会导致数据立刻被删除,因此还会占用资源,所以提出截断表的概念:

TRUNCATE TABLE myemp;//rollback也没用

表重命名

DDL属于数据对象定义语言,Oracle提供一个数据字典,用于记录所有的对象的状态,创建、删除操作会在数据字典里面有记录,这是oracle自己维护的,可以通过命令完成操作该数据字典

数据字典用户通常主要分为三类:
USER_*:用户的数据字典信息
DBA_*:管理员的数据字典
ALL_*:所有人都可以看到额数据字典

之前使用过的:

SELECT * FROM tab;

可以使用数据字典完成:

SELECT * FROM user_tables;

这个数据字典主要保存了数据的存储情况,占用资源情况,所以一般情况下使用tab就行了

表的重命名实际就是修改数据字典

RENAME member TO person;//将表member修改为person

数据表的删除、闪回技术

删除数据表是属于数据对象的操作

DROP TABLE 表名称;//但是删除后会出现BIN开头的表

在Oracle 10g之后,对于删除的操作提供了挽救的机会,也就是保存在缓存中以供恢复,也成为闪回技术。在任何数据库中都不会提供批量删除数据库表的操作

查看回收站

SHOW RECYCLEBIN;//老版本的命令,不支持
SELECT * FROM user_recyclebin;//查看
FLASHBACK TABLE person TO BEFORE DROP;//从删除状态恢复

彻底删除表:

DROP TABLE person PURGE;

清空回收站:

PURGE RECYCLEBIN

删除回收站的表:

PURGE TABLE person

修改表结构

修改列

ALTER TABLE member MODIFY(name VARCHAR2(20) DEFAULT 'NO NAME');

增加列

ALTER TABLE member ADD(address VARCHAR2(30));
ALTER TABLE member ADD(sex VARCHAR2(30) DEFAULT '男');

需要注意的是,修改字段的默认值,会在所有数据上生效,因此不建议使用

删除列

ALTER TABLE DROP COLUMN sex;

约束管理

非空约束

某个字段的内容不为空,在每个列的后面使用NOT NULL,如果插入数据的时候没有设置值或者插入的值为null会报错

唯一约束

某一个列的内容不能重复,使用UNIQUE来修饰字段,如果插入重复的内容会报错。在oracle中约束也是一个对象,也会存于数据字典user_contraints和user_cons_columns中,前者可以查到约束对象的名字(在报错信息中),后者能够查到加了约束的列。

唯一约束的简写是uk,因此可以在定义表字段时写成:

CONTRAINT uk_email UNIQUE(email) //推荐设置约束的名字,方便查看,约束的名字也不能重复

同时注意null不在唯一约束的判断范围之内

主键约束

主键约束=非空约束+唯一约束

//两种写法
mid NUMBER PRIMARY KEY
CONSTRAINT pk_mid PRIMARY KEY(mid)

复合主键,多个字段都是主键,只有当多个字段都重复时才会认为重复,不推荐使用

检查约束

在数据列上设置一些过滤条件,往往不会设置,通过程序来进行

age NUMBER(3) 
CONSTRAINT ck_age CHECK(age BETWEEN 0 AND 350)

外键约束

在父子表中体现的一种约束操作,比如一个人有多本书,初期的设计如下:

CREATE TABLE book(
	bid NUMBER,
	title VARCHAR2(20),
	mid NUMBER
)
CREATE TABLE member(
	mid NUMBER,
	name VARCHAR(20),
	CONSTRAINT pk_mid PRIMARY KEY(mid)
)

book中需要添加外键约束:

CONSTRAINT fk_mid FOREIGN KEY(mid) REFERENCES member(mid)

增加外键约束需要注意的是:

如果直接删除member表会报失败:表中的唯一/主键被外键引用,所以要改变删除顺序:

DROP TABLE book;
DROP TABLE member;

也有强制删除父表的语句,不考虑外键:

DROP TABLE member CASCADE CONSTRAINT;

同时如果要作为子表外键的父表列,这个列必须设置唯一约束或者主键约束

如果现在主表中的某一行数据有对应子表的数据,那么先删除子表的数据再删除主表的数据,因此有下面两个删除方式

级联删除:自动删除子表数据

CONSTRAINT fk_mid FOREIGN KEY(mid) REFERENCES member(mid) ON DELETE CASCADE
DELETE FROM member WHERE mid=1;//可以同时删除子表数据

级联更新:删除主表数据,子表对应外键字段置为null

CONSTRAINT fk_mid FOREIGN KEY(mid) REFERENCES member(mid) ON DELETE SET NULL

修改约束

一般禁止修改约束,在表创建的时候就设置完整。如果添加约束,需要定义约束名字

增加约束

ALTER TABLE member ADD CONSTRAINT pk_mid PRIMARY KEY(mid);

如果表中的数据违反了约束,该约束创建不会成功

上面的操作语法不能添加非空约束,只能通过修改表来实现

ALTER TABLE member MODIFY(name VARCHAR2(20) NOT NULL)

删除约束

ALTER TABLE member DROP CONSTRAINT 约束名称

DML、DDL实战

编写一个数据库的脚本:

DROP TABLE purchase;
DROP TABLE customer;
DROP TABLE product;
CREATE TABLE product(
	productid VARCHAR2(5),
	productname VARCHAR2(20),
	unitprice NUMBER,
	category VARCHAR2(50),
	provider VARCHAR2(50),
	CONSTRAINT pk_productid PRIMARY KEY(productid),
	CONSTRAINT ck_unitprice CHECK(unitprice>0)
);
CREATE TABLE customer(
	customerid VARCHAR2(5),
    name VARCHAR2(20)	NOT NULL,
    location VARCHAR2(50),
    CONSTRAINT pk_customerid PRIMARY KEY(customerid)
);
CREATE TABLE purcase(
	customerid VARCHAR2(5),
	productid VARCHAR2(5),
	quantity VARCHAR2(5),
	CONSTRAINT fk_customerid FOREIGN KEY(customerid) REFERENCES customer(customerid) ON DELETE CASCADE,
	CONSTRAINT fk_productid FOREIGN KEY(productid) REFERENCES product(productid)ON DELETE CASCADE,
	CONSTRAINT ck_quantity CHECK(quantity BETWEEN 0 AND 20)
);

增加测试数据;

INSERT INTO product(productid,productname,unitprice,category,provider) VALUES('M01','佳洁士',8.0,'牙膏','宝洁');
INSERT INTO product(productid,productname,unitprice,category,provider) VALUES('M02','高露洁',6.50,'牙膏','高露洁');
INSERT INTO product(productid,productname,unitprice,category,provider) VALUES('M03','洁诺',5.00,'牙膏','联合利华');
INSERT INTO product(productid,productname,unitprice,category,provider) VALUES('M04','舒肤佳',3.00,'香皂','宝洁');
INSERT INTO product(productid,productname,unitprice,category,provider) VALUES('M05','夏士莲',5.0,'香皂','联合利华');
INSERT INTO product(productid,productname,unitprice,category,provider) VALUES('M06','雕牌',2.50,'洗衣粉','纳爱斯');
INSERT INTO product(productid,productname,unitprice,category,provider) VALUES('M07','中华',3.50,'牙膏','联合利华');
INSERT INTO product(productid,productname,unitprice,category,provider) VALUES('M08','汰渍',3.00,'洗衣粉','宝洁');
INSERT INTO product(productid,productname,unitprice,category,provider) VALUES('M09','碧浪',4.00,'洗衣粉','宝洁');

INSERT INTO customer(customerid,name,location) VALUES('C01','Dennis','海淀');
INSERT INTO customer(customerid,name,location) VALUES('C02','John','朝阳');
INSERT INTO customer(customerid,name,location) VALUES('C03','Tom','东城');
INSERT INTO customer(customerid,name,location) VALUES('C04','Jenny','东城');
INSERT INTO customer(customerid,name,location) VALUES('C05','Rick','西城');

INSERT INTO purcase(customerid,productid,quantity) VALUES('C01','M01','3');
INSERT INTO purcase(customerid,productid,quantity) VALUES('C01','M05','2');
INSERT INTO purcase(customerid,productid,quantity) VALUES('C01','M08','2');
INSERT INTO purcase(customerid,productid,quantity) VALUES('C02','M02','5');
INSERT INTO purcase(customerid,productid,quantity) VALUES('C02','M06','4');
INSERT INTO purcase(customerid,productid,quantity) VALUES('C03','M01','1');
INSERT INTO purcase(customerid,productid,quantity) VALUES('C03','M05','1');
INSERT INTO purcase(customerid,productid,quantity) VALUES('C03','M06','3');
INSERT INTO purcase(customerid,productid,quantity) VALUES('C03','M08','1');
INSERT INTO purcase(customerid,productid,quantity) VALUES('C04','M03','7');
INSERT INTO purcase(customerid,productid,quantity) VALUES('C04','M04','3');
INSERT INTO purcase(customerid,productid,quantity) VALUES('C05','M06','2');
INSERT INTO purcase(customerid,productid,quantity) VALUES('C05','M07','8');

COMMIT;

数据查询实战

求购买了供应商宝洁的产品的所有顾客信息

SELECT * FROM customer WHERE customerid IN(
SELECT customerid FROM purcase WHERE productid in (
	SELECT productid FROM product WHERE provider='宝洁'
));

求购买的商品包含了顾客Dennis所购买的所有商品的顾客姓名

SELECT name FROM customer WHERE customerid IN(
    SELECT customerid FROM purcase WHERE productid IN (
        SELECT productid FROM purcase WHERE customerid=(
            SELECT customerid FROM customer WHERE name='Dennis'
        )
    )
);
//上面的写法不正确,正确的应该采取下面的写法
SELECT * FROM customer ca
WHERE NOT EXISTS(
SELECT p1.productid FROM purcase p1 WHERE customerid=(
	SELECT customerid FROM customer WHERE name='Dennis')
	MINUS(
	SELECT p2.productid
	FROM purcase p2
	WHERE customerid=ca.customerid)
) AND ca.name<>'Dennis';

求牙膏卖出数量最多的供应商

SELECT provider
FROM product WHERE productid=(
SELECT productid
FROM purcase
WHERE productid IN (
	SELECT productid FROM product WHERE category='牙膏') 
GROUP BY productid
HAVING SUM(quantity)=(
	SELECT MAX(SUM(quantity)) FROM purcase WHERE productid IN (SELECT productid FROM product WHERE category='牙膏') 
	GROUP BY productid));

数据更新实战

将所有的牙膏商品单价增加10%

UPDATE product SET unitprice=unitprice*1.1 WHERE category='牙膏';

删除没有购买过的商品

DELETE FROM product WHERE productid NOT IN(SELECT productid FROM purcase);

常用数据对象

定义序列

自动增长列

在oracle 12c之后才会有增长列,在这之前可以使用序列的方式进行

CREATE SEQUENCE 名称 [MAXVALUE 最大值|NOMAXVALUE] [MINVALUE 最小值|NOMINVALUE] [INCREMENTBY 步长] [START WITH 开始值] [CYCLE|NOCYCLE] [CACHE 缓存个数|NOCACHE]

创建序列是属于DDL,会存于数据字典中:

CREATE SEQUENCE myseq;
SELECT * FROM user_sequences;

user_sequences的表结构分析:

SEQUENCE_NAME:序列名称,本次为MYSEQ
MIN_VALUE:当前序列为最小值,本次为1
MAX_VALUE:当前序列的最大值,本次为"1.0E28"
INCREMENT_BY:每次序列增长的步长内容
CY:是否为循环序列,本次为N
OR:是否需要排序
CACHE_SIZE:缓存个数,默认是20个,在CYCLE情况下必须小于最大值
LAST_NUMBER:最后的数值,它是缓存个数*步长+初始值

如果想使用序列,可以通过两个伪列来完成:

nextval:获取序列的下一个内容,每一次调用序列的值都会增长
currval:获取当前序列,每一次调用不会增长

SELECT myseq.currval FROM dual;//必须执行nextval才能使用这个
SELECT myseq.nextval FROM dual;

//开发中的使用
INSERT INTO mytab(id,name) VALUES(myseq.nextval,'HELLO')

缓存的作用:

为了保证序列的性能问题,会利用缓存将序列先创建好,但是如果数据库关闭了,缓存会有跳号问题,也就是序列可能不连续

SELECT myseq.nextval FROM dual;
SELECT sequence_name,cache_size,last_number FROM user_sequences;

删除序列:

DROP SEQUENCE myseq;

视图

视图的创建和使用

视图一般用来封装复杂查询

CREATE [OR REPLACE] VIEW 名称 AS 子查询 

//为scott开通创建视图的权限
CONN sys/change_install AS SYSDBA;
GRANT CREATE VIEW TO scott;
CONN scott/tiger
CREATE VIEW myview AS SELECT * FROM emp WHERE deptno=10;

SELECT* FROM user_views;//视图对应的数据字典

查询视图:

SELECT * FROM myview;

删除:

DROP VIEW myview;//视图一般不会删除,而是替换

通过视图包装复杂的查询:

CREATE OR REPLACE VIEW myview AS
SELECT 
FROM dept,(SELECT deptno dno,COUNT(*) count FROM emp GROUP BY deptno) temp
WHERE d.deptno=temp.dno(+)

视图只包含有查询语句的临时数据,并不是真实存在的:

CREATE VIEW myview AS SELECT * FROM emp WHERE deptno=20;

上面视图的部门是可以更新的,emp表中的内容也会被改变,因此可以对视图的更新进行检查:

CREATE OR REPLACE VIEW myview AS SELECT * FROM emp WHERE deptno=20 WITH CHECK OPTION;
//此时更新视图的部门则会出现错误

也可以创建自读视图:如果使用DML操作为会报错

CREATE OR REPLACE VIEW myview AS SELECT * FROM emp WHERE deptno=20 WITH READ ONLY;

封装复杂查询的视图:

复杂多表的视图一般没法修改,因为涉及到多个表

CREATE OR REPLACE VIEW myview AS SELECT e.empno,e.ename,e.job,d.dname,e.sal,m.ename
FROM emp e,dept d,emp m
WHERE e.deptno=d.deptno AND e.mgr=m.empno(+);

其他数据库对象

同义词

SELECT SYSDATE FROM dual;

dual是临时表,它是sys用户的,但是不用加sys限定词来访问该表,这就是同义词带来的效果,创建同义词:

CREATE [PUBLIC] SYNONYM 同义词名称 FOR 模式.表名称
CREATE SYNONYM semp FOR scott.emp;

创建完同义词就可以拿来查询

SELECT * FROM semp;

但是这个同意词还不能被其他用户所使用,它只能被sys所用,如果想让其他用户使用,需要创建公共同义词:

CREATE PUBLIC SYNONYM semp FOR scott.emp;

索引

索引能够做什么,以及为什么需要索引:

SELECT * FROM emp WHERE sal>1500;

这是一条非常简单的查询,数据在这条查询中做了什么呢?我们可以打开追踪器:

CONN sys/change_on_install AS SYSDBS:
SET AUTOTRACE ON;
CONN scott/tiger;
SELECT * FROM emp WHERE sal>1500;//返回分析的信息

返回的信息中有TABLE ACCESS FULL信息,表示要加进行全表扫描,这样会有性能问题。进行ORDER BY排序来解决这个问题?不行,因为ORDER BY是最后执行的。这时候可以创建索引实现二叉树数据结构进行查找优化:

CREATE INDEX emp_sal_ind ON scott.emp(sal);

此时再查询,跟踪器返回的信息显式使用INDEX RANGE SCAN。

如果索引的内容一直在改变,那么索引所维护的二叉树将会频繁变动,这会造成时间消耗。所以一般都是针对主键建立索引,因为主键不会频繁变动。

用户管理

使用sys登录,然后创建一个新用户:

CREATE USER log IDENTIFIED BY wangwang;

如果此时用该用户登录,会提示没有创建session权限:

GRANT 权限1,权限2,TO 用户名
GRAANT CREATE SESSION TO dog

此时如果创建表会报权限不足,因为每一步都需要权限,因此oracle提供了角色的概念,有两个主要的角色:CONNECT,RESOURCE(表以及表空间角色)

GRANT CONNECT,RESOURCE TO dog;

用户得到新的权限之后,需要重新登录才能获取新的权限

修改用户密,由于用户并不多,所以用户的维护就可以由sys进行了

ALTER USER dog IDENTIFIED BY miaomiao;//修改密码
ALTER USER dog PASSWORD EXIRE;//密码过期,需要重新设置密码
ALTER USER dog ACCOUNT LOCK; //锁定
ALTER USER dog ACCOUNT UNLOCK; //锁定

除了系统权限,还有一些对象权限:可以针对一个对象下的数据表进行访问的定义,有四种权限:INSERT,UPDATE,DELETE,SELECT

GRANT SELECT,INSERT ON scott.emp TO dog;

回收权限:

REVOKE SELECT,INSERT ON scott.emp FROM dog;

删除用户:

DROP USER dog CASCADE;

数据库的备份

数据导出:

采用命令行的方式操作

需要注意的是,数据导出必须保证其他用户不会更新数据

cd backup;
exp	//输入用户名和密码,输入缓冲区大小等等

数据恢复:进入到文件备份所在路径

imp //导入数据

数据库的冷备份

成为归档备份,指数据库关闭服务,所有的事务都提交了之后才备份。备份需要以下的内容:

控制文件:控制着整个oracle的实例信息,可以使用v$controlfile数据字典查看

重做日志文件:通过v$logfile数据字典找到

数据文件:v$datafile数据字典找到

核心配置文件:使用SHOW PARAMETER pfile数据字典找到

记录上面查到的文件路径(用管理员用户登录),关闭oracle服务

SHUTDOWN IMMEDIATE

拷贝出所有的备份文件

启动服务器:

STARTUP

数据库设计

第一范式

比如用户的联系方式用一个字段来表示,但是联系方式有多种,可以再进行细分,因此这就不符合第一设计范式。第一范式的核心意义就是在于使用常见的数据类型:

日期描述不能拆分
对于姓名国内和国外是不同的,国外是first name,last name

第二个范式

数据表中不存在非关键字段对任意一候选关键字段的部分函数的依赖。(实际描述的就是多对多的关系)

比如总价=商品单价*购买数量,总价字段就不符合第二范式

函数依赖是指某几个字段是否可以推导出其他字段

第三范式

数据表之中不存在非关键字段对任意一候选关键字段传递函数依赖(一对多关系)

范式的解释

参考知乎的文章

范式是“符合某一种级别的关系模式的集合,表示一个关系内部各属性之间的联系的合理化程度”

实际上你可以把它粗略地理解为一张数据表的表结构所符合的某种设计标准的级别

你可能感兴趣的:(JavaWeb)