Oracle查询优化改写技巧与案例(第一章) —— 单表查询

全屏书中详细目录:Oracle查询优化改写技巧与案例 —— 目录

第1章 单表查询

1.1 查询表中所有的行与列

在进行查询操作之前,一般先看一下表的结构,例如 desc emp;

查询所有的信息,直接使用 select * from 表名 即可

SQL> select * from emp;


     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 MARTIN     SALESMAN        7698 28-9月 -81           1250       1400         30

1.2 从表中检索部分行

查询表中有多少个销售人员,加上where过滤条件即可,职位列是job

SQL> select * from emp where job='SALESMAN';


     EMPNO ENAME      JOB              MGR HIREDATE              SAL       COMM     DEPTNO
---------- ---------- --------- ---------- -------------- ---------- ---------- ----------
      7499 ALLEN      SALESMAN        7698 20-2月 -81           1600        300         30
      7521 WARD       SALESMAN        7698 22-2月 -81           1250        500         30
      7654 MARTIN     SALESMAN        7698 28-9月 -81           1250       1400         30
      7844 TURNER     SALESMAN        7698 08-9月 -81           1500          0         30

1.3 查找空值

若要查某一列为空的数据,比如,返回提成(comm)为空的数据

SQL> select * from emp where comm = null;
未选定行

明显的数据库有数据,但是却没有查到,问题在哪儿呢? 

实际上,NULL是不能用“=”运算符的,要用 IS NULL判断,正确语法如下

SQL> select * from emp where comm is null;


     EMPNO ENAME      JOB              MGR HIREDATE              SAL       COMM     DEPTNO
---------- ---------- --------- ---------- -------------- ---------- ---------- ----------
      7369 SMITH      CLERK           7902 17-12月-80            800                    20
      7566 JONES      MANAGER         7839 02-4月 -81           2975                    20
      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

NULL不支持加减乘除,大小比较,相等比较,否则只能为空

SQL> select * from dept where 1>=null;
未选定行
SQL> select * from dept where 1<=null;
未选定行
SQL> select * from dept where 1+null<=0;
未选定行
SQL> select * from dept where 1*null<=0;
未选定行

对于其他函数,在使用时最好测试一下有NULL时,会返回什么结果。

SQL> select replace('abcdef','a',NULL) as str from dual;

STR
-----
bcdef

SQL> select greatest(1,null) from dual;

GREATEST(1,NULL)
----------------


SQL>

可以看到,不同的函数对NULL的支持也不一样,所以大家遇到NULL时最好先测试一下结果受什么影响,而不是仅凭经验甚至是臆测

知识点:Oracle函数之greatest函数详解Oracle中replace函数的使用

1.4 将空值转换为实际值

对于下列语句

select coaleace(comm, 0) from emp;

有人会问,为什么是coalesce函数,而不是nvl函数?

这是因为coalesce函数更好用,看一下示例,返回多个值中第一个不为空的值。

/* 创建视图 */
create or replace view v as
select null as c1, null as c2, 1 as c3, null as c4, 2 as c5, null as c6 from dual union all
select null as c1, null as c3, null as c3, 3 as c4, null as c5, 2 as c6 from dual;

select * from v;

select coalesce(c1,c2,c3,c4,c5,c6) as c from v;

可以看到,相对于nvl来说,coalesce支持多个参数,能很方便的返回第一个不为空的值,如果上面的值改为nvl,就需要嵌套很多层了

select nvl(nvl(nvl(nvl(nvl(c1,c2),c3),c4),c5),c6) as c from v;

知识点:Oracle中scott用户创建视图权限不足(解决)Oracle中coalesce函数的使用【Oracle】decode 和 nvl 函数的用法及区别?

1.5 查找满足多个条件的行

对于简单的査询,操作起来比较容易,那么复杂一点的呢?比如,我们要査询部门10 中的所有员工、所有得到提成的员工, 以及部门20中工资不超过2000美元的员工。

这是三个条件的组合, 符合上述任一条件即可。

我们把这三个条件整理成逻辑表达式的形式: (部门10中的员工 0R 所有得到提成的员工 0R(工资<=2000and部门号=20))

注意: 对于多个条件的组合,要使用括号, 这样在更改维护语句时可以不必再考虑优先级问题, 而且可以很容易地借助各种工具找到各组合条件的起止位置。

