新创建的用户没用登录权限,需要进行授权
Oracle中已存在三个重要的角色:connect角色,resource角色,dba角色。 CONNECT角色: --是授予最终用户的典型权利,最基本的 ALTER SESSION --修改会话 CREATE CLUSTER --建立聚簇 CREATE DATABASE LINK --建立数据库链接 CREATE SEQUENCE --建立序列 CREATE SESSION --建立会话 CREATE SYNONYM --建立同义词 CREATE VIEW --建立视图 RESOURCE角色: --是授予开发人员的 CREATE CLUSTER --建立聚簇 CREATE PROCEDURE --建立过程 CREATE SEQUENCE --建立序列 CREATE TABLE --建表 CREATE TRIGGER --建立触发器 CREATE TYPE --建立类型 DBA角色:拥有全部特权,是系统最高权限,只有DBA才可以创建数据库结构,并且系统权限也需要DBA授出,且DBA用户可以操作全体用户的任意基表,包括删除命令: grant dba to 用户名;注意:使用简单的写法必须按照表中的字段的顺序来插入值,而且如果有为空的字段使用null
在删除语句中如果不指定删除条件的话就会删除所有的数据
drop,truncate,delete区别
drop (删除表):删除内容和定义,释放空间。简单来说就是把整个表去掉.以后要新增数据是不可能的,除非新增一个表。
drop语句将删除表的结构被依赖的约束(constrain),触发器(trigger)索引(index);依赖于该表的存储过程/函数将被保留,但其状态会变为:invalid。
Truncate(清空表中的数据):删除内容、释放空间但不删除定义(保留表的数据结构)。与drop不同的是,只是清空表数据而已。
注意:truncate 不能删除行数据,要删就要把表清空
delete (删除表中的数据):delete 语句用于删除表中的行。delete语句执行删除的过程是每次从表中删除一行,并且同时将该行的删除操作作为事务记录在日志中保存以便进行进行回滚操作。
Truncate与不带where的delete :只删除数据,而不删除表的结构(定义)
在数据库中事务是工作的逻辑单元,一个事务是由一个或多个完成一组的相关行为的SQL语句组成,通过事务机制确保这一组SQL语句所作的操作要么完全成功执行,完成整个工作单元操作,要么一点也不执行。
主要作用:确保数据库的完整性。
Atomicity(原子性): 事务中sql语句不可分割,要么都做,要么都不做
Consistency(一致性) : 指事务操作前后,数据库中数据是一致的,数据满足业务规则约束(例如账户金额的转出和转入),与原子性对应。
Isolation(隔离性):多个并发事务可以独立运行,而不能相互干扰,一个事务修改数据未提交前,其他事务看不到它所做的更改。
Durability(持久性):事务提交后,数据的修改是永久的。
错读|脏读:当一个事务修改数据时,另一事务读取了该数据,但是第一事务由于某种原因取消对数据修改,使数据返回了原状态,这是第二个事务读取的数据与数据库中数据不一致.这就叫错读。
非重复读取:是指一个事务读取数据库中的数据后,另一个事务则更新了数据,当第一个事务再次读取其中的数据时,就会发现数据已经发生了改变,这就是非重复读取。非重复读取所导致的结果就是一个事务前后两次读取的数据不相同。
假读:如果一个事务基于某个条件读取数据后,另一个事务则更新了同一个表中的数据,这时第一个事务再次读取数据时,根据搜索的条件返回了不同的行,这就是假读。
事务中遇到的这些异常与事务的隔离性设置有关,事务的隔离性设置越多,异常就出现的越少,但并发效果就越低,事务的隔离性设置越少,异常出现的越多,并发效果越高。
针对3中读取的数据时产生的不一致现象,在ANSI SQL标准92中定义了4个事务的隔离级别,如下:
隔离层错读|脏读非重复读取|不可重复读假读|幻读READ UNCOMMITTED(非提交读)read uncommitted是是是READ COMMITTED(提交读) read committed否是是Repeatable READ(可重复读) repeatable read否否是Serializable(串行读)否否否Oracle支持上述四种隔离层中的两种(不支持脏读):read committed 和 serializable。除此之外oralce中还定义read only 和 read write隔离层。
Read committed: 这是oracle默认的隔离层。
Serializable:事务与事务之间完全隔开,事务以串行的方式执行,这并不是说一个事务必须结束才能启动另外一个事务,而是说这些事务的执行的结果于一次执行的事务的结果一致。
Read only和 read write 当使用read only时,事务中不能有任何修改数据库中数据的操作语句,这包括 insert、update、delete、create语句。Read only是serializable的一个子集,区别是read only 只读,而serialzable可以执行DML操作。Read write它是默认设置,该选项表示在事务中可以有访问语句、修改语句.但不经常使用.
列举如下:
Set transaction read only 事务中不能有任何修改数据库中数据的操作语句insert、update、delete、create
Set transaction read write 默认设置,该选项表示在事务中可以有访问语句、修改语句
Set transaction isolation level read committed oracle默认的隔离层
Set transaction isolation level serializable serialzable可以执行DML(增删改查)操作
注意:这些语句是互斥的.即不能够同时设置两个或者两个以上的选项。
在oracle数据库中,没有提供开始事务处理语句,所有的事务都是隐式开始的,也就是说在oracle中,用户不可以显示使用命令来开始一个事务.oracle任务第一条修改数据库的语句,或者一些要求事务处理的场合都是事务的隐式开始。但是当用户想要终止一个事务处理时,必须显示使用commit和rollback语句结束。
总结:
开始事务:连接到数据库,执行DML、DCL、DDL语句(oracle数据库中所有的事务都是隐式开始的)
结束事务: 1.执行DDL(例如CREATE TABLE),DCL(例如GRANT),系统自动执行commit语句(隐式提交)
2.执行commit/rollback
3.退出/断开数据库的连接自动执行commit语句(隐式提交)
4.进程意外终止,事务自动rollback(隐式提交)
5.事务commit时会生成一个唯一的系统变化号(SCN)保存到事务表
(1)commit 提交事务
(2)rollback 回滚事务
有一张person表:
删除第九条记录
当再打开一个窗口进行查询发现pid为9的人物还在
结论:当我们在一个窗口执行完delete语句时,我们去另一个窗口中去查看数据库数据,发现并没有我们删除数据,这是因为oracle的事务对数据库的变更的处理,我们必须做提交事务才能让数据真正的插入到数据库中,在同样在执行完数据库变更的操作后还可以把事务进行回滚,这样就不会插入到数据库。如果事务提交后则不可以再回滚。
提交事务:commit
回滚事务:rollback 事务没有提交之前都可以回滚,提交了就不能回滚
提交事务后第二个窗口查询结果发生变化.
事务保存点:可以在事务的任何地方设置保存点,以便rollback(回滚)
语法:
savepoint 保存点名字; 示例: savepoint a1; --设置一个保存点 a1; update person set name='张飞' where pid='8' --修改一条数据 savepoint a2; --设置一个保存点 a1; update tmp set name='关羽' where id='7' --再修改一条数据 rollback to a2; --回滚到a2 保存点。 此时在 a2 保存点之后的所有数据修改视为无效。 rollback to a1; --这里可以再从a2保存点再回滚到a1 保存点。 此时在 a1 保存点之后的所有数据修改视为无效。 rollback; --回滚全部。即撤消至上一次提交事务后的所有数据修改。 commit; --提交事务 将回滚后的事务提交,并会删除所有保存点。注意:我们可以从a2向前再回滚到a1 ,但无法从a1回滚到a2。也就是只能向前回滚,不能从前面的点再向后回滚!
锁是一种机制,多个事务同时访问一个数据库对象时,该机制可以实现对并发的控制
独占锁(Exclusive Lock):即X锁,又称排它锁,是用来防止同时共享相同资源的锁。加了独占锁的数据库对象不能被其它事务读取和修改。
共享锁(Share Lock):即S锁,是通过对数据存取的高并行性来实现的。加了共享锁的数据库对象可以被其它事务读取,但是不能被其它事务修改。
DDL锁(data locks,数据锁):用于保护数据库对象的结构,如表、索引等的结构定义。
DML锁(dictionary locks,字典锁):用于保护数据的完整性,能够防止同步冲突的DML和DDL操作的破坏性交互。
内部锁和闩(internal locks and latches): 由oracle自己管理以保护数据库的内部结构,如数据文件,对用户是不可见的。
当 DDL命令发出时,Oracle会自动在被处理的对象上添加DDL锁定,从而防止对象被其他用户所修改。当DDL命令结束以后,则释放DDL锁定。DDL锁定不能显式的被请求,只有当对象结构被修改或者被引用时,才会在对象上添加DDL锁定。比如创建或者编译 存储过程时会对引用的对象添加DDL锁定。在创建视图时,也会对引用的表添加DDL锁定等。 在执行DDL命令之前,Oracle会自动添加一个隐式提交命令,然后执行具体的DDL命令,在DDL命令执行结束之后,还会自动添加一个隐式提交命令。实际上,Oracle在执行DDL命令时,都会将其转换为对数据字典表的DML操作。比如我们发出创建表的DDL命令时,Oracle会将表的名称插入数据字典表tab里,同时将表里的列名以及列的类型插入col里,同 时将表里的列名以及列的类型插入col里,同时将表里的列名以及列的类型插入col表里等。因此,在DDL命令中需要添加隐式的提交命令,从而提交那些对数据字典表的DML操作。即使DDL命令失 败,它也会发出提交命令。DDL锁包括三种类型:排它的DDL锁定、共享的DDL锁定、可打破的解析锁定。
排它的DDL锁定(Exclusive DDL Lock) 大部分的DDL操作都会在被操作的对象上添加排他的DDL锁定,从而防止在DDL命令执行期间,对象被其他用户所修改。当对象上添加了排他的DDL锁定以后,该对象上不能再添加任何其他的DDL锁定。如果是对表进行DDL命令,则其他进程也不能修改表里的数据。
共享的DDL锁定(Shared DDL Lock ) 用来保护被DDL的对象不被其他用户进程所更新,但是允许其他进程在对象上添加共享的DDL锁定。如果是对表进行DDL命令,则其他进程可以同时修改表里的数据。比如我们发出create view命令创建视图时,在视图的所引用的表(这种表也叫基表)上添加的就是共享的DDL命令。也就是说,在创建视图时,其他用户不能修改基表的结构,但是可以更新基表里的数据。
可打破的解析锁定(Breakable Parsed Lock) 在shared pool里缓存的SQL游标或者PL/SQL程序代码都会获得引用对象上的解析锁定。如果我们发出DDL命令修改了某个对象的结构时,该对象相关的、位于 shared pool里的解析锁定就被打破,从而导致引用了该对象的SQL游标或者PL/SQL程序代码全都失效。下次再次执行相同的SQL语句时,需要重新解析,这 也就是所谓的SQL语句的reload了。可打破的解析锁定不会阻止其他的DDL锁定,如果发生与解析锁定相冲突的DDL锁定,则解析锁定也会被打破。
用来保证并行访问数据的完整性。能够防止同步冲突的DML和DDL操作的破坏性交互。在Oracle数据库中,DML锁主要包括TM锁和TX锁,其中TM锁称为表级锁,TX锁称为事务锁或行级锁。
行级锁(TX锁),对一条记录加上TX锁后,其他用户不能修改、删除该记录。
表级锁(TM锁),防止在修改表数据时,表的结构发生变化。
内部锁保护内部数据库结构,如数据文件,对用户是不可见的。
当一个会话保持另一个会话正在请求的资源上的锁定时,就会发生阻塞。被阻塞的会话将一直挂起,直到持有锁的会话放弃锁定的资源为止。锁机制的适当应用保证了数据的完成性,应用不当会导致死锁,从而我们又将锁分为悲观锁与乐观锁。
锁在用户修改之前就发挥作用,如:Select …for update(nowait) ,Select * from tab1 for update 用户发出这条命令之后,oracle将会对返回集中的数据建立行级封锁,以防止其他用户的修改。 如果此时其他用户对上面返回结果集的数据进行dml或ddl操作都会返回一个错误信息或发生阻塞。 1)对返回结果集进行update或delete操作会发生阻塞。 2)对该表进行ddl操作将会报:Ora-00054:resource busy and acquire with nowait specified. 此时Oracle已经对返回的结果集上加了排它的行级锁,所有其他对这些数据进行的修改或删除操作都必须等待这个锁的释放,产生的外在现象就是其他的操作将发生阻塞,这个操作commit或rollback. 同样这个查询的事务将会对该表加表级锁,不允许对该表的任何ddl操作,否则将会报出ora-00054错误::resource busy and acquire with nowait specified.
乐观的认为数据在select出来到update进去并提交的这段时间数据不会被更改。这里面有一种潜在的危险就是由于被选出的结果集并没有被锁定,是存在一种可能被其他用户更改的可能。因此Oracle仍然建议是用悲观封锁,因为这样会更安全。
当两个用户希望持有对方的资源时就会发生死锁。即两个用户互相等待对方释放资源,oracle认定为产生了死 锁,在这种情况下,将以牺牲一个用户作为代价,另一个用户继续执行,牺牲的用户的事务将回滚。
4个常见的dml语句会产生阻塞 INSERT ,UPDATE,DELETE,SELECT…FOR UPDATE 。 INSERT:Insert发生阻塞的唯一情况就是用户拥有一个建有主键约束的表。当2个的会话同时试图向表中插入相同的数据时,其中的一个会话将被阻塞,直到另外一个会话提交或会滚。一个会话提交时,另一个会话将收到主键重复的错误。回滚时,被阻塞的会话将继续执行。 UPDATE 和DELETE:当执行Update和delete操作的数据行已经被另外的会话锁定时,将会发生阻塞,直到另一个会话提交或会滚。 Select …for update:当一个用户发出select…for update的错作准备对返回的结果集进行修改时,如果结果集已经被另一个会话锁定,就是发生阻塞。需要等另一个会话结束之后才可继续执行。可以通过发出 select… for update nowait的语句来避免发生阻塞,如果资源已经被另一个会话锁定,则会返回以下错误:Ora-00054:resource busy and acquire with nowait specified.
1:用户1对A表进行Update,没有提交。 2:用户2对B表进行Update,没有提交。 此时双反不存在资源共享的问题。 3:如果用户2此时对A表作update,则会发生阻塞,需要等到用户一的事物结束。 4:如果此时用户1又对B表作update,则产生死锁。此时Oracle会选择其中一个用户进行会滚,使另一个用户继续执行操作。
死锁模拟
person 表如下
1. 开两个进程(此处使用sqldeveloper模拟)建立两个事务
事务1:
set transaction read write;事务2:
set transaction read write;2. t1 时刻 事务1和事务2中分别执行如下语句
事务1:
update PERSON set name='大乔',gender=0,birthday=TO_DATE('2020-05-23 14:55:45','yyyy-MM-dd hh24:mi:ss') WHERE pid=7;事务2:
update PERSON set name='孙权',gender=1,birthday=TO_DATE('2020-05-23 14:55:45','yyyy-MM-dd hh24:mi:ss') WHERE pid=8;结果如下:
1行已更新。 1行已更新。 t2时刻事务1和事务2中分别执行如下语句执行事务1:
update PERSON set name='周瑜',gender=1,birthday=TO_DATE('2020-05-23 14:55:45','yyyy-MM-dd hh24:mi:ss') WHERE pid=8;执行事务2:
update PERSON set name='孙尚香',gender=0,birthday=TO_DATE('2020-05-23 14:55:45','yyyy-MM-dd hh24:mi:ss') WHERE pid=7;事务1的结果如下:
事务2的结果如下:
commit 事务1,事务2结果如下:注意:用时299.015s, commit事务1前等待的时间
1、执行commit或者rollback结束事务 2、终止会话。 3、查找到阻塞锁,就终止掉次锁,也可以使用Kill 关键字杀死次会话。
第三种方法演示:
在等待资源时执行,查找阻塞会话执行sql语句: select sid,serial#,username from v$session where sid in (select blocking_session from v$session);执行结果
执行sql: alter system kill session '7,96';注意:在上面例子中,如果在事务1中执行第二步的sql会发生如下错误:
在行: 13 上开始执行命令时出错 - alter system kill session '7,96' 错误报告 - ORA-00027: 无法终止当前会话 00027. 00000 - "cannot kill current session" *Cause: Attempted to use ALTER SYSTEM KILL SESSION to kill the current session. *Action: None.解决方法:另起一个session,执行第二步的sql
结果如下:(session kill成功,死锁解除,事务2更新成功)
事务1:
事务2:
1. 避免应用不运行长事务。
2. 经常提交以避免长时间锁定行。
3. 避免使用LOCK命令锁定表。
4. 在非高峰期间执行DDL操作,在非高峰期间执行长时间运行的查询或事务。
本节内容参考的博客有:
闪回技术是Oracle强大数据库备份恢复机制的一部分,在数据库发生逻辑错误的时候,闪回技术能提供快速且最小损失的恢复(多数闪回功能都能在数据库联机状态下完成)。需要注意的是,闪回技术旨在快速恢复逻辑错误,对于物理损坏或是介质丢失的错误,闪回技术就回天乏术了,还是得借助于Oracle一些高级的备份恢复工具如RAMN去完成
闪回数据库不使用撤销数据,使用另外一种机制来保留回退所需要的恢复数据,当启用闪回数据库,发生变化的数据块会不断从数据库缓冲区缓存中复制到闪回缓冲区,然后,称为恢复写入器(Recovery Writer)的后台进程会将这些数据刷新到磁盘中的闪回日志文件中。闪回的过程,则是一个 提取闪回日志–>将块映像复制回数据文件 的过程。
在很多数据库中都存在一个自动增长的列,如果现在要想在oracle 中完成自动增长的功能, 则只能依靠序列完成,所有的自动增长操作,需要用户手工完成处理。
语法
CREATE SEQUENCE *** -- 创建序列名称 [INCREMENT BY n] -- 递增的序列值是 n 如果 n 是正数就递增,如果是负数就递减 默认是 1 [START WITH n] --从第几个值开始,递增默认是 minvalue 递减是 maxvalue [{MAXVALUE n | NOMAXVALUE}] -- 最大值 [{MINVALUE n | NOMINVALUE}] -- 最小值 [{CYCLE | NOCYCLE}] --循环/不循环 [{CACHE n | NOCACHE}];--分配并存入到内存中(缓存) ----------------------------------范例-------------------------------------- CREATE SEQUENCE "PID" MINVALUE 1 MAXVALUE 9999999999999999999999999999 INCREMENT BY 1 START WITH 61 CACHE 20 NOORDER --不排序 NOCYCLE; -- 不循环 --------------------------------------------------------------------------- --范例:创建一个pid的序列,验证自动增长的操作: CREATE SEQUENCE pid; 序列创建完成之后,所有的自动增长应该由用户自己处理,所以在序列中提供了以下的两种操作: nextval :取得序列的下一个内容 currval :取得序列的当前内容 select pid.nextval from dual; select pid.currval from dual; 在插入数据时需要自增的主键中可以这样使用: INSERT INTO person VALUES(pid.nextval,'张三',1,TO_DATE('2000-01-01','yyyy-MM-dd')); 在实际项目中每一张表会配一个序列,但是表和序列是没有必然的联系的,一个序列被哪一张表使用都可以,但是我们一般都是一张表用一个序列。序列的管理一般使用工具来管理。
数据库开发中,约束是必不可少,使用约束可以更好的保证数据的完整性
主键约束都是在id上使用,而且本身已经默认了内容不能为空,可以在建表的时候指定。
例:创建一个带有主键约束的表:
create table person( pid number(10) primary key, -- 主键约束关键字primary key name varchar2(10), gender number(1) default 1, -- default关键字代表默认值 birthday date );这是系统自动分配的约束的名字
1.主键不可重复
执行SQL语句:(注意:此时表中已经存在id为1的数据)
INSERT INTO person VALUES(1,'张三',1,TO_DATE('2000-01-01','yyyy-MM-dd'));2.主键不可为空
INSERT INTO person VALUES(null,'张三',1,TO_DATE('2000-01-01','yyyy-MM-dd'));我们可以自己来指定主键约束的名字
必如:
create table person01( pid number(10), name varchar2(10), gender number(1) default 1, birthday date, constraint person_pk_pid primary key(pid) )自定义的主键约束名字
使用非空约束,可以使指定的字段不可以为空**。
范例:建立一张pid和name不可以为空的表
create table person02( pid number(10) not null, -- 不能为空 name varchar2(10) not null, -- 不能为空 gender number(1) , birthday date ); --执行sql INSERT INTO person02 VALUES(1,null,1,TO_DATE('2000-01-01','yyyy-MM-dd'));表中的一个字段的内容是唯一的
范例:创建一个name是唯一的表
create table person03( pid number(10) , name varchar2(10) unique, --name唯一 gender number(1) , birthday date ); --分别执行sql INSERT INTO person02 VALUES(1,'张三',1,TO_DATE('2000-01-01','yyyy-MM-dd')); INSERT INTO person02 VALUES(2,'张三',1,TO_DATE('2000-01-01','yyyy-MM-dd'));唯一约束的名字也可以自定义
create table person04( pid number(10) , name varchar2(10), gender number(1) , birthday date, constraint person_name_uk unique(name) --自定义唯一约束名字 );使用检查约束可以来约束字段值的合法范围。
范例:创建一张表性别只能是1或2
create table person05( pid number(10) , name varchar2(10), gender number(1) check(gender in (0, 1)), --检查约束 值只能是 0 或者 1 birthday date ); -- 执行sql INSERT INTO person05 VALUES(1,'张三',2,TO_DATE('2000-01-01','yyyy-MM-dd'));检查约束也可以自定义
create table person06( pid number(10) , name varchar2(10), gender number(1), birthday date, constraint person_gender_ck check(gender in (1,2)) -- 自定义检查约束名字 );上面所讲的都是单表的约束,外键是两张表的约束,可以保证关联数据的完整性。
范例:创建两张表,一张英雄表hero,一张是英雄天赋表hero_genius,英雄和英雄天赋是一对多的关系
create table hero( hid number(10) , -- id name varchar2(10), -- 英雄姓名 gender number(1) , -- 英雄性别 birthday date, -- 出生日期 constraint hero_hid_pk primary key(hid) -- 主键 ); CREATE TABLE hero_job ( jid NUMBER(10), -- 职业id hid NUMBER(10), -- 英雄id job VARCHAR2(10), -- 英雄职业 CONSTRAINT hero_job_jid_pk PRIMARY KEY ( jid ) ); -- 分别执行sql: INSERT INTO hero VALUES (1,'赵云',1,TO_DATE('2000-01-01', 'yyyy-MM-dd')); INSERT INTO hero_job VALUES (1,2,'战士');在两张表中插入如上两条数据,如果没有用外键约束我们发现在hero_job表中插入的hid在hero表中不存在时,可以添加成功,这样在数据库中就产生了脏数据。此时需要外键来约束它
使用外键约束再次建表:
create table hero( hid number(10) , -- id name varchar2(10), -- 英雄姓名 gender number(1) , -- 英雄性别 birthday date, -- 出生日期 constraint hero_hid_pk primary key(hid) -- 主键 ); CREATE TABLE hero_job ( jid NUMBER(10), -- 职业id hid NUMBER(10), -- 英雄id job VARCHAR2(10), -- 英雄职业 CONSTRAINT hero_job_jid_pk PRIMARY KEY ( jid ), constraint hero_job_hid_fk foreign key(hid) references hero(hid) ); -- 分别执行sql: INSERT INTO hero VALUES (1,'赵云',1,TO_DATE('2000-01-01', 'yyyy-MM-dd')); INSERT INTO hero_job VALUES (1,2,'战士');结论:当hero_job表中插入的hid表hero中没有时,会报错,不产生脏数据
-- 执行sql DROP TABLE hero;删表时一定先删子表再删主表,如果直接删主表会出现由于约束存在无法删除的问题
-- 当hero_job表和表hero中有关联字段时执行sql DELETE hero WHERE hid = 1; -- 报错 DELETE hero_job WHERE jid= 1; -- 可以删除不报错删除主表的数据可以先删除子表的关联数据,再删主表,也可以使用级联删除
级联删除在外键约束上要加上on delete cascade 如
CREATE TABLE hero_job ( jid NUMBER(10), hid NUMBER(10), job VARCHAR2(10), CONSTRAINT hero_job2_jid_pk PRIMARY KEY ( jid ), constraint hero_job_hid_fk foreign key(hid) references hero(hid) on delete cascade )这样删除主表数据的时候会把子表的关联数据一同删除
总结:外键一定是主表的主键
删表时一定先删子表再删主表,如果直接删主表会出现由于约束存在无法删除的问题
但是可以强制删除drop table hero cascade constraint;(不建议)
删除主表的数据可以先删除子表的关联数据,再删主表,也可以使用级联删除。
当删除主表数据的时候会把字表的关联数据一同删除
在查询的结果列中可以使用别名
Select 列名 别名,列名 别名,... from emp; 例: Select empno 部门编号,ename 部门名称 from emp;mysql数据库中:
SELECT concat(pid,'是id',name,'是名字') FROM person;oracle数据库中:
select '编号是' || empno || '的人,姓名是:' || ename from emp;查询每个雇员的年薪
select ename,sal*12 年薪 from emp;Sql中支持四则运算“+,-,*,/“
语法:
select * from 表名 where 条件 或者 select 列名1,列名2,... from 表名 where 条件 例:查询工资大于1500的所有雇员例:查询工资大于1500的所有雇员
SELECT * from emp WHERE SAL>1500;示例:查询每月能得到奖金(comm)的雇员
分析:只要字段中存在内容表示不为空,如果不存在内容就是null,
不为空语法:
where 列名 IS NOT NULL; 例:查询出emp表中comm(奖金)不为空的数据 select * from emp WHERE comm is not null;为空语法:
where 列名 IS NULL; 例:查询出emp表中comm(奖金)为空的数据 select * from emp WHERE comm is null;范例:查询工资大于1500并且有奖金领取的雇员
分析:多个查询条件同时满足之间使用‘AND’
select * from emp WHERE comm is NOT null and sal>1500;范例:查询工资大于1500或者有奖金的雇员
分析:多个查询条件或满足,条件之间使用“OR”
select * from emp WHERE comm is NOT null or sal>1500;范例:查询工资不大于1500和没有奖金的人
语法:NOT(查询条件)
select * from emp WHERE comm is null and not(sal>1500);范例:基本工资大于1500但是小于3000的全部雇员
分析:sal>1500, sal<3000
select * from emp WHERE sal>1500 and sal <3000;Between and相当于sal > =1500 and sal <= 3000
select * from emp WHERE sal between 1500 and 3000;范例:查询1981-1-1到1981-12-31号入职的雇员
分析:between and 不仅可以使用在数值之间,也可以用在日期的区间
select * from emp WHERE HIREDATE between TO_DATE('1981-1-1', 'yyyy-MM-dd') and TO_DATE('1981-12-31', 'yyyy-MM-dd');范例:查询雇员名字叫SMITH的雇员
在oracle中的查询条件中查询条件的值是区分大小写的
select * from emp WHERE ename='SMITH'; select * from emp WHERE ename='smith';范例:查询雇员编号是7369,7499,7521的雇员编号的具体信息
如果使用之前的做法可以使用OR关键字
select * from emp WHERE EMPNO=7369 or EMPNO=7499 or EMPNO=7521;实际上,此时指定了查询范围,那么sql可以使用IN关键字
语法:
列名 IN (值1,值2,....) 列名 NOT IN (值1, 值2,...) select * from emp WHERE EMPNO in (7369,7499,7521);其中的值不仅可以是数值类型也可以是字符串
范例:查询雇员姓名是’SMITH’,’ALLEN’,’WARD’的雇员具体信息
select * from emp WHERE ename in ('SMITH','ALLEN','WARD');在常用的站点中经常会有模糊查询,即:输入一个关键字,把符合的内容全部的查询出来,在sql中使用LIKE语句完成。
在LIKE中主要使用以下两种通配符
“%”:可以匹配任意长度的内容
“_”:可以匹配一个长度的内容
范例:查询出所有雇员姓名中第二个字符包含“M”的雇员
select * from emp WHERE ename LIKE('_M%')在LIKE中如果没有关键字表示查询全部
select * from emp WHERE ename LIKE('%%')查询名字中带有“M”的雇员
select * from emp WHERE ename LIKE('%M%')在oracle中不等号的用法可以有两种形式“<>”和“!=”
范例:查询雇员编号不是7369的雇员信息
select * from emp WHERE empno <> 7369; select * from emp WHERE empno != 7369; 在sql中可以使用ORDER BY对查询结果进行排序 ,order by 子句可以将查询的结果,按照一定的顺序进行排序。当排序列的数据类型是字符串时,将按照字符串在字母表中的顺序进行排序
语法:DESC 降序 ASC 升序
SELECT * FROM 表名 {WEHRE 查询条件} ORDER BY 列名1 ASC|DESC,列名2...ASC|DESC 或者: SELECT 列名 FROM 表名 {WEHRE 查询条件} ORDER BY 列名1 ASC|DESC,列名2...ASC|DESC范例:查询雇员的工资从低到高
分析:ORDER BY 列名 默认的排序规则是升序排列,可以不指定ASC,如果按着降序排列必须指定DESC
select * from emp ORDER BY SAL -- 升序范例:查询雇员的工资从高到低
select * from emp ORDER BY SAL DESC -- 降序 在使用SQL中的ORDER BY按照表中的多个列对表做排序是,会按照第一个列的排序条件作为排序基准,当第一个列的值都相同时,才会按照后面的列的排序条件作为排序基准; 这里不再演示。
使用多列进行排序时,左边的列的排序优先级高于右侧
如果存在多个排序字段可以用逗号分隔
select * from emp ORDER BY SAL ASC, HIREDATE desc; --先对SAL进行排序,再满足SAL升序条件下再对HIREDATE进行排序注意ORDER BY语句要放在sql的最后执行。
排序 order by 经验:当排序时存在null时就会产生问题 nulls first , nulls last
如果Order by 中指定了表达式Nulls first则表示null值的记录将排在最前(不管是asc 还是 desc)
如果Order by 中指定了表达式Nulls last则表示null值的记录将排在最后 (不管是asc 还是 desc)
范例:查询雇员的工资从低到高(此处我加了两个工资为空的雇员)
select * from emp order by sal nulls first; -- null记录排在最前范例:查询雇员的工资从高到低
select * from emp order by sal DESC nulls last; -- null记录排在最后 group by 子句用于对记录集合进行分组,一旦使用分组之后,select 语句的真实操作目标为各个分组数据,每次循环处理的也是各个分组,而不是单条记录。
语法:
SELECT column_name,... | * FROM table_name,... WHERE expressions group by column_name,... SELECT 列1,列2,... FROM 表名 WHERE 表达式 group by 列,...范例:查询出每个部门编号各有多少人:
SELECT DEPTNO,count(*) FROM emp GROUP BY DEPTNO;范例:查询出部门编号相等的雇员各有多少人并按照升序排列
SELECT DEPTNO,count(*) FROM emp GROUP BY DEPTNO ORDER BY DEPTNO;group by语句中select指定的字段必须是“分组依据字段”。
对于需要分组查询的子句,ORDER BY 需要置于groub by 后面,并且排序字段需要是 groub by 的分组字段
Having 子句用于对查询表结构进行处理,可用于分组之后,多和group by一起使用,放在group by之后,可包含聚合函数。
Having子句可用于group by之后,语法类似于where子句,where子句用于group by 之前。
having子句种的字段必须是上层结果表中有的。
having子句中可以使用聚合函数,where语句中不可以使用聚合函数
Having子句执行顺序:where语句>group by>聚合函数>Having
Mysql中group by之后会自动对分组的数据进行聚合处理,选取每组的第一个。(Mysql语法不严谨,Hive中未被分组的字段就必须采用聚合处理。)
集合结果指定条件 注:HAVING子句中能够使用三种要素:常数,聚合函数,GROUP BY子句中指定的列名(聚合建) HAVING子句: 用having就一定要和group by连用, 用group by不一有having(它只是一个筛选条件用的)
语法:
SELECT column_name,.. | * FROM table_name,... WHERE expressions groub by column_name,... having expressions范例:查询出部门编号相等的雇员各有多少人,并且部门编号大于10按照升序排列
SELECT DEPTNO,count(*) c FROM emp GROUP BY DEPTNO HAVING DEPTNO >10 ORDER BY DEPTNO ;dual是伪表
把小写的字符转换成大小的字符
SELECT upper('smith') FROM dual;把大写字符变成小写字符
SELECT lower('SMITH') FROM dual;把首字母大写
SELECT initcap('smith') FROM dual;字符串的连接可以使用concat也可以使用“||”建议使用“||”
SELECT concat('Hello','World') FROM dual; SELECT 'Hello'||'World' FROM dual;concat函数执行结果
||执行结果
字符串的截取,使用substr,第一个参数是源字符串,第二个参数是开始索引,第三个参数长度,开始的索引使用1和0效果相同
SELECT substr('HelloWorld',1,6) FROM dual; SELECT substr('HelloWorld',0,6) FROM dual;获取字符串的长度
SELECT length('HelloWorld') FROM dual;字符串替换,第一个参数是源字符串,第二个参数被替换的字符串,第三个是替换字符串
SELECT replace('HelloWorld','l','x') FROM dual;四舍五入函数:round()
默认情况下round四舍五入取整,可以自己指定保留的位数。
SELECT round(12.534) FROM dual; -- 四舍五入取整 SELECT round(12.534,2) FROM dual; -- 保留两位小数数字截取函数:trunc(),默认全部去掉小数,也可以指定保留的位数
SELECT trunc(12.534) FROM dual; -- 取整 SELECT trunc(12.534,2) FROM dual; -- 保留两位小数取整函数
向上取整ceil(m)表示取大于等于m的最小整数
SELECT ceil(12.534) FROM dual; SELECT ceil(-12.534) FROM dual;
向下取整floor(m)表示取小于等于m的最大整数
SELECT floor(12.534) FROM dual; SELECT floor(-12.534) FROM dual;
取余数mod()
SELECT mod(10,3) FROM dual; -- 10/3 取余数Oracle中提供了很多和日期相关的函数,包括日期的加减,在日期加减时有一些规律
日期 – 数字 = 日期
日期 + 数字 = 日期
日期 – 日期 = 数字
select to_char(sysdate, 'yyyy' ) from dual; -- 年 select to_char(sysdate, 'MM' ) from dual; -- 月 select to_char(sysdate, 'dd' ) from dual; -- 日 select to_char(sysdate, 'Q') from dual; -- 季 select to_char(sysdate, 'iw') from dual; -- 周 select to_char(sysdate,'hh') from dual; -- 12小时计时 select to_char(sysdate,'hh24') from dual -- 24小时计时 select to_char(sysdate,'mi') from dual; -- 分钟 select to_char(sysdate,'ss') from dual; -- 秒 SELECT TO_CHAR(sysdate,'yyyy-MM-dd hh24:mi:ss') from dual; -- 获取当前时间 SELECT SYSDATE FROM dual; -- 获取当前时间范例:查询雇员的进入公司的周数。
SELECT ename,round((sysdate-hiredate)/7) from emp;获得两个时间段中的月数:month_between(d,e) 日期d与e之间的月份数,e先于d
存在小数用round()函数取整
范例:查询所有雇员进入公司的月数
SELECT ename,round(months_between(sysdate,hiredate)) from emp; select months_between(date'2019-05-29',date'2019-01-29') from dual;获得几个月后的日期:add_months(d,n) 日期d加n个月
范例:求出三个月后的日期
SELECT add_months(sysdate,3) from dual;两个日期间的天数
select floor(sysdate - to_date('20161010','yyyymmdd')) strTime from dual;日期函数
add_months(d,n) --日期d加n个月 last_day(d) --包含d的月份的最后一天的日期 months_between(d,e) --日期d与e之间的月份数,e先于d new_time(d,a,b) --a时区的日期和时间d在b时区的日期和时间 next_day(d,day) --比日期d晚,由day指定的周几的日期 sysdate `--当前的系统日期和时间 greatest(d1,d2,...dn) --给出的日期列表中最后的日期 least(d1,k2,...dn) --给出的日期列表中最早的日期 to_char(d [,fmt]) --日期d按fmt指定的格式转变成字符串 to_date(st [,fmt]) --字符串st按fmt指定的格式转成日期值,若fmt忽略,st要用缺省格式 round(d [,fmt]) --日期d按fmt指定格式舍入到最近的日期 trunc(d [,fmt]) --日期d按fmt指定格式截断到最近的日期附:TO_CHAR():字符串转换函数
1).用途:将日期类型转换为字符类型的函数,主要用于查询,也可以进行删除使用。to_char(p1,p2) 表示将日期p1按照p2的格式转换成字符串类型
SELECT ename,to_char(hiredate,'yyyy-MM-dd') FROM emp;2).在结果中10以下的月前面被补了前导零,可以使用fm去掉前导零
SELECT ename,to_char(hiredate,'fmyyyy-MM-dd') FROM emp;3).TO_CHAR还可以给数字做格式化
范例:把雇员的工资按三位用“,”分隔,在oracle中“9”代表一位数字
SELECT ename,to_char(sal,'99,999') FROM emp;4).如果在钱的前面加上国家的符号可以使用“$”代表是美元,如果要使用本地的钱的单位使用“L”
SELECT ename,to_char(sal,'L99,999') FROM emp;to_date(p1,p2) 表示字符串p1按照p2的格式转成日期类型。
用途:将字符类型转化成日期类型的函数,主要用于插入、查询和修改。
SELECT TO_DATE('2020-05-23 14:55:45','yyyy-MM-dd hh24:mi:ss') FROM dual;to_number:数值转换函数
to_number可以把字符串转换成数值
SELECT to_number('10')+to_number('10') FROM dual;NVL(a,b)就是判断a是否是NULL,如果不是返回a的值,如果是返回b的值 通过查询获得某个字段的合计值,如果这个值为null将给出一个预设的默认值
范例:查询所有的雇员的年薪
SELECT ename,(SAL*12+comm) FROM emp;发现很多员工的年薪是空的,原因是很多员工的奖金是null,null和任何数值计算都是null,这时我们可以使用nvl来处理。
SELECT ename,nvl(comm,0),(SAL*12+nvl(comm,0)) FROM emp;该函数类似if…else if…esle
语法:
decode(col/expression, [search1,result1],[search2, result2]....[default]) --理解成:decode(value,if1,then1,if2,then2,if3,then3,...,else)Col/expression:列名或表达式 Search 1,search 2…:用于比较的条件 Result 1, result 2…:返回值 如果col/expression和Search[i]匹配就返回result[i],否则返回default的默认值
Value 代表某个表的任何类型的任意列或一个通过计算所得的任何结果。当每个value值被测试,如果value的值为if1,Decode 函数的结果是then1;如果value等于if2,Decode函数结果是then2;等等。事实上,可以给出多个if/then 配对。如果value结果不等于给出的任何配对时,Decode 结果就返回else 。需要注意的是,这里的if、then及else 都可以是函数或计算表达式。
范例:当是列名时
SELECT name, decode(gender,1,'男',0,'女') FROM person;范例:当是表达式时
SELECT decode(1,1,'我是1',2,'我是2','我是无名') FROM dual; SELECT decode(2,1,'我是1',2,'我是2','我是无名') FROM dual;范例:查询出所有雇员的职位的中文名
SELECT ename, decode( job, 'CLERK', '记账员', 'SALESMAN', '销售员', 'MANAGER', '经理', 'ANALYST', '分析员', 'PRESIDENT', '董事长', '无业' ) FROM emp;语法:
CASE 表达式或列 WHEN comparison_expr1 THEN 返回值 [WHEN comparison_expr2 THEN 返回值 WHEN comparison_exprn THEN 返回值 ELSE else_expr] END范例:查询出所有雇员的职位的中文名
--------------第一种写法------------- SELECT ename, CASE job WHEN 'CLERK' THEN '记账员' WHEN 'SALESMAN' THEN '销售员' WHEN 'MANAGER' THEN '经理' WHEN 'ANALYST' THEN '分析员' WHEN 'PRESIDENT' THEN '董事长' ELSE '无业' END FROM emp; --------------第二种写法------------- SELECT ename, CASE WHEN job ='CLERK' THEN '记账员' WHEN job ='SALESMAN' THEN '销售员' WHEN job ='MANAGER' THEN '经理' WHEN job ='ANALYST' THEN '分析员' WHEN job ='PRESIDENT' THEN '董事长' ELSE '无业' END FROM emp;范例:查询出所有员工的记录数
SELECT count(*) FROM emp; SELECT count(ename) FROM emp;范例:查询出来员工最低工资
SELECT min(sal) FROM emp;范例:查询出员工的最高工资
SELECT max(sal) FROM emp;范例:查询出员工的平均工资
SELECT avg(sal) FROM emp;范例:查询出20号部门的员工的工资总和
SELECT sum(sal) FROM emp WHERE deptno=20;一条语句包含我们经常用到的一些关键字,select,from,where,group by,order by,它的执行顺序如下:
先执行from关键字后面的语句,明确数据的来源,它是从哪张表取来的。
接着执行where关键字后面的语句,对数据进行筛选。
再接着执行group by后面的语句,对数据进行分组分类。
然后执行select后面的语句,也就是对处理好的数据,具体要取哪一部分。
最后执行order by后面的语句,对最终的结果进行排序。
表结构:
person_01表:
person_02表:
UNION:将多个查询的结果组合到一个查询结果之中,并去掉重复值,同时进行默认规则的排序;
UNION ALL:将多个查询结果组合到一个查询结果中。包括重复值,不进行排序;
语法结构:UNION
select 表1的列1, 表1的列2,... from 表1 union select表2的列1, 表2的列2,... from表2;范例:UNION
SELECT * FROM PERSON_01 UNION SELECT * FROM PERSON_02;范例:UNION ALL
SELECT * FROM PERSON_01 UNION All SELECT * FROM PERSON_02;其中表1的列1和表1的列2是来自于表1的两列,表2的列1和表2的列2是来自于表2的两列,需要注意的是,如果union前面是n列,那么后面也必须是n列,即union前后列数必须相同。而且查询结果的列名是按照union前面n列的名称命名(如例1)。
INTERSECT:返回多个查询结果同样的部分
语法结构:
select 表1的列1, 表1的列2,... from 表1 intersect select 表2的列1, 表2的列2,... from 表2;范例:
SELECT * FROM PERSON_01 INTERSECT SELECT * FROM PERSON_02;MINUS:返回两个查询结果的差集
语法结构:
select 表1的列1, 表1的列2,... from 表1 MINUS select 表2的列1, 表2的列2,... from 表2;范例:
SELECT * FROM PERSON_01 MINUS SELECT * FROM PERSON_02;**1.**保证两个sql查询的列数是个数一致的
**2.**保证两个sql查询的列的数据类型是一致的
**3.**保证两个sql查询的列是相同的,否则查询的结果是无意义的
使用一张以上的表做查询就是多表查询
语法:
SELECT {DISTINCT} *|列名.. FROM 表名 别名,表名1 别名 {WHERE 限制条件 ORDER BY 排序字段 ASC|DESC...}范例:查询员工表和部门表
SELECT * FROM emp,dept;我们发现产生的记录数是56条,我们还会发现emp表是14条,dept表是4条,56正是emp表和dept表的记录数的乘积,我们称其为笛卡尔积。
如果多张表进行一起查询而且每张表的数据很大的话笛卡尔积就会变得非常大,对性能造成影响,想要去掉笛卡尔积我们需要关联查询。
在两张表中我们发现有一个共同的字段是depno,depno就是两张表的关联的字段,我们可以使用这个字段来做限制条件,两张表的关联查询字段一般是其中一张表的主键,另一张表的外键。
SELECT * FROM emp e,dept d WHERE e.DEPTNO=d.DEPTNO; -- 多表查询我们可以为每一张表起一个别名关联之后我们发现数据条数是14条,不在是56条。
范例:查询出雇员的编号,姓名,部门的编号和名称,地址
SELECT e.EMPNO,e.ENAME,d.DEPTNO,d.DNAME,d.LOC FROM emp e,dept d WHERE e.DEPTNO=d.DEPTNO;范例:查询出每个员工的上级领导
分析:emp表中的mgr字段是当前雇员的上级领导的编号,所以该字段对emp表产生了自身关联,可以使用mgr字段和empno来关联
SELECT e.EMPNO,e.ENAME,e1.EMPNO,e1.ENAME FROM emp e,emp e1 WHERE e.MGR = e1.EMPNO;范例:在上一个例子的基础上查询该员工的部门名称
分析:只要在上一个例子基础上再加一张表的关联,使用deptno来做关联字段即可
SELECT e.EMPNO,e.ENAME,e.MGR,e1.EMPNO,e1.ENAME,d.DNAME FROM emp e,emp e1,dept d WHERE e.MGR = e1.EMPNO and e.DEPTNO = d.DEPTNO;范例:查询出每个员工编号,姓名,部门名称,工资等级和他的上级领导的姓名,工资等级
SELECT e.deptno, e.ename, d.dname, decode( s.grade, 1, '一级工资', 2, '二级工资', 3, '三级工资', 4, '四级工资', 5,'五级工资' ) grade, e1.ename, decode( s1.grade, 1, '一级工资', 2, '二级工资', 3, '三级工资', 4, '四级工资', 5,'五级工资' ) grade FROM emp e, emp e1, dept d, salgrade s, salgrade s1 WHERE e.mgr = e1.empno AND e.deptno = d.deptno and e.sal between s.losal and s.hisal and e1.sal between s1.losal and s1.hisal;1.右连接
当我们在做基本连接查询的时候,查询出所有的部门下的员工,我们发现编号为40的部门下没有员工,但是要求把该部门也展示出来,我们发现上面的基本查询是办不到的
经典总结: 有+号的表不全部显示,对面的表全部显示
口诀:
右外连接+在左
左外连接+在右,
有+不全显
无+就全显
SELECT e.EMPNO,e.ENAME,d.DEPTNO,d.DNAME FROM emp e, dept d WHERE e.DEPTNO(+) = d.DEPTNO;使用(+)表示左连接或者右连接,当(+)在左边表的关联条件字段上时是右连接,如果是在右边表的关联条件字段上就是左连接。
范例:查询出所有员工的上级领导
分析:我们发现使用我们以前的做法发现KING的上级领导没有被展示,我们需要使用左右连接把他查询出来
SELECT e.EMPNO,e.ENAME,e1.ENAME FROM emp e, emp e1 WHERE e.MGR = e1.EMPNO(+);ON子句
On相当于where
范例:emp表和dept表联查
SELECT * FROM emp e JOIN dept d on e.DEPTNO = d.DEPTNO;left join
SELECT * FROM emp e left JOIN dept d on e.DEPTNO = d.DEPTNO;right join
SELECT * FROM emp e RIGHT JOIN dept d on e.DEPTNO = d.DEPTNO;分组统计需要使用GROUP BY来分组
语法:
SELECT * |列名 FROM 表名 {WEHRE 查询条件} {GROUP BY 分组字段} ORDER BY 列名1 ASC|DESC,列名2...ASC|DESC范例:查询每个部门的人数
SELECT DEPTNO,count(ename) FROM emp e GROUP BY DEPTNO;范例:查询出每个部门的平均工资
SELECT DEPTNO,avg(sal) FROM emp e GROUP BY DEPTNO;如果我们想查询出来部门编号,和部门下的人数
SELECT DEPTNO,count(ename) FROM emp;我们发现报了一个ORA-00937的错误
SELECT DEPTNO,ENAME,count(ename) FROM emp e GROUP BY DEPTNO;注意:
1. 如果使用分组函数,不使用GROUP BY 只可以查询出来分组函数的值
2. 如果使用分组函数,SQL只可以把GOURP BY分组条件字段和分组函数查询出来,不能有其他字段
范例:按部门分组,查询出部门名称和部门的员工数量
SELECT d.DEPTNO,d.DNAME,count(ename) FROM emp e,dept d WHERE e.DEPTNO=d.DEPTNO GROUP BY d.DNAME,d.DEPTNO;范例:查询出部门人数大于5人的部门
分析:需要给count(ename)加条件,此时在本查询中不能使用where,可以使用HAVING
SELECT d.DNAME,count(ename) FROM emp e,dept d WHERE e.DEPTNO=d.DEPTNO GROUP BY d.DNAME HAVING COUNT(ename)>5;范例:查询出部门平均工资大于2000的部门
SELECT d.DNAME,AVG(SAL) FROM emp e,dept d WHERE e.DEPTNO=d.DEPTNO GROUP BY d.DNAME HAVING AVG(SAL)>2000;在一个查询的内部还包括另一个查询,则此查询称为子查询。
Sql的任何位置都可以加入子查询。
范例:查询比7654编号工资高的雇员
分析:查询出7654编号员工的工资是多少,把它作为条件
SELECT * FROM emp e WHERE e.SAL > (SELECT sal FROM emp e1 WHERE e1.EMPNO=7654);所有的子查询必须在“()”中编写
子查询在操作中有三类:
单列子查询:返回的结果是一列的一个内容
单行子查询:返回多个列,有可能是一个完整的记录
多行子查询:返回多条记录
范例:查询出比雇员7654的工资高,同时从事和7788的工作一样的员工
SELECT * FROM emp e WHERE e.SAL > (SELECT sal FROM emp e1 WHERE e1.EMPNO=7654) and e.JOB=(SELECT e2.job FROM emp e2 WHERE e2.EMPNO=7788);范例:要求查询每个部门的最低工资和最低工资的雇员和部门名称
SELECT em.ENAME,d.DNAME,a.m FROM emp em, dept d, (SELECT d.DEPTNO,min(sal) m FROM emp e,dept d WHERE e.DEPTNO = d.DEPTNO GROUP BY d.DEPTNO) a WHERE d.DEPTNO = a.DEPTNO and em.sal = a.m;在返回多条记录的子查询可以把它的结果集当做一张表,给起个别名, 如图中的a。
范例:查询出每个部门最低工资的员工
SELECT * FROM emp WHERE sal in (SELECT min(sal) m FROM emp e GROUP BY DEPTNO);exists (sql 返回结果集为真)
not exists (sql 不返回结果集为真)
范例:查询出有员工的部门有哪些?
SELECT * FROM dept WHERE DEPTNO in (SELECT distinct deptno from emp); --distinct去重 SELECT * from dept d WHERE exists (SELECT * from emp e WHERE e.deptno = d.deptno);范例:查询出没有员工的部门
SELECT * from dept d WHERE not exists (SELECT * from emp e WHERE e.deptno = d.deptno);语法:
Insert into 表名(列1,列2,列3,….) Select 列1,列2,列3,…. From 表名 Where条件注意:
此时不要写values
插入的列名与查询的列名要一致
范例:把person_01表中pid=1的数据添加到person_02表中
INSERT INTO PERSON_02 SELECT * FROM PERSON_01 WHERE pid= 1;等
Rownum:表示行号,实际上此是一个列,但是这个列是一个伪列,此列可以在每张表中出现。
关于rownum
rownum永远按照默认的顺序生成
rownum只能使用< <=;不能使用> >=
rownum永远从1开始
范例:查询员工表中工资最高的前三名
错误写法
select rownum,empno,ename,sal from emp WHERE rownum<=3 order by sal desc ;正确的写法
我们可以先排序,在根据行号进行查询
SELECT a.EMPNO,a.ename,a.SAL,rownum from (SELECT * FROM emp ORDER BY sal DESC) a WHERE rownum<=3;==rownum只能使用< <=;不能使用> > ===
检测是否能使用< <= 和> >=
select rownum,empno,ename,sal from emp where rownum<=8; -- 能够查询出数据 select rownum,empno,ename,sal from emp where rownum>=5; -- 不能够查询出数据 select rownum,empno,ename,sal from emp where rownum>=5 and rownum<=8; -- 不能够查询出数据范例:查询emp表带有rownum列
select rownum, t.* from emp t;我们可以根据rownum来取结果集的前几行,比如前5行
select rownum, t.* from emp t WHERE rownum<=5; 但是我们不能取到中间几行,因为rownum不支持大于号,只支持小于号,如果想实现我们的需求怎么办呢?答案是使用子查询,也正是oracle分页的做法。
第一种: 查询第5到11条
select * from(select rownum r ,emp.* from emp) a WHERE a.r>=5 and a.r<=11; --或者-- SELECT p.* FROM(SELECT e.*,rownum r FROM (SELECT * FROM emp) e) p WHERE r>=5 and r<=11;第二种: 查询第5到11条
SELECT a.* FROM (SELECT emp.*,rownum r FROM emp WHERE rownum<=11) a WHERE a.r >=5; ---或者--- select * from (select rownum rm, a.* from (select * from emp) a where rownum <= 11) b where b.rm >= 6;视图就是封装了一条复杂查询的语句。
语法1:
CREATE VIEW 视图名称 AS 子查询范例:建立一个视图,此视图包括了20部门的全部员工信息
--注意:scott用户无权限 此操作是在system用户下进行的 create view emp_dept20 as SELECT * FROM emp WHERE DEPTNO = 20;
视图创建完毕就可以使用视图来查询,查询出来的都是20部门的员工
SELECT * from emp_dept20;语法2:
CREATE OR REPLACE VIEW 视图名称 AS 子查询如果视图已经存在我们可以使用语法2来创建视图,这样已有的视图会被覆盖。
create or replace view emp_dept20 as SELECT * FROM emp WHERE DEPTNO = 20;修改视图字段
UPDATE emp_dept20 set ENAME = '史密斯' WHERE EMPNO =7369;我们可以设置视图为只读。
语法:
CREATE OR REPLACE VIEW 视图名称 AS 子查询 WITH READ ONLY create or replace view emp_dept20 as SELECT * FROM emp WHERE DEPTNO = 20 WITH READ ONLY;再次执行修改语句
UPDATE emp_dept20 set ENAME = '张三' WHERE EMPNO =7369;==索引是用于加速数据存取的数据对象。==合理的使用索引可以大大降低i/o 次数,从而
提高数据访问性能。索引有很多种我们主要介绍常用的几种:
为什么添加了索引之后,会加快查询速度呢?
****图书馆****:如果杂乱地放书的话检索起来就非常困难,所以将书分类,然后再建一个箱子,箱
子里面放卡片,卡片里面可以按类查询,按书名查或者类别查,这样的话速度会快很多很多,
这个就有点像索引。索引的好处就是提高你找到书的速度,但是正是因为你建了索引,就应该有人专门来维护索引,维护索引是要有时间精力的开销的,也就是说索引是不能乱建的,所以建索引有个原则:如果有一个字段如果不经常查询,就不要去建索引。现在把书变成我们的表,把卡片变成我们的索引,就知道为什么索引会快,为什么会有开销。
主键 唯一
单列索引是基于单个列所建立的索引,
语法:
CREATE index 索引名 on 表名(列名)复合索引是基于两个列或多个列的索引。在同一张表上可以有多个索引,但是
要求列的组合必须不同,比如:
Create index emp_idx1 on emp(ename,job); Create index emp_idx1 on emp(job,ename);范例:给person表的name建立索引
create index pname_index on person(name);范例:给person表创建一个name和gender的索引
create index pname_gender_index on person(name, gender);rowid 行地址
ROWID是ORACLE中的一个重要的概念。用于定位数据库中一条记录的一个相对唯一地址值。通常情况下,该值在该行数据插入到数据库表时即被确定且唯一。ROWID它是一个伪列,它并不实际存在于表中。它是ORACLE在读取表中数据行时,根据每一行数据的物理地址信息编码而成的一个伪列。所以根据一行数据的ROWID能找到一行数据的物理地址信息。从而快速地定位到数据行。数据库的大多数操作都是通过ROWID来完成的,而且使用ROWID来进行单记录定位速度是最快的。
范例:
select rowid,ename,sal from emp;根据行地址查找数据
select * from emp WHERE rowid = 'AAAR3sAAEAAAACUAAA';查看索引的性能
查看执行计划
explain plan for select * from person WHERE name = '张三'; select * from table(dbms_xplan.display); -- 两条sql 需要在同一窗口执行执行计划的常用列字段解释:
基数(Rows):Oracle估计的当前操作的返回结果集行数
字节(Bytes):执行该步骤后返回的字节数
耗费(COST)、CPU耗费:Oracle估计的该步骤的执行成本,用于说明SQL执行的代价,理论上越小越好(该值可能与实际有出入)
时间(Time):Oracle估计的当前操作所需的时间
索引的使用原则:
在大表上建立索引才有意义
在where子句后面或者是连接条件上的字段建立索引(经常使用的列才建索引)
列中数据分布很广
4.表经常被访问,且数据量还很大访问的数据大概占总数据的3%-4%
不要创建索引的情况:
表很小
列不经常作为条件出现在where语句中
查询的数据大于2%到4%
表经常更新
原理参考:二叉树 BTree B+树
procedure [prə’siːdʒə] n. 程序,手续;步骤
language ['læŋgwɪdʒ] n. 语言;语言文字;表达能力
declare [dɪ’kleə] vt. 宣布,声明;断言,宣称vi. 声明,宣布
begin [bɪ’gɪn] vt. 开始
什么是PL/SQL?
PL/SQL(Procedure Language/SQL)
PLSQL是Oracle对sql语言的过程化扩展,指在SQL命令语言中增加了过程处理语句(如分支、循环等),使SQL语言具有过程处理能力。把SQL语言的数据操纵能力与过程语言的数据处理能力结合起来,使得PLSQL面向过程但比过程语言简单、高效、灵活和实用。
优点:
1.**支持SQL,**SQL是访问数据库的标准语言,通过SQL命令,用户可以操纵数据库的数据。PL/SQL支持所有的SQL数据操纵命令、游标控制命令、事务控制命令、SQL函数、运算符和伪列。同时PL/SQL和SQL语言紧密集成,PL/SQL支持所有的SQL数据类型和NULL值。
2.支持面向对象编程,PL/SQL支持面向对象的编程,在PL/SQL中可以创建类型,可以对类型进行继承,可以在子程序中重载方法等。
3.更好的性能,SQL是非过程语言,只能一条一条的执行,而PL/SQL把一个PL/SQL统一进行编译后执行,同时还可以把编译好的PL/SQL块存储起来,以备重用,减少了应用程序和服务器之间的通 信时间,所以PL/SQL是高效而快速的。
4.可移植性,使用PL/SQL编写的应用程序语言,可以移植到任何操作平台的ORACLE服务器,同时还可以编写可移植程序库,在不同环境中使用。
5.安全性,可以通过存储过程对客户机和服务器之间的应用程序逻辑进行分割,这样可以限制对ORACLE数据库的访问,数据库还可以授权和撤销其他用户的访问权利。
范例1:为职工涨工资,每人涨10%的工资。
update emp set sal=sal*1.1
范例2:例2: 按职工的职称长工资,总裁涨1000元,经理涨800元,其他人员涨400元。
这样的需求我们就无法使用一条SQL来实现,需要借助其他程序来帮助完成,也可以使用pl/sql。
PL/SQL是一种块结构的语言。一个PL/SQL包含了一个或多个逻辑快,逻辑块中可以声明变量、写程序主体、还可以捕获异常和异常处理。每个逻辑快分为三个部分,语法结构如下:
DECLARE 声明 宣布 说明部分 (变量说明,游标说明,例外说明 〕 BEGIN 语句序列 (DML语句〕… EXCEPTION 例外处理语句 End; Eg: DECLARE BEGIN dbms_output.put_line('HelloWorld');/System.out.println(“”); End;注意:
1.PL/SQL是一种编程语言,有自己独有的数据类型,变量声明和赋值以及流程控制语句。
2.对大小写不敏感,但是为了规范:关键字全部大写,其余部分小写。
3.每一条语句以分号结束。
PL/SQL特殊符号说明:
在程序的声明阶段可以来定义常量和变量。
变量的基本类型就是Oracle中的建表时字段的变量如char, varchar2, date, number, boolean, long定义语法:
varl char(15); psal number(9,2);说明变量名、数据类型和长度后用分号结束说明语句。
常量定义:
married constant boolean:=true 引用变量 Myname emp.ename%type;引用型变量,即Myname的类型与emp表中ename列的类型一样
在sql中使用into来赋值
declare emprec emp.ename%type; begin -- 将查询到的语句赋值给emprec select t.ename into emprec from emp t where t.empno = 7369; dbms_output.put_line(emprec); end; 记录型变量 Emprec emp%rowtype 代表一行记录变量分量的引用
declare p emp%rowtype; begin select * into p from emp t where t.empno = 7369; dbms_output.put_line(p.ename || ' ' || p.sal); end;注意 := 赋值符号等价于java中的=号;
= 逻辑等,判断两个值是否相等,等价于java中的==号
开启控制台输出语句:
set serveroutput on;语法1
IF 条件 THEN 语句1; 语句2; END IF;语法2
IF 条件 THEN 语句序列1; ELSE 语句序列 2; END IF;语法3
IF 条件 THEN 语句; ELSIF 条件 THEN 语句; ELSIF 条件 THEN 语句; … ELSE 语句; END IF;范例1:
accept inputNum prompt '请输入一个数字'; declare pnum number := &inputNum; begin if pnum = 1 then dbms_output.put_line('我是1'); elsif pnum = 2 then dbms_output.put_line('我是2'); else dbms_output.put_line('没有输出数字'); end if; end; --&代表地址符,意思是指向num的值 --接收的是字符串类型,此处会发生类型转换范例3:判断人的不同年龄段18岁以下是未成年人,18岁以上40以下是成年人,40以上是老年人
accept num prompt ‘请输入一个年龄’; declare mynum number := # begin if mynum < 18 then dbms_output.put_line('未成年人'); elsif mynum >= 18 and mynum < 40 then dbms_output.put_line('中年人'); elsif mynum >= 40 then dbms_output.put_line('老年人'); end if; end;语法1
WHILE 条件 LOOP .. . total : = total + salary; END LOOP;语法2 (比较常用)
Loop EXIT when 条件; …… End loop语法3
FOR I IN 1 . . 3 LOOP -- ..表示范围操作符 1..3 从1到3 语句序列 ; END LOOP ;范例:输出1到10的数字
--语法1 DECLARE num number :=1; BEGIN WHILE num<=10 LOOP dbms_output.put_line(num); num := num+1; END loop; END; --语法2 DECLARE num NUMBER :=1; BEGIN LOOP EXIT WHEN num>10; dbms_output.put_line(num); num :=num+1; END LOOP; END; --语法3 DECLARE BEGIN FOR num IN 1..10 LOOP dbms_output.put_line(num); END LOOP; END;范例:1到100的数字的和
DECLARE i NUMBER :=1; sums NUMBER :=0; BEGIN WHILE i<=100 LOOP sums:=sums+i; i:=i+1; END LOOP; dbms_output.put_line(sums); END; 在写java程序中有集合的概念,那么在pl/sql中也会用到多条记录,这时候我们就要用到游标,游标可以存储查询返回的多条数据。
语法:
CURSOR 游标名 [ (参数名 数据类型,参数名 数据类型,...)] IS SELECT 语句; 例如:cursor c1 is select ename from emp;游标的使用步骤:
打开游标: open c1; (打开游标执行查询)
取一行游标的值:fetch c1 into pjob; (取一行到变量中)
关闭游标: close c1;(关闭游标释放资源)
游标的结束方式 exit when c1%notfound
注意: 上面的pjob必须与emp表中的job列类型一致:
定义:pjob emp.empjob%type;
范例1:使用游标方式输出emp表中的员工编号和姓名
declare cursor pc is select * from emp; pemp emp%rowtype; begin open pc; loop fetch pc into pemp; exit when pc%notfound; dbms_output.put_line(pemp.empno || ' ' || pemp.ename); end loop; close pc; end;范例2:写一段PL/SQL程序,为部门号为20的员工涨工资。
declare cursor pc(dno emp.deptno%type) is select empno from emp where deptno = dno; pno emp.empno%type; begin open pc(20); loop fetch pc into pno; exit when pc%notfound; update emp t set t.sal = t.sal + 1000 where t.empno = pno; end loop; close pc; end;异常是程序设计语言提供的一种功能,用来增强程序的健壮性和容错性。
系统定义异常
no_data_found (没有找到数据)
too_many_rows (select …into语句匹配多个行)
zero_divide ( 被零除)
value_error (算术或转换错误)
timeout_on_resource (在等待资源时发生超时)
范例1:写出被0除的异常的plsql程序
declare pnum number; begin pnum := 1 / 0; exception when zero_divide then dbms_output.put_line('被0除'); when value_error then dbms_output.put_line('数值转换错误'); when others then dbms_output.put_line('其他错误'); end;用户也可以自定义异常,在声明中来定义异常
DECLARE No_data exception; 如果遇到异常我们要通过raise语句将异常抛出 raise no_data;范例2:查询部门编号是50的员工没有的话抛出异常
declare no_emp_found exception; cursor pemp is select t.ename from emp t where t.deptno = 50; pename emp.ename%type; begin open pemp; fetch pemp into pename; if pemp%notfound then raise no_emp_found; -- raise语句将异常抛出 end if; close pemp; exception when no_emp_found then dbms_output.put_line('没有找到员工'); when others then dbms_output.put_line('其他错误'); end; 存储过程(Stored Procedure)是在大型数据库系统中,一组为了完成特定功能的SQL 语句集,经编译后存储在数据库中,用户通过指定存储过程的名字并给出参数(如果该存储过程带有参数)来执行它。存储过程是数据库中的一个重要对象,任何一个设计良好的数据库应用程序都应该用到存储过程。
好处
优点: 执行速度比较快,可重复使用,减少网络流量,安全
缺点:可移植性比较差
创建存储过程语法:
create [or replace] PROCEDURE 过程名[(参数名 in/out 数据类型)] AS begin PLSQL子程序体; End; 或者 create [or replace] PROCEDURE 过程名[(参数名 in/out 数据类型)] is begin PLSQL子程序体; End 过程名;范例1:给指定的员工涨100工资,并打印出涨前和涨后的工资
分析:我们需要使用带有参数的存储过程
CREATE OR REPLACE PROCEDURE addSal1 ( eno IN NUMBER ) IS pemp emp % rowtype; BEGIN SELECT * INTO pemp FROM emp WHERE empno = eno; UPDATE emp SET sal = sal + 100 WHERE empno = eno; dbms_output.put_line ( '涨工资前' || pemp.sal || '涨工资后' || ( pemp.sal + 100 ) ); END addSal1; -- 调用 begin -- 调用程序 addsal1(eno => 7902); commit; end;语法:
create or replace function 函数名(Name in type, Name out type, ...) return 数据类型 is 结果变量 数据类型; begin return(结果变量); end[函数名];存储过程和存储函数的区别
一般来讲,过程和函数的区别在于函数可以有一个返回值;而过程没有返回值。
但过程和函数都可以通过out指定一个或多个输出参数。我们可以利用out参数,在过程和函数中实现返回多个值。
调用:
declare esal number; begin esal:=fun_countyearsal(7839); dbms_output.put_line(esal); end;范例:使用存储函数来查询指定员工的年薪
create or replace function empincome(eno in emp.empno%type) return number is psal emp.sal%type; pcomm emp.comm%type; begin select t.sal into psal from emp t where t.empno = eno; return psal * 12 + nvl(pcomm, 0); end;使用存储过程来替换上面的例子
create or replace procedure empincomep(eno in emp.empno%type, income out number) is psal emp.sal%type; pcomm emp.comm%type; begin select t.sal, t.comm into psal, pcomm from emp t where t.empno = eno; income := psal*12+nvl(pcomm,0); end empincomep; --调用: declare income number; begin empincomep(7369, income); dbms_output.put_line(income); end; 数据库触发器是一个与表相关联的、存储的PL/SQL程序。每当一个特定的数据操作语句(Insert,update,delete)在指定的表上发出时,Oracle自动地执行触发器中定义的语句序列。
数据确认
实施复杂的安全性检查
做审计,跟踪表上所做的数据操作等
数据的备份和同步
语句级触发器 :在指定的操作语句操作之前或之后执行一次,不管这条语句影响了多少行 。
行级触发器(FOR EACH ROW) :触发语句作用的每一条记录都被触发。在行级触发器中使用old和new伪记录变量, 识别值的状态。
语法:
CREATE [or REPLACE] TRIGGER 触发器名 {BEFORE | AFTER} {DELETE | INSERT | UPDATE [OF 列名]} ON 表名 [FOR EACH ROW [WHEN(条件) ] ] declare …… begin PLSQL 块 End 触发器名 触发器名:触发器对象的名称。由于触发器是数据库自动执行的,因此该名称只是一个名称,没有实质的用途。 触发时间:指明触发器何时执行,该值可取: before:表示在数据库动作之前触发器执行; after:表示在数据库动作之后触发器执行。 触发事件:指明哪些数据库动作会触发此触发器: insert:数据库插入会触发此触发器; update:数据库修改会触发此触发器; delete:数据库删除会触发此触发器。 表 名:数据库触发器所在的表。 for each row:对表的每一行触发器执行一次。如果没有这一选项,则只对整个表执行一次。范例:插入员工后打印一句话“一个新员工插入成功”
create or replace trigger testTrigger after insert on person declare -- 局部变量 begin dbms_output.put_line('一个员工被插入'); end testTrigger;范例:不能在休息时间插入员工
create or replace trigger before insert on person declare weekend varchar2(10); begin select to_char(sysdate, 'day') into weekend from dual; if weekend in ('星期一') then raise_application_error(-20001, '不能在非法时间插入员工'); end if; end validInsertPerson;当执行插入时会报错
在触发器中触发语句与伪记录变量的值
触发语句:old:newInsert所有字段都是空(null)将要插入的数据Update更新以前该行的值更新后的值delete删除以前该行的值所有字段都是空(null)范例:判断员工涨工资之后的工资的值一定要大于涨工资之前的工资
create or replace trigger addsal4p before update of sal on myemp for each row begin if :old.sal >= :new.sal then raise_application_error(-20002, '涨前的工资不能大于涨后的工资'); end if; end; --调用 update myemp t set t.sal = t.sal - 1; 提示错误信息需求:使用序列,触发器来模拟mysql中自增效果
分析:创建一个基于该表的before insert 触发器,在触发器中使用刚创建的SEQUENCE。
create or replace trigger user1_trigger before insert on user1 for each row begin select user1_seq.nextval into:new.id from sys.dual; end;psal emp.sal%type; pcomm emp.comm%type; begin select t.sal into psal from emp t where t.empno = eno; return psal * 12 + nvl(pcomm, 0); end;
使用存储过程来替换上面的例子 ```plsql create or replace procedure empincomep(eno in emp.empno%type, income out number) is psal emp.sal%type; pcomm emp.comm%type; begin select t.sal, t.comm into psal, pcomm from emp t where t.empno = eno; income := psal*12+nvl(pcomm,0); end empincomep; --调用: declare income number; begin empincomep(7369, income); dbms_output.put_line(income); end; 数据库触发器是一个与表相关联的、存储的PL/SQL程序。每当一个特定的数据操作语句(Insert,update,delete)在指定的表上发出时,Oracle自动地执行触发器中定义的语句序列。
数据确认
实施复杂的安全性检查
做审计,跟踪表上所做的数据操作等
数据的备份和同步
语句级触发器 :在指定的操作语句操作之前或之后执行一次,不管这条语句影响了多少行 。
行级触发器(FOR EACH ROW) :触发语句作用的每一条记录都被触发。在行级触发器中使用old和new伪记录变量, 识别值的状态。
语法:
CREATE [or REPLACE] TRIGGER 触发器名 {BEFORE | AFTER} {DELETE | INSERT | UPDATE [OF 列名]} ON 表名 [FOR EACH ROW [WHEN(条件) ] ] declare …… begin PLSQL 块 End 触发器名 触发器名:触发器对象的名称。由于触发器是数据库自动执行的,因此该名称只是一个名称,没有实质的用途。 触发时间:指明触发器何时执行,该值可取: before:表示在数据库动作之前触发器执行; after:表示在数据库动作之后触发器执行。 触发事件:指明哪些数据库动作会触发此触发器: insert:数据库插入会触发此触发器; update:数据库修改会触发此触发器; delete:数据库删除会触发此触发器。 表 名:数据库触发器所在的表。 for each row:对表的每一行触发器执行一次。如果没有这一选项,则只对整个表执行一次。范例:插入员工后打印一句话“一个新员工插入成功”
create or replace trigger testTrigger after insert on person declare -- 局部变量 begin dbms_output.put_line('一个员工被插入'); end testTrigger;范例:不能在休息时间插入员工
create or replace trigger before insert on person declare weekend varchar2(10); begin select to_char(sysdate, 'day') into weekend from dual; if weekend in ('星期一') then raise_application_error(-20001, '不能在非法时间插入员工'); end if; end validInsertPerson;当执行插入时会报错
在触发器中触发语句与伪记录变量的值
触发语句:old:newInsert所有字段都是空(null)将要插入的数据Update更新以前该行的值更新后的值delete删除以前该行的值所有字段都是空(null)范例:判断员工涨工资之后的工资的值一定要大于涨工资之前的工资
create or replace trigger addsal4p before update of sal on myemp for each row begin if :old.sal >= :new.sal then raise_application_error(-20002, '涨前的工资不能大于涨后的工资'); end if; end; --调用 update myemp t set t.sal = t.sal - 1; 提示错误信息需求:使用序列,触发器来模拟mysql中自增效果
分析:创建一个基于该表的before insert 触发器,在触发器中使用刚创建的SEQUENCE。
create or replace trigger user1_trigger before insert on user1 for each row begin select user1_seq.nextval into:new.id from sys.dual; end;