当我们只需要知道数据内容,但并不对数据进行操作的时候,我们就需要使用DQL语句来进行数据的查询读取(只读)。
DQL:Data Query Language 数据查询语言。
DQL的关键字只有一个,就是SELECT,但它却比DDL与DML加起来都要复杂:
关键词 | 作用 |
---|---|
SELECT | 查询表中数据 |
通过select-from-where语句,即可查询指定表的特定数据,这也是最基础最简单的查询语句。我们以学生信息表t_student为例,该表中有五个字段:s_id,name,age,gender,classes分别记录:学生ID,学生姓名,学生年纪,学生性别,学生班级。建表:
create table t_student
(s_id number,
name varchar2(20),
age number,
gender varchar2(10),
classes varchar2(5)
);
对该表的查询示例:
select r.name,r.* from t_student r where r.s_id = 2;
其中,t_student后的r,为表取的临时别名,仅在当前sql语句中生效。别名可以为除了关键字的任意名字,主要是为了方便sql编写,不必再写一长串的表名。
r.*中的星号表示展示目标表的所有列。
子查询,就是一种嵌套查询,在很多sql语句中都可以灵活的使用。使用子查询,我们就可以把一个查询集的结果,当作一张表;也可以把查询的字段值,作为另一个查询的where筛选条件等等。有了子查询,sql更加灵活多变,可以实现更复杂的逻辑。
来个案例:请查询出比小虎年纪更大的所有学生信息。
看到这个需求,我们首先得查出小虎年纪,然后再查比该年纪更大的学生信息。可以想到用子查询一步到位~:
select * from t_student t
where t.age > (select r.age from t_student r where r.name = '小虎' );
需要注意的是,子查询与外层查询是分开的,可以看作是两步查询,及时使用了同一张表,我们应该看作是不同时间维度上的两张表。为了阅读更方便,建议将同一张表取两个不同别名,用来区分。
当我们使用模糊查询like时,常会与通配符一起使用。PL/SQL中常用的通配符有两个:
SELECT * FROM t_student r where r.name like '%A%';
--包括A开头和A结尾的
示例,查询学生名字的第三个字母为A的:
--前两个字符用_替代,_的地方必须有一个字符
SELECT * FROM t_student r where r.name like '__A%';
查询结果的排序方式,往往并不方便我们查看,我们可以在查询语句结果通过order by语句,来指定字段(列)进行排序。order by默认是从小至大,再加上desc关键字,可以改为从大至小排序。例:
select r.age,r.* from t_student r where r.classes = 'A' order by r.age [desc];
[ ]中为可选项。
ORDER BY子句的目标字段可以是多个,但会一
当我们只想查询某些数据库的基本信息,或者是一些不需要实际存放在某张表中的数据,则可以通过Oracle提供的dual来实现。
dual表是一个实际存在的表,它只有固定的一行一列,不能用于存储数据。因为select语句必须接上from某张表,我们常常用它来换取系统时间等信息,实例:
select sysdate from dual; --获取系统时间
select to_char(sysdate,'YYYY-MM-DD hh24:mi:ss') from dual; --执行内置函数
select 666*777 from dual; --单纯进行计算
当我们需要对某列进行分组统计时,则会使用到group by子句。
举个实际点的例子,我们想知道t_student表中,A,B,C班,分别有多少学生。如果不用group by,只能一个班一个班的查询,如下:
select count(1) from t_student r where r.classes = 'A';
select count(1) from t_student r where r.classes = 'B';
select count(1) from t_student r where r.classes = 'C';
通过这种方式也不靠谱,如果还有D班级,则会遗漏。如果在查询语句结尾,使用group by子句,则会将指定字段的所有值分组,相同的为一组来展示,需要注意的是,一单使用group by子句,则查询的列里只能为group by指定的列,或内置函数。实例:
select r.classes,count(1) from t_student r
group by r.classess;
-------------------------
查询结果为:
classess count(1)
----------- ----------
A 25
B 30
C 20
GROUP BY子句的目标字段也可以是多个。
PL/SQL中提供的内置函数大多都挺好用的,方便快捷。但如果我们想对函数的计算结果进行筛选,是不可以直接在where条件中写函数并限制值的,因为在where条件里是还未查出结果的,函数得不到计算范围。
此时我们可以在where条件语句结束后,使用HAVING子句。having后也是写条件语句,与where类似,但having的条件语句可以使用对函数计算结果进行筛选了,having中的函数是对where条件的查询结果来计算的。
例如,我们只想查出班级人数大于25人的班级:
select r.classess,count(1) from t_student r
group by r.classess
having count(1) > 25;
其实having大多数情况也可以通过两层select来实现,这个案例也可以改写为:
select t.classes,t.cou
from(select r.classess,count(1) cou from t_student r group by r.classess) t
where t.cou > 25;
我们将group by的查询结果作为一张表,来进行嵌套查询。其中,由于count为关键字,所以为count(1)的列其取个别名,方便查询。我们也可以猜测一下,having的内部实现,是不是就是类似嵌套查询呢?
虽然两种方法都可以达到相同目的,但在代码可读性、性能等方面上,having更具优势,所以更推荐使用having。
以上的内容中,都是对一张表进行的查询,但实际数据库中总是会设计很多表,多张表之前也会有许多关联关联关系,我们常常会需要用到多表的联合查询。
除了上面提到的t_student学生信息表外,我们再建一个学生的考试分数表t_score,字段有三个:s_id,score,course,分别记录:学生ID,得分,课程。建表:
create table t_score
(s_id number,
score number,
course varchar2(20)
);
有着主外键关联关系,并不一定需要有外键约束,只要逻辑上有着值相等的列,根据这个列建立起来的等值连接,被称为内连接,或简单连接。
示例的t_student表和t_score表中s_id字段就是主外键关系,虽然没有外键约束,我们可以建立内连接来进行查询:
select r.name,t.score,t.course from t_student r,t_score t where r.s_id = t.s_id;
内连接的这个案例,正常场景下,用这样的连接查询就可以查出想要的结果了。但做程序开发,往往重点就在于全面考虑异常情况。来个异常例子:如果一名学生缺考了,那t_score表中就没有他的s_id对应数据,内连接则无法查出缺考的场景数据。
对于一张表中数据是全的,另一张表中数据可能有缺失的情况,就可以使用外连接来查询。使用(+)来表示外连接,语法格式如下:
--右外连接
select table1.column,table2.column
from table1 , table2
where table1.column(+) = table2.column;
--左外连接
select table1.column,table2.column
from table1 , table2
where table1.column = table2.column(+);
别被这个左右绕晕了,总之就是,(+)出现在哪个表字段后面,就表示这边表的字段可能不完全,需要被(+)补充一下的意思。
有了外连接,那么对于上面提到的异常缺考情况,我们可以通过该实例查出来:
select r.s_id,r.name,t.score,t.course from t_student r,t_score t
where r.s_id = t.s_id(+);
如果学生缺考,则t_student表中的name等字段数据还是可以查出来,但对应t_score表的t.score,t.course则会为NULL空。
交叉连接是指两张表的数据行相乘得到的笛卡尔积,在where条件后不对两张表进行关联,则会产生交叉连接。如:
select r.*,t.* from t_student r,t_score t;
得到的结果集数量是两张表行数的乘积,且由于每行缺少有意义的关联关系,结果集也失去了意义。
自然连接是内连接的严格要求后的升级版,如果两张表有着相同名字的字段,且数据类型也相同,那么可以使用natural join来让他们自然连接,示例:
select * from t_student natural join t_score;
但实际过程中一般不使用,还是采用内连接方式逻辑更清晰,提高代码可读性。
来一个案例:请查询出缺考语文科目学生的学生信息。
该例子中需要的结果只是学生信息,即t_student表,但学生是否缺考我们需要根据t_score成绩表才知道。如何实现不在from中添加t_score表,但在where中却可以将t_score表作为条件限制呢?。使用exists语句,则可以办到。
exists是存在的意思,它后面接一个子查询,如果该子查询能查到结果集,则条件成立。not exists则是不存在,如果后面的子查询查不到结果集,条件才成立。
查询缺考学生信息实例:
select * from t_student r
where not exists(select 1 from t_score t where r.s_id = t.s_id and t.course = '语文');
我们称每个查询语句的结果为结果集,如果多个结果集的列名和列数据类型一致的话,可以使用union或union all来合并,及取并集。
如果我们有一张学生信息历史表t_student_history,记录了学生以前的信息。现在有一个需求,需要同时展示t_student与t_student_history中,s_id为1的数据。此使,就可以使用union来实现:
select s_id,name,age,gender,classes from t_student where s_id = 1
union
select s_id,name,age,gender,classes from t_student_history where s_id = 1;
被union的结果集,列名、数据类型、顺序均相同,才可以编译通过,成功合并两个结果集的查询结果。该案例中,如果历史表中的数据,与当前表数据一模一样,则会被消除,完全相同的数据无论多少后,最后只保留一行。
而union all与union基本一样,主要的区别是union all不会消除重复数据,而是全部保留。
有取并集的方法,那就少不了取交集和差集嘛,来就来全套~
intersect,是取交集,只会得到两个结果集数据相同的部分。
minus,是取差集,会得到除去两个结果集数据相同的部分后的数据。
语法与union相同,写在两个select中间。