那么我们可以这样写

select * from emp where (deptno = 10
       /* 所有的到提成的员工,注意千万不要写成 comm <> null */
       or comm is not null
       /* 部门(20)中工资超过2000美元的员工 */
       or (sal <=2000 and deptno = 20)
   );

如上述语句,可以清楚看到起止位置,对于复杂的组合条件来说,要方便阅读许多

1.6 从表中检索部分列

前面我们都是取表中所有的列, 但实际的场景中, 常常只需要返回部分列的数据就可以。如只需员工编码、员工名称、雇佣日期、工资。所以一般要明确指定査询哪些列, 而不是用“*”号来代替。另外,明确要返回的列也会使语旬的维护更简单, 而不必每次看到语句时都需要査看表结构才知道会返回什么数据。

select empno, ename, hiredate, sal from emp where deptno= 10;

1.7 为列取有意义的名称

不是每个人都能看懂那些简写的字母是什么意思,所以应该给列取个别名。你可以如下面所示在 as 后面跟别名,也可以不要 as,直接在列名后跟别名即可。看看下面这个报表数据, 就会一目了然。

SQL> select ename as 姓名, deptno as 部门编号, sal 工资 from emp;

姓名         部门编号       工资
---------- ---------- ----------
SMITH              20        800
ALLEN              30       1600
WARD               30       1250
JONES              20       2975
MARTIN             30       1250

1.8 在WHERE子句中引用取别名的列

写报表时经常会加上各种条件,而直接在条件中使用别名比列名(如: B01、 B02、B03) 要清晰得多,引用别名时千万别忘了嵌套一层,因为这个别名是在select之后才有效的。 

如下示例是寻找那些拖了国家 GDP后腿的人:

SQL> select * from (select sal as 工资, comm as 提成 from emp) x where 工资<1000;

      工资       提成
---------- ----------
       800
       950

1.9 拼接列

若有人不喜欢看表格式的数据,希望返回的数据都像“ CLARK 的工作是MANAGER” 这样的显示 。 我们可以用字符串连接符“||”来把各列拼在一起 。

SQL> select ename || ' 的工作是 ' || job as msg from emp where deptno = 10;

MSG
-----------------------------
CLARK 的工作是 MANAGER
KING 的工作是 PRESIDENT
MILLER 的工作是 CLERK

当然,拼接列对我们来说还有其他意义,看下面的例子

SQL> select 'truncate table ' || owner || '.' || table_name || ';' as 清空表 from all_tables where owner = 'SCOTT';

清空表
-----------------------------------------------------------------------------
truncate table SCOTT.DEPT;
truncate table SCOTT.EMP;
truncate table SCOTT.BONUS;
truncate table SCOTT.SALGRADE;

没错,就是用SQL来生成SQL,当需要大量类似的SQL需要生成时,就可以先写一个语句然后进行修改,直接用基础数据或数据字典来批量生成,不过这个例子中的SQL不要去执行,否则会丢失数据。

1.10 在SELECT语句中使用条件逻辑

有时为了更清楚地区分返回的信息,需要做如下处理。

如:当职员工资小于或等于2000美元时,就返回消息“过低”,大于或等于4000美元时,就返回消息“过高”,如果在这两者之间,就返回“0K”

类似这种需求也许会经常遇见,处理这样的需求可以用 casec when 来判断转化

select ename,
       sal,
       case
         when sal <= 2000 then
          '过低'
         when sal >= 4000 then
          '过高'
         else
          'ok'
       end as status
  from emp
 where deptno = 10

这种方式还经常用在报表中,比如按工资分档次统计人数

select 档次, count(*) as 人数
  from (select (case
                 when sal <= 1000 then
                  '0000-1000'
                 when sal <= 2000 then
                  '1000-2000'
                 when sal <= 3000 then
                  '2000-3000'
                 else
                  '好高'
               end) as 档次,
               ename,
               sal
          from emp)
 group by 档次
 order by 1;

1.11 限制返回的行数

在线查询时,并不要求每次都返回所有的数据,比如,进行抽查时候会要求只返回两条数据

我们可以利用伪列 rownum 来过滤,rownum 依次对返回的每一条数据做一个标识。

SQL> select * from emp where rownum <=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

如果直接用 rownum = 2来查询会出现什么情况呢?

SQL> select * from emp where rownum = 2;

