这里安装的是11gR2,推荐win7安装,不推荐使用linux,
打开计算的管理,查看服务,建议改成手动启动服务。这些服务中有两个关键的服务:
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 optionsSQL>
也可以通过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 optionsSQL>
找到安装目录product下的目录:
product\11.2.0\dbhome_1
单击deinstall文件进行卸载,按照提示进行操作
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中拷贝文件
DML:数组操作语言,数据的更新与查询
DDL:数据定义语言,数据表,索引,同义词,约束等
DCL:数据控制语言,权限控制
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;
SELECT DISTINCT job FROM emp;
//关系运算:>,=,<
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;
需要注意的是,如果使用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
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
查询每个职位的名称,职位的平均工资,但是要求显示职位的平均工资高于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子查询:一般返回单行多列,并且需要某些查询的时候使用
返回单行单列:
统计公司的最低工资的员工:
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必须结合GROUP BY子句,也就是要分组
要求统计出所有高于公司平均工资的部门编号、平均工资、部门人数
SELECT deptno, COUNT(*),AVG(sal)
FROM emp
GROUP BY deptno
HAVING AVG(sal)>(SELECT AVG(sal) FROM emp);
了解,性能不高
查询每个雇员的编号、姓名、工作、部门名称
一般会选择使用多表查询,也可以使用子查询
//多表查询
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;
查询出每个部门的编号,名称,地址以及员工数,平均工资
//采用多表查询的方式
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,表示会自动生成行号:
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;
实际开发用的不多,它是针对于每行数据提供的物理地址
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 约束名称
编写一个数据库的脚本:
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
数据表中不存在非关键字段对任意一候选关键字段的部分函数的依赖。(实际描述的就是多对多的关系)
比如总价=商品单价*购买数量,总价字段就不符合第二范式
函数依赖是指某几个字段是否可以推导出其他字段
数据表之中不存在非关键字段对任意一候选关键字段传递函数依赖(一对多关系)
参考知乎的文章
范式是“符合某一种级别的关系模式的集合,表示一个关系内部各属性之间的联系的合理化程度”
实际上你可以把它粗略地理解为一张数据表的表结构所符合的某种设计标准的级别