oracle产品线围绕企业开发平台的
企业开发平台四大组件:unix,weblogic中间件,java编程语言,oracle数据库
oracle 开发主要分两类
数据库管理:dba
数据库编程:分两部分
sql编程
pl/sql编程(子程序,触发器,面向对象,游标)
下载 官方网站下载相应的版本到本地,并解压缩。
环境 在启动安装之前的环境配置。
安装
只要是行与列的集合就是数据表
SQL> ed hello
SQL> @hello
数据库开发都以dml操作为主
数据库设计时以ddl操作为主
数据类型 number(7,2) 小数位是两位,总共是7位,整数是5位
#######################################
简单查询的最大特征在于可以控制列,它无法进行数据行的控制。
select [distinct] *| 列名 [别名],列名 [别名],... from 表名 [别名];
distinct是去掉重复的行。指所查询出来的所有列内容都一样的去重。
执行顺序是先from再select
子句有两个 一个是select ...,一个是from ...。或者还会有where ...,order by ...。
进行数据的投影-----控制所需要显示的数据列。
支持四则运算,再加上别名就美观了。
select aa,bb,cc*2 from emp;
select aa,bb,cc*2 nian from emp;
select trans_new_price-2 sum from tis_ft_adjust_price where rownum <=5;
select trans_new_price*2 sum from tis_ft_adjust_price where rownum <=5;
拼接列的值。列名1 || 列名2
select adjust_time || operator_id ww from tis_ft_adjust_price where rownum <=5;
格式化输出,字符串用单引号引起来,数字直接写,不用引用。
select 'tj:'||adjust_reason||',ti:'||ADJUST_TIME ww from tis_ft_adjust_price where rownum <=5;
#######################################
限定查询,控制数据行
1.sql语句的执行顺序
第三步:选出所需要的数据列 select *
第一步:确定数据来源 from table
第二步:筛选数据行 where 条件
第四步:数据排序 order by
2.限定符号的使用,以下是标准sql支持的。
若干个条件判断符,
>,<,=,>=,<=,!=(<>).
is not null,is null
like,not like _匹配任意一位字符,%匹配多位。
in,not in not in中不能有null,in可以有。where 字段 in(8899,2234,7554,null); 不连续的行
betwwen...and where 字段|数值 betwwen 最小值 and 最大值; 连续的行
以上只能使用一次,如果有多个条件,则用逻辑运算符:
and,or,not(非)
书写标准,先查列,再将select from where 分别写三行,再写各种限定,表限定,行限定,列限定。这是按照执行顺序写的。
desc tis_bk_user;
select user_id
from tis_bk_user
where rownum <=5;
select *
from tis_ft_g_owner_clear
where trans_freight between 10000 and 11000;
betwwen...and...是一个运算符,也可以用关系运算符与逻辑运算符组合来使用,但效率低。where trans_freight>10000 and trans_freight<11000。
oracle所有的运算符不受数据类型的控制,以上是对数字的判断,字符串意义不大,重点是对日期时间的判断。
select clear_time
from tis_ft_g_owner_clear
where clear_time between '2015-07-28 11:13:57' and '2015-07-29 11:47:07';
select *
from tis_bk_role
where not role_id<to_number('001056');
空判断
select null + 1 from emp; 这个表有几行,就返回几行空行
null不能使用关系运算,关系可以判断的是数据,null属于一个未知的数据。
select *
from tis_bk_role
where role_id in (001050,001025,001034);
select * from emp where enam like '%%';
select * from emp;
这两条是一样的结果,下面虽然效率高,但上面在程序开发中方便后续扩充
order by 字段1 asc|desc,字段2 asc|desc...
默认是按照自然顺序排列的,也就是输入数据时的顺序。也可以进行多字段的排序。如果某一个字段重复的话。
所有的排序操作都是在where之后执行的,order by 永远最后执行。
###############################################
单行函数
字符串函数 replace(),substr(),length(),
select user_accout,initcap(USER_ACCOUT)
from tis_bk_user;
select user_accout,length(USER_ACCOUT)
from tis_bk_user;
select *
from tis_bk_user
where length(user_accout)=5;
replace(列名|数据,‘原内容’,'替换的内容')
利用replace()可以取消字符串中的空格
create table t2 as select * from tis_bk_user_info;
select replace(user_name,'木','森')
from t2
where user_name like '%神木%';
下标从1开始的
substr(列名|数据,开始点) 指定位置到结尾
substr(列名|数据,开始点,长度) 截取范围
select substr('helloworld',3) from dual;
select substr('helloworld',3,5) from dual;
截取后三个
select substr('helloworld',length('helloworld')-2) from dual;
select substr('helloworld',-3)
数值函数
mod(),round(),trunc()
round() 四舍五入
-2小数点前两位,2小数点后两位
select
round(998.8876568),
round(9999.77777655,2),
round(9999.77777655,-2),
round(-15.132)
from dual;
trunc() 不四舍五入进行处理
mod() 求模(求余数)
日期函数
日期函数(oracle自己特色)
如何可以取得当前的日期,用伪列sysdata
伪列sysdate,systimestamp,
虚拟表dual
select sysdate,systimestamp from dual;
日期计算模式
日期+1 后一天
日期-1 前一天
日期-日期
没有日期+日期
select sysdate,sysdate-2,sysdate+3 from dual;
如果只是依靠天娄无法获得一个准确的年或月,所以oracle里面才提供了日期处理函数,利用这些函数可以避免闰年与闰月的问题
计算下一个周二的日期
SQL> select next_day(sysdate,'TUE') from dual;
trunc(months_betwwen(sysdate,hiredate)/12) year
trunc(mod(months_between(sysdate,hiredate)/12)) month
转换函数
利用to_char进行年,月,日的拆分,只是提供一个思路,oracle本身的一个自动转换。
to_char
下面两个几乎等于无用to_date
to_number
to_char(列|日期|数字,格式)
select to_char(sysdate,'yyyy:mm:dd'),to_char(sysdate,'yyyy-mm-dd hh24:mi:ss') from dual;
select to_char(98468472392823,'L999,999,999,999,999,999') from dual;
通用函数
nvl()
decode()
#################################################
多表查询(进入了复杂查询的阶段)
笛卡尔积一直存在,无法消除,虽然能消除显示问题。数据量一大,多表查询会带来严重的问题。
多表查询之中的多张表一定要有关联关系,否则不能查询
判断程序慢的两种方法:程序算法慢(cpu 占用率高),数据库数据大(内存占用高)
由数据量决定,用不用多表查询,如果数据量几百行,随便用,几十万行,就得选用其它技术点了。
select count(*)
from costs a,prod b
where a.prod_id=b.prod.id;
有明确的关联字段的案例如下:
确定要使用的数据表 emp,dept
确定已知的关联字段 prod_id
select a.pname,a.sal,b.loc,b.job
from emp a,dept b
where a.prod_id=b.prod_id;
没有明确的关联字段,而是关联条件
select e.name,e.sal,s.grade
from emp e,salgrade s
where e.sal between s.lowsal and s.higsal;
分析过程如下:(关联字段或关联条件)
第一:写出第一张表的查询
第二:引入第二张表,加表,加字段,加条件,消除笛卡尔积的显示
第三:引入第三张表,加表,加字段,加条件,用and连接。
#################################
表的连接操作
连接操作本身就是一个查询
select * from dept;
select * from salgrade;
两张表的所有行组合,from之中
select * from salgrade,dept;
两张表所有组合的任意列,select list之中
select a.grade,b.dname from salgrade a,dept b;
对于两张表进行多表查询对于消除笛卡尔积主要依靠连接模式来处理的,表的连接模式有两种:
内连接:在之前都利用where子句来消除笛卡尔积,只有满足条件的数据才会显示出来
外连接:不用管是左还是右,只要所需要的数据全部显示就可以了。分左外连接,右外连接,全外连接(几乎不会出现)
where d.deptno=s.deptno(+) 左连接
where d.deptno(+)=s.deptno 右连接
(+)是oracle特有的
left join是以A表的记录为基础的,A可以看成左表,B可以看成右表,left join是以左表为准的.
换句话说,左表(A)的记录将会全部表示出来,而右表(B)只会显示符合搜索条件的记录(例子中为: A.aID = B.bID).
B表记录不足的地方均为NULL.
三种写法
http://www.cnblogs.com/kerrycode/p/5935704.html
select a.user_id,a.user_account||','||a.user_type aa,b.user_name
from tis_ft_user a,tis_ft_user_info b
where rownum<5 and a.user_id(+)=b.user_id;
select a.user_id,a.user_account||','||a.user_type aa,b.user_name
from tis_ft_user a left join tis_ft_user_info b on a.user_id=b.user_id
where rownum<5;
select user_id,a.user_account||','||a.user_type aa,b.user_name
from tis_ft_user a inner join tis_ft_user_info b using(user_id)
where rownum<5;
仔细观察一下,就会发现,和left join的结果刚好相反,这次是以右表(B)为基础的,A表不足的地方用NULL填充.
inner join(相等联接或内联接)
sql语句如下:
SELECT * FROM a
INNER JOIN b
ON a.aID =b.bID
等同于以下SQL句:
SELECT *
FROM a,b
WHERE a.aID = b.bID
##################################
数据集合操作 并集,交集,差集,补集
是将若干个查询结果合并在一起,若干个数据查询结果所返回的数据结构必须一致。
union 取消重复行
union all
intersect 交集
minus 差集
如果我们需要将两个select语句的结果作为一个整体显示出来,我们就需要用到union或者union all关键字。union(或称为联合)的作用是将多个结果合并在一起显示出来。
union和union all的区别是,union会自动压缩多个结果集合中的重复结果,而union all则将所有的结果全部显示出来,不管是不是重复。
1. union:对两个结果集进行并集操作,不包括重复行,同时进行默认规则的排序;
2. union All:对两个结果集进行并集操作,包括重复行,不进行排序;
3. intersect:对两个结果集进行交集操作,不包括重复行,同时进行默认规则的排序;
4. minus:对两个结果集进行差操作,不包括重复行,同时进行默认规则的排序。
可以在最后一个结果集中指定Order by子句改变排序方式。
SELECT * FROM emp
WHERE sal < 1500;
7369SMITHCLERK790217-DEC-8080020
7521WARDSALESMAN769822-FEB-81125050030
7654MARTINSALESMAN769828-SEP-811250140030
7876ADAMSCLERK778823-MAY-87110020
7900JAMESCLERK769803-DEC-8195030
7934MILLERCLERK778223-JAN-82130010
SELECT * FROM emp
WHERE sal BETWEEN 1000 AND 2000
ORDER BY 1;
7499ALLENSALESMAN769820-FEB-81160030030
7521WARDSALESMAN769822-FEB-81125050030
7654MARTINSALESMAN769828-SEP-811250140030
7844TURNERSALESMAN769808-SEP-811500030
7876ADAMSCLERK778823-MAY-87110020
7934MILLERCLERK778223-JAN-82130010
SELECT * FROM emp
WHERE sal < 1500
union
SELECT * FROM emp
WHERE sal BETWEEN 1000 AND 2000
ORDER BY 1;
7369SMITHCLERK790217-DEC-8080020
7499ALLENSALESMAN769820-FEB-81160030030
7521WARDSALESMAN769822-FEB-81125050030
7654MARTINSALESMAN769828-SEP-811250140030
7844TURNERSALESMAN769808-SEP-811500030
7876ADAMSCLERK778823-MAY-87110020
7900JAMESCLERK769803-DEC-8195030
7934MILLERCLERK778223-JAN-82130010
SELECT * FROM emp
WHERE sal < 1500
union all
SELECT * FROM emp
WHERE sal BETWEEN 1000 AND 2000
ORDER BY 1;
7369SMITHCLERK790217-DEC-8080020
7499ALLENSALESMAN769820-FEB-81160030030
7521WARDSALESMAN769822-FEB-81125050030
7521WARDSALESMAN769822-FEB-81125050030
7654MARTINSALESMAN769828-SEP-811250140030
7654MARTINSALESMAN769828-SEP-811250140030
7844TURNERSALESMAN769808-SEP-811500030
7876ADAMSCLERK778823-MAY-87110020
7876ADAMSCLERK778823-MAY-87110020
7900JAMESCLERK769803-DEC-8195030
7934MILLERCLERK778223-JAN-82130010
7934MILLERCLERK778223-JAN-82130010
SELECT * FROM emp
WHERE sal < 1500
intersect
SELECT * FROM emp
WHERE sal BETWEEN 1000 AND 2000
ORDER BY 1;
7521WARDSALESMAN769822-FEB-81125050030
7654MARTINSALESMAN769828-SEP-811250140030
7876ADAMSCLERK778823-MAY-87110020
7934MILLERCLERK778223-JAN-82130010
SELECT * FROM emp
WHERE sal < 1500
minus
SELECT * FROM emp
WHERE sal BETWEEN 1000 AND 2000
ORDER BY 1;
7369SMITHCLERK790217-DEC-8080020
7900JAMESCLERK769803-DEC-8195030
####################################
统计函数(也叫分组函数)
count(),sum(),avg(),:支持数字
max(),min():支持数字,字符串,日期数据类型
在没有数据的时候,只有count返回结果,其它是null
count()有三种形式:
count(*):统计所有的数据行
count(字段):统计所有不为空的数据行数
count(distinct 字段)统计所有不重复的数据行
####################################
分组统计
group by 找重复的列
第四步:选出所需要的数据列 select *|分组列
第一步:确定数据来源 from table
第二步:筛选数据行 where 条件
第三步:针对筛选的数据行进行分组 group by 分组字段1,分组字段2,
第五步:数据排序 order by
select sid,count(*),min(statistic#),max(statistic#)
from v$sesstat
group by sid;
统计函数
是针对一张表的完整统计,
分组统计
分组的前提是存在有重复,允许单独一行记录进行分组。
分组的时候有一些约定条件
1、如果查询不使用group by 子句,那么select 子句中只允许出现统计函数,其它任何字段不允许出现。
或者说是统计函数不与其它字段同时出现。
正确
select count(*) from emp;
错误
select empno,count(*) from emp;
2、如果查询中使用group by 子句,那么select 子句中只允许出现分组字段,统计函数,其它任何字段都不允许出现。
正确
select job,count(*) from emp group by job;
错误
select ename,job,count(*) from emp group by job;
3、统计函数允许嵌套,但是嵌套之后的select 子句里面只允许出现嵌套函数,而不允许出现任何字段,包括分组字段。
正确
select max(avg(sal)) from emp group by deptno;
错误
select deptno,max(avg(sal)) from emp group by deptno;
可以将下面的查询结果想象成一张数据表,这张表没有group by子句,那么就回到了第一个约定条件,只允许出现统计函数。
所以就解释了第三个约定条件。
select deptno,avg(sal) from emp group by deptno;
####################################
多表查询与分组统计
having子句,此时就不能用where子句了,having 跟随group by 而出现。
针对分组后的数据进行筛选,是在group by
select job,avg(sal)
from emp
group by job
having avg(sal)>2000;
where 与having的区别
1:where 是在group by 之前执行的,先选出可以参与分组的数据,不能使用统计函数。
2:having是在group by 之后执行的,可以使用统计函数。
##########################################
子查询
子查询并没有特殊的语法,可以出现在任意子句之中(select ,from,where,group by,having,order by),但必须用()声明
子查询就是查询的嵌套。
############################################
在where子句中使用子查询
where子句主要是进行数据的筛选,而且通过分析可以发现,
单行单列,单行多列,多行单列,都可以在where子句
中出现。
--查单行单列的
--要求查出谁的工资最低
不能直接拿800这个数据直接使用,因为这个数据是需要统计出来的,
而想要知道这个内容,可以利用min();
--所以分两步
1.先查出最低的
select min(sal) from emp;
2.再用最低的去过滤
以上查询返回单行单列,本质上就是一个数值。
select * from emp
where sal=(select min(sal) from emp);
上下两个查询,下面是写死了,上面动态计算的,比较灵活。
select * from emp
where sal=800;
--查出公司雇佣最早的雇员
select * from emp
where hiredate=(select min(hiredate) from emp);
--子查询返回单行多列(了解就行,用的不多)
--查出与scott工资相同,职位相同的所有雇员
select * from emp
where
(sal,job)=(select sal,job from emp where ename='SCOTT')
and ename<>'SCOTT';
--子查询返回多行单列(比较重要)
--子查询返回多行单列实际上相当于告诉用户一个数据的操作范围,从...到...
而如果想要进行范围的判断,在where中提供有三个运算符,in,any,all
in与not in
select sal from emp where job='MANAGER';
上面返回了多行单列
select * from emp
where sal in
(select sal from emp where job='MANAGER');
多行单列就相当于给出了我们一个查询范围
select * from emp
where sal not in
(select sal from emp where job='MANAGER');
not in中不能有空,否则查不出来。一定要保证子查询中不能有空
select * from emp
where comm not in
(select comm from emp);
any操作
sal=any 功能上与in没有区别
sal>any 比子查询返回内容的最小值要大
sal<any 比子查询返回内容的最大值要小
select * from emp
where sal>any
(select sal from emp where job='MANAGER');
all操作
sal>all 比子查询返回内容的最大值要大
sal<all 比子查询返回内容的最小值要小
select * from emp
where sal<all
(select sal from emp where job='MANAGER');
#################################
exists()条件
主要测试在一个子查询中行的存在
如果子查询有数据返回(至少有一行,不管什么数据)就表示条件满足,那么就可以显示出数据,否则不显示
select * from emp
where exists (
select * from emp where deptno=20);
如果子查询有数据返回,外部查询就有数据返回,子没有父就没有
exists()与in()区别
exists()以行为主
in()以列(数据)为主
exists()要比in()性能更高,判断行有无比判断数据有无更快。
使用exists()只关心子查询里面返回的是否有行,至于什么行,不关心
select * from emp
where exists(
select 'hello' from dual where 1=1);
select * from emp
where not exists(
select 'hello' from dual where 1=2);
##################################################
having子句中使用子查询
要使用having必须结合group by子句,要使用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.job,e.ename,d.dname
from emp e,dept d
where e.deptno=d.deptno;
变换成子查询
select e.empno,e.job,e.ename,
(select dname d from dept d where d.deptno=e.deptno)
from emp e;
实际上在select子句里面出现的子查询核心目的在于:行列转换
from子句中出现子查询(重点)
主要是思路问题,是需要去思考的
################################################
DML包括查询与更新。
数据的更新包括:增加,修改,删除
更新离不开查询
数据的增加
数据的修改
数据的删除
先复制一份原始数据
create table myemp as select * from emp;
数据的增加
insert into myemp() values();
常用的三种类型
字符串:‘字符串’
数值:直接编写
日期:有三种
sysdate
根据日期的保存结构编写字符串:‘天-年-月’
利用to_date()将字符串转换为date型数据
完整语法
值与字段对应即可。
insert into myemp(empno,job,sal,hiredate,ename,deptno,mgr,comm)
values(6666,'清洁工',2000,to_date('1988-09-08','yyyy-mm-dd'),'王二',40,7396,null);
执行上面的语句报下面的错
ORA-01756: quoted string not properly terminated
将清洁工与王二换成英文即可。或者撤销NLS_LANG环境变量,再insert就可以了,因为我的系统上有这个变量
但是行插进去了,却乱码了(变成???)
[oracle@db Downloads]$ echo $NLS_LANG
AMERICAN_AMERICA.ZHS16GBK
[oracle@db Downloads]$ echo $LANG
en_US.UTF-8
乱码问题先不处理了,所以先选用英文。
insert into myemp(empno,job,sal,hiredate,ename,deptno,mgr,comm)
values(6668,'aa',2000,to_date('1988-09-08','yyyy-mm-dd'),'aa1',40,7396,null);
insert into myemp(empno,job,sal,hiredate,ename,deptno,mgr,comm)
values(6669,'aa',2000,to_date('1986-09-08','yyyy-mm-dd'),'aa2',40,7397,null);
省略字段语法
必须注意要与表中的字段顺序一致。
在开发时尽量使用完整语法,一条数据一行
##########################################
update 表名 set 字段=内容,字段=内容,...[where 更新条件(s)]
普通的
update myemp set sal=2500,comm=40 where ename='aa2';
带子查询的
update myemp set sal=(select avg(sal) from myemp)
where sal=(select min(sal) from myemp);
update myemp set hiredate=sysdate,sal=sal*1.2
where hiredate between '01-jan-81' and '31-dec-81';
where to_char(hiredate,'yyyy')=1981;
不带条件的,是修改所有的记录
update myemp set comm=null;
#################################
先查再删除
select * from myemp where empno=7369;
delete from myemp where empno=7369;
删除若干个数据
select * from myemp where empno in(7566,7788,7799);
delete from myemp where empno in(7566,7788,7799);
删除也可以结合子查询
select * from myemp order by sal desc;
delete from myemp where sal=(select max(sal) from myemp)
删除全部数据几乎是不可能的
delete from myemp;
####################################
事务处理的概念
保证数据完整性的一种手段
具有ACID原则
保证你一个人更新数据的时候,其它人不能更新。
sessionA
sessionB
每一个session有自己独立的事务。
缓冲区中的数据可以rollback
rollback;
commit;
更新操作被事务保护。要成功就一起成功,要失败就退回原点重新再来。
事务锁
两个session进行同一条数据的操作,谁手快谁成功。
sessionA操作了,但没提交,sessionB执行的时候会一直等待。也就是说A锁定了这条数据,在没有提交或回流之前,sessionB不能操作。只能等待它提交后才能操作。
update myemp set sal=5000 where empno=7566;
update myemp set sal=90000 where empno=7566; 一直等待。
事务处理的过程中,存在行级锁。事务的隔离性
在整个程序的世界里,只有两个方法可以评价程序:时间复杂度与空间复杂度
时间换空间,不加硬件的情况下,就等吧。
空间换时间。加硬件,不加硬件的话,用云计算,来临时空间换时间。
在不改变现有硬件的情况下,可以利用一个周期来完成,不是一次完成。可能需要半年时间,但不影响用户。活跃用户与僵shi用户。
################################
伪列,很多,重要的就这两个
列本身不存在,但却可以使用
rownum 行号
rowid 数据开发并不重要,数据分析时会用到
select rownum,empno,ename,job from emp;
不是固定的,是动态生成的。行号是根据查询结果动态生成的。
select rownum,empno,ename,job from emp where deptno=10;
rownum做两件事
1、取得第一行
select rownum,empno,ename,job from emp where deptno=10 and rownum=1;
一般不会做select * from 表名;的操作,因为如果表太大的话,会是灾难的
一般只查第一行。
2、取得前N行(一个更为重要的特性)
select * from emp where rownum<=10;
6到10行的记录,分页程序就是这样实现的,上一页与下一页。这是很普遍的
select *
from(
select rownum rn,ename,empno from emp where rownum <=10) temp
where temp.rn>5;
分页的固定格式
currentPage 当前页
lineSize 每页行数
select *
from (
select rownum rn,列,...
from 表名
where rownum<=currentPage*lineSize) temp
where temp.rn>(currentPage-1)*lineSize;
样例:
select *
from(
select rownum rn,ename,empno
from emp
where rownum <=9) temp
where temp.rn>6;
#############################
rowid 理解就可以了
分析上使用,开发时不常用。
针对每行数据提供一个物理地址。
select rowid,job,ename from emp;
AAAVREAAEAAAACXAAA
AAAVRE 数据对象编号
AAE 数据文件编号
AAAACX 数据块号
AAA 数据行号
题目:删除表中重复的数据,只保留最原始的一个。
create table mydept as select * from dept;
SQL> select rowid,deptno,dname,loc from mydept;
ROWID DEPTNO DNAME LOC
------------------ ---------- -------------- -------------
AAAV5uAAEAAAAMzAAA 10 ACCOUNTING NEW YORK
AAAV5uAAEAAAAMzAAB 20 RESEARCH DALLAS
AAAV5uAAEAAAAMzAAC 30 SALES CHICAGO
AAAV5uAAEAAAAMzAAD 40 OPERATIONS BOSTON
插入测试数据
insert into mydept(deptno,dname,loc) values(10,'ACCOUNTING','NEW YORK');
insert into mydept(deptno,dname,loc) values(20,'RESEARCH','DALLAS');
insert into mydept(deptno,dname,loc) values(30,'SALES','CHICAGO');
insert into mydept(deptno,dname,loc) values(40,'OPERATIONS','BOSTON');
表的不正当设计,造成了表中有重复数据,所以为了删除,就要用到rowid来解决。
rowid是一直累加的,不会减小。原始数据的rowid较小,所以先分组,再将分组中较小的查出来,
然后取反(not in)删除即可。
select rowid,deptno,dname,loc from mydept;
delete from mydept where rowid not in(
select min(rowid)
from mydept
group by deptno,dname,loc);
min()是一个多行单列
select max(rowid),deptno,dname,loc
from mydept
group by deptno,dname,loc;
这只是rowid的一个使用说明,讲解索引的时候也会用到rowid