未选定行

因为 rownum 是依次对数据做标识的,就像上学时依据考分排名一样,需要有第一名,后面才会有第二名,所以,需要先把所以的数据取出来,才能有第二名。

正确的取第二行数据的查询应该是下面这样,先生成序号。

SQL> select * from (select rownum as sn, emp.* from emp where rownum <=2) where sn = 2;

        SN      EMPNO ENAME      JOB              MGR HIREDATE              SAL       COMM     DEPTNO
---------- ---------- ---------- --------- ---------- -------------- ---------- ---------- ----------
         2       7499 ALLEN      SALESMAN        7698 20-2月 -81           1600        300         30

1.12 从表中随机返回n条记录

为了防止作假,像 1.11 节中那样抽查数据还不行,还需要随机抽查。

我们可以先用 dbms_random 来对数据进行随机排序,然后取其中的前三行。

SQL> select empno, ename from (select empno, ename from emp order by dbms_random.value()) where rownum <= 3;

     EMPNO ENAME
---------- ----------
      7499 ALLEN
      7566 JONES
      7844 TURNER

有人会问,为什么要嵌套一层呢?直接这样用多好。

select empno, ename from emp where rownum <= 3 order by dbms_random.value;
/* 为了观察方便,对结果进行排序 */
select * from (select empno, ename from emp where rownum <= 3 order by dbms_random.value) order by 1;

     EMPNO ENAME
---------- ----------
      7369 SMITH
      7499 ALLEN
      7521 WARD

多运行几次会发现,每次的得到数据都是一样的,并不是随机,这是为什么呢?

为了便于解释,先对上面的语句进行等价改写:

select empno, ename, dbms_random.value ran
  from emp
 where rownum <= 3
 order by ran;

查看语句中这几处的执行顺序为:

  • select

  • rownum

  • order by

也就是说,要先取出数据,然后在生成序号,最后才是排序

我们可以通过子查询把排序前后的序号分别取出来对比

select rownum as 排序后,
       排序前,
       empno  as 编码,
       ename  as 姓名,
       ran    as 随机数
  from (select rownum as 排序前, empno, ename, dbms_random.value ran
          from emp
         where rownum <= 3
         order by ran);

同样,你再多运行几次,看看是不是和刚才的描述一致。

因此,正确的写法是:先随机排序,再取出数据

知识点:利用rownum伪列实现分页查询内容【Oracle】dbms_random函数用法快速生成多条测试数据

1.13 模糊查询

先建立如下视图

create or replace view v as
  select 'ABCEDF' as  vname from dual 
  union all
  select '_BCEFG' as  vname from dual 
  union all
  select '_BCEDF' as  vname from dual
  union all
  select '_\BCEDF' as  vname from dual
  union all
  select 'XYCEF' as  vname from dual;

要求一:查处 vname 中包含字符串"ced"的

方法比较简单。常见的模糊查询如下

SQL> select * from v where vname like '%CED%';

    VNAME
    -------
    ABCEDF
    _BCEDF
    _\BCEDF

要求二:查出 vname 中包含字符串"_BCE"的

SQL> select * from v where vname like '_BCE%';

    VNAME
    -------
    ABCEDF
    _BCEFG
    _BCEDF

发现多出了一个 ABCEDF,因为在like子句中有两个通配符:%(替代一个或多个字符)、“_”(替代一个或多个字符)。

在这里,“_”被当成通配符了,怎么办呢?

莫着急,我们可以使用转义字符来处理

SQL> select * from v where vname like '\_BCE%' escape '\';

    VNAME
    -------
    _BCEFG
    _BCEDF

ESCAPE 把'\'标识为转义字符,而'\'把'_'转义为字符,而非其原来的通配符含义

或许有人注意到其中有一行值为'_\BCEDF',那么加了 ESCAPE '\' 后返回什么样的数据呢?

SQL> select * from v where vname like '_\BCE%' escape '\';
第 1 行出现错误:
ORA-01424: 转义符之后字符缺失或非法

看到没,会报错,熟悉开发的朋友都知道,需要双写转义字符即可。

SQL> select * from v where vname like '_\\BCE%' escape '\';

    VNAME
    -------
    _\BCEDF

对于字符串中包含“%”的情况也是一样。转义即可


赞(52) 打赏
未经允许不得转载:优客志 » 数据库
分享到:

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