一、Oracle(1)

作者: 木鱼_cc | 来源:发表于2018-08-03 17:27 被阅读0次

    1.基本SQL select语句
    1.1 sqlplus登陆
    1.2sqlplus 的基本操作
    1.3基本select语句
    1.4SQL语句使用注意事项
    1.5算数运算 +-*/
    1.6NULL值
    1.7 连接符
    1.8SQL和sqlplus

    2.过滤和排序数据
    2.1where条件过滤
    2.2order by

    3.单行函数
    3.1字符函数
    3.2数值函数
    3.3时间函数
    3.4日期函数
    3.5转换函数
    3.6通用函数
    3.7条件表达式

    4.分组函数
    4.1分组函数
    4.2分组数据
    4.3HAVING
    4.4group by 的增强(扩展知识)

    5.多表查询
    5.1等值链接
    5.2不等值链接
    5.3外链接
    5.4自连接
    5.5层次查询

    6.子查询
    6.1定义子查询 需要注意的问题
    6.2主、子查询在不同表间进行
    6.3在主查询的where、select、having、from 放置子查询
    6.4在from后面放置子查询
    6.5一般先执行子查询,再执行主查询
    6.6一般不在子查询中使用Order by(练习)
    6.7 单行子查询只能使用单行操作符;多行子查询只能使用多行操作符
    6.8子查询中Null

    7.集合运算
    7.1集合运算符
    7.2集合运算需要注意的问题

    1.基本SQL select语句

    1.1 sqlplus登陆

    sqlplus 账号/密码

    1.2sqlplus 的基本操作

    显示当前用户:SQL>show user;
    查看当前用户下的表:

    SQL>select * from tab;
    tab:数据字典(记录数据库和应用程序源数据的目录),包含当前用户下的表
    

    查看表的结构SQL>desc emp;(desc -> description描述)
    设置行宽:set linesize 120;
    设置页面:

    set pagesize 100;
    或者将上述行宽和页面写入如下配置文件,永久配置
    (Oracle下载目录)\product\11.2.0\dbhome_1\sqlplus\admin\glogin.sql
    

    设置员工名列宽:col ename for a20(a表示字符串)
    设置薪水列为4个数字:col sal for 9999(一个9表示一个数字)

    1.3基本select语句

    其语法格式为:

    SELECT *|{[DISTINCT]column|expression[alias],...}
    FROM table;
    

    查询所有员工的所有记录:

    SQL>select * from emp;
    (效果等同于)
    SQL>select empno,ename,job,mgr,hiredate,sal,comm,deptno from emp;
    

    查询员工号、姓名、薪水:

    SQL>select empno,ename,sal from emp;
    

    增加年薪:

    SQL>select empno,ename,sal,sal*12 
        from emp;
    

    "/"执行上一条成功执行的SQL语句

    修改上一条SQL语句

    1. 用c命令来修改(c = change)
      默认,光标闪烁位置指向上一条SQL语句的第一行,输入2则定位到第二行
      c /错误关键字/正确关键字
      使用"/"来执行修改过的SQL语句

    例如:错误输入了:

    SQL> select empno,ename,sal,sal*12
       form emp;("from"书写错误,该错误位于整条SQL语句的第二行)
    (1) 输入:2  终端提示:2 * from emp
    (2) 输入:c  /form/from  终端提示2 * from emp(意为改正后的sql语句样子)
    (3)输入:/
    
    1. 使用ed命令来修改 edit
      ed 弹出系统默认的文本编辑器(如记事本)
      修改、保存、退出、执行"/"

    2. 别名:as

    SQL>select empno as "员工号",ename "姓名",sal "月薪",sal * 12 年薪
        from emp;
    关键字as写与不写没有区别
    “”有与没有取决于别名中是否有空格或者关键字
    
    1. DISTINCT 关键字

    “DISTINCT”关键字,重复记录只取一次

    SQL>select deptno from emp;---->  SQL> select DISTINCT deptno from emp;
    SQL>select job from emp;---->  SQL> select DISTINCT job from emp;
    SQL>select DISTINCT deptno,job from emp;会发现没有行减少,因为deptno不重复。因此得出,distinct作用于后面的所有列
    
    1.4SQL语句使用注意事项
    • SQL语言大小写不敏感
    • SQL可以写在一行或者多行
    • 关键字不能被缩写也不能分行
    • 各字句一般要分行写
    • 使用缩进提高语句的可读性
    1.5算数运算 +-*/
    • 乘除优先级高于加减
    • 优先级相同时,按照从左到右运算
    • 可以使用括号改变优先级

    查询:员工号、姓名、月薪、年薪、奖金、年收入
    SQL>select deptno,ename,sal ,sal * 12 ,comm,comm+sal*12 from emp;

    结果不正确。没有奖金的员工,年收入不正确


    1

    需注意,在程序开发过程中,数据是核心。程序再正确也没有用,必须保证数据不能丢,且正确。对于上面的结果,有对有错的情况是最危险的。

    1.6NULL值

    ※NULL值问题:1.包含NULL值得表达式都为空。2.NULL != NULL

    解决:虑空函数:nvl(a,b)如果a为NULL,函数返回b,所以:sal * 12 + nvl(comm,0)年收入。

    查询奖金为NULL的员工信息:
    SQL>select * from emp where comm = NULL(SQL中不使用==)

    在SQL中,判断一值是否等于另外一值不用"="和"!="而使用is和is not
    SQL>select * from emp where comm is NULL;(is not)

    • 空值是无效的,未指定的,未知的或不可预知的值
    • 空值不是空格或者0
    1.7 连接符

    Oracle 中定义了一个连接符"||"用来连接字符串
    显示"xxx是一个xxx"怎么显示呢?
    SQL>select ename ||'is a'||'job' from emp;

    但是如果显示一个"hello world"应该怎么显示呢?

    1. 使用concat函数来实现:
    SQL>select concat('hello','world') from ????
    这两个字符串不是任何一个表的内容,在Oracle中,定义一张"伪表"dual用来满足SQL99语法(ANSI)
    语法规定:select后 必须接 from
    
    但是,concat函数最多只能有两个参数,再多的参数需要用连接符"||"来完成拼接!
    
    1.8SQL和sqlplus

    我们已经学习使用了select,应该知道还有update,delete,insert,create...
    同时,我们学习了ed,c,set,col,desc...
    SQL->语言,关键字不能缩写
    sqlplus->Oracle提供的工具,可在里面执行SQL语句,它配有自己的命令(ed,c,set,col)特点是缩写关键字

    SQL

    • 一种语言
    • ANSI标准
    • 关键字不能缩写
    • 使用语句控制数据库中的表的定义信息和表中的数据

    SQL *Plus

    • 一种环境
    • Oracle的特性之一
    • 关键字可以缩写
    • 命令不能改变数据库中的数据的值
    • 集中运行

    2.过滤和排序数据

    2.1where条件过滤
    2.1.1日期格式

    查询10号部门的员工信息
    SQL>select * from emp where deptno=10

    查询"KING"的信息

    SQL>select * from emp where ENAME='KiNg';未选定行
    注意:字符串大小写敏感
    SQL>select * from emp where ENAME='KING';则正确
    

    查询入职日期为1981年11月17日的员工

    SQL>select * from emp where hiredate='1981-11-17'可以吗?
    参看,SQL>select sysdate from dual 查看系统当前的日期(注意其格式)
    SQL>select * from emp where hiredate='17-11月-81
    

    现在,我们想修改日期格式显示方式

    获取系统当前日期格式:

    SQL>select * from v$nls_parameters;(数据字典,类似于tab)
    设置列宽度,SQL>col parameter for a30;
    
    修改日期格式:
    SQL>alter session set NLS_DATE_FORMAT= 'yyyy-mm-dd';
    
    再次查询:
    SQL>select * from emp where hiredate='1981-11-17';
    
    SQL>alter session set NLS_DATE_FORMAT= 'yyyy-mm-dd hh24:mi:ss'; 显示带有时间的日期
    
    SQL>select sysdate from dual; 再次查看系统时间
    
    改回系统默认格式:SQL>alter session set NLS_DATE_FORMAT='DD-MON-RR';
    
    1. 字符和日期要包含在单引号中
    2. 字符大小写敏感,日期格式敏感
    3. 默认的日期格式是DD-MON-RR
    
    2.1.2比较运算
    • 普通比较运算
    = 等于(不是==)   > 大于 
    >= 大于等于        < 小于
    <= 小于等于        <>不等于(也可以是!=)
    
    • BETWEEN...AND... 介于两值之间

    查询工资在1000-2000之间的员工:
    使用比较运算符
    SQL>select * from emp where sal >= 1000 and sal < 2000;(注意第二个sal不能省略)
    用between and:
    SQL>select * from emp where sal between 1000 and 2000;

    注意:1.包含边界 2.小值在前,大值在后。(对于日期也是如此)

    查询81年2月至82年2月入职的员工信息:
    SQL>select * from emp where hiredate between '1-2月-81' and '30-1月-82';

    • IN:在集合中。(NOT IN 不在集合中)

    查询部门号为10和20的员工信息

    1.SQL>select * from emp where deptno=10 or deptno=20;
    2.SQL>select * from emp where deptno IN (10,20);
    SQL>select * from emp where deptno not in (10,20);
    

    但是,如果是...not in(10,20,NULL)可不可以呢?
    NULL空值:如果结果中含有NULL,不能使用not in操作符,但可以使用in操作符

    • like 模糊查询‘%’匹配多个字符。‘_’匹配一个字符
    查询名字以S开头的员工:
    SQL>select * from emp where ename like 'S%';
    
    查询名字是4个字的员工:
    SQL>select * from emp where ename like '____';
    
    查询名字中包含_的员工
    SQL>select * from emp where ename like '%\_% escape '\';
    
    2.1.3逻辑运算
    AND 逻辑并
    OR  逻辑或
    NOT 逻辑非
    如果...where 表达式1 and 表达式2;
        ...where 表达式2 and 表达式1;
    这两句SQL语句功能一样吗?效率一样吗?
    
    SQL 优化
    SQL在解析where的时候,是从右到左解析的。
    所以
    and时应该将易假的值放在右侧
    or时应该将易真的值放在右边
    
    2.2order by
    • 使用ORDER BY子句排序
      • ASC(ascend):升序。默认采用升序方式
      • DESC(descend)降序。
    • ORDER BY 子句在SELECT语句的结尾
    SELECT last_name,job_id,department_id,hire_date
    FROM employees
    ORDER BY hire_date;
    
    SQL>select * from emp order by deptno,sal ;
    order by后与多列时,列名之间用逗号隔分,order by会同时作用于多列。上例的运行结果会在同一个部门内升序,部门间再升序
    

    查询员工信息,按奖金由高到低排序:

    SQL>select * from emp order byu comm desc
    结果前面的值是NULL,数据在后面,如果是一个100页的报表,这样的显示肯定不正确。较为人性化的显示应该将空值放在最后,即
    SQL>select * from emp order by comm desc nulls last(注意是nulls而不是null)
    

    排序的规则

    • 可以按照select语句中的列名排序
    • 可以按照别名列名排序
    • 可以按照select 语句中的列名的顺序值排序
    • 如果要按照多列进行排序,则规则是先按照第一列排序,如果向彤彤,则按照第二列排序,以此类推

    3.单行函数

    单行函数:只对一行进行变换,产生一个结果。函数可以没有参数,但必须要有返回值。如:concat、nvl

    • 操作数据对象
    • 接受参数返回一个结果
    • 只对一行进行变换
    • 每行返回一个结果
    • 可以转换数据类型
    • 可以嵌套
    • 参数可以是一列或者一个值
    3.1字符函数

    操作对象是字符串

    大致可以分为两大类,一类是大小写控制函数,主要有lower、upper、initcap
    SQL>select lower('Hello,WorLD') 转小写,upper('Hello,WorLD') 转大写,initcap('Hello,WorLD') 首字母大写 from dual

    另一类是字符控制函数,有CONCAT、SUBSTR、LENGTH/LENGTHB、INSTR、LPAD|RPAD、TRIM、REPLACE

    substr(a,b):从a中,第b位开始取(计数从1开始),取到结尾
    SQL>select substr('hello world',3) from dual;
    
    substr(a,b,c),从a中第b位开始,向右取c位
    SQL>select substr('hello world',3,5) from dual;
    
    length:字符数,lengthb字节数
    SQL>select length('hello world') 字符数,lengthb('hello world') 字节数 from dual;注意中英文差异
    
    instr:在母串中查找子串,找到返回下表,计数从1开始。没有返回0
    SQL>select instr('hello world','llo') from dual
    
    lpad:左填充,参1:待填充的字符串,参2:填充后字符串的总长度(字节),参3填充什么
    rpad:右填充
    SQL>select lpad('abcd',10,'*') 左,rpad('abcd',10,'#') 右 from dual
    
    trim:去掉前后指定的字符
    SQL>select trim('H' from 'Hello worldH') from dual;
    注意语法,期间含有from关键字
    
    replace:替换
    SQL>select replace('hello world','l','*') from dual;
    
    删除字符串'hello world'中的字符'l'
    SQL>select replace('hello world','l','') from dual;
    
    注意:上述的字符函数都不会对数据本身作出不可逆转的修改,只是在显示上做了一些转换。
    
    3.2数值函数
    ROUND:四舍五入
    ROUND (45.926,2)    45.93
    
    round(45.926,2) 2表达的含义是保留两位小数,第二个参数如果是0可以忽略不写
    SQL>select round(45.926,2) 一,round(45.926,1) 二,round(45.926,0) 三,round(45.926,-1) 四,round(45.926,-2) 五 from dual;
    
            一         二         三         四         五
    ---------- ---------- ---------- ---------- ----------
         45.93       45.9         46         50          0
    
    
    
    TRUNC:截断
    TRUNC(45.926,2)     45.92
    
    MOD:求余
    MOD(1600,300)       100
    
    3.3时间函数

    在Oracle中日期型的数据,既有日期部分,也有时间部分。

    SQL>select sysdate from dual; (这里没有时间部分,因为系统默认的格式中不显示时间
    SQL>select to_char(sysdate,'yyyy-mm-dd hh24:mi:ss') from dual;
    SQL>select to_char(sysdate,'day') from dual 可以显示当前日期星期几
    
    日期加、减数字得到的结果仍为日期。单位:天
    显示:昨天、今天、明天
    SQL>select (sysdate-1) 昨天,(sysdate) 今天,(sysdate + 1) 明天 from dual;
    SQL>select to_char(sysdate-1,'yyyy-mm-dd') 昨天,to_char(sysdate,'yyyy-mm-dd') 今天,to_char(sysdate+1,'yyyy-mm-dd') 明天 from dual;
    
    既然一个日期型的数据加上或者减上一个数字得到的结果仍为日期,两个日期相减,得到的就是相差的天数。
    
    计算员工的工龄:
    SQL>select ename,hiredate,(sysdate - hiredate) 天,(sysdate - hiredate) / 7 星期,(sysdate - hiredate) / 30 月,(sysdate - hiredate)/365 年 from emp; 
    
    !!注意:日期和日期只能相减,不能相加。日期只能和数字相加
    
    
    3.4日期函数

    上面求取员工工龄的结果不精确,如果想将其算精确,可以使用日期函数来做

    months_between 两个日期值相差的月数(精确值)

    SQL>select ename,hiredate,(sysdate-hiredate)/30 一,months_between(sysdate,hiredate) 二 from emp;
    

    add_months:在某个日期值上,加上多少个月,正数向后计算,负数向前计算。

    计算95个月以后是哪年、哪月、哪天

    SQL>select add_months(sysdate,95) 哪一天 from dual;
    

    last_day:日期所在月的最后一天

    SQL>select last_day(sysdate) from dual
    

    next_day:指定日期的下一个日期

    SQL>select next_day(syddate,'星期一') from dual 
    从当前时间算起,下一个星期一
    

    round,trunc:对日期型数据进行四舍五入和截断

    SQL>select round(sysdate,'month'),round(sysdate,'year') from dual;
    SQL>select trunc(sysdate,'month'),round(sysdate,'year') from dual;
    
    3.5转换函数

    在不同的数据类型之间完成转换。将“123” 转换为 123 。 有隐式转换和显示转换之分。

    隐式转换:
    SQL>select * from emp where hiredate = '17-11月-81' 由Oracle数据库来做

    显示转换:
    SQL>select to_char(sysdate,'yyyy-mm-dd hh24:mi:ss') from dual; 通过转换函数来完成

    隐式转换,前提条件是:被转换的对象是可以转换的。(ABC->625可以吗?)

    Oracle自动完成下列转换

    源数据类型 目标数据类型
    VARCHAR2 or CHAR NUMBER
    VARCHAR2 or CHAR DATE
    NUMBER VARCHAR2
    DATE VARCHAR2

    显示转换,借助to_char(数据,格式)、to_number、to_date函数来完成转换


    1 2

    如果隐式转换和显示转换都可以使用,应该首选哪个呢?

    • SQL优化:如果隐式、显示都可以使用,应该首选显示,这样可以省去Oracle的解析过程
    练习:2015-05-11 16:17:06 今天是 星期一输出
    SQL>select to_char(sysdate,'yyyy-mm-dd hh24:mi:ss "今天是" day') from dual)
    在固定的格式加入自定义的格式,是可以的,必须要加 “ ”
    
    练习二:2015-05-11 16:17:06 今天是 星期一 转换为日期
    SQL>select to_date('2015-05-11 16:17:06 今天是 星期一','yyyy-mm-dd hh24:mi:ss "今天是" day') from dual;
    

    下面是在TO_CHAR函数中经常使用的几种格式


    3

    查询员工的薪水,2位小数,本地货币代码,千位符
    SQL>select to_char(sal,'L9,999.99') from emp; L9,999.99之间没有空格

    将¥2,975.00转换成数字:
    SQL>select to_number('¥2,9750.00','L9,999.99') 转换数字 from dual;

    3.6通用函数

    这些函数适用于任何数据类型,同时也适用于空值

    • NVL(expr1,expr2)
    • NVL2(expr1,expr2,expr3)
    • NULLIF(expr1,expr2)
    • COALESCE(expr1,expr2,...,expr n)

    nvl2:是nvl函数的增强版,nvl2(a,b,c) 当a = null 返回c,否则返回b

    使用nvl2求员工的年收入
    SQL>select empno,ename,sal,sal*12,sal*12+nvl2(comm,comm,0) 年薪 from emp

    nullif,nullif(a,b) 当a=b时返回null,不相等的时候返回a值
    SQL>select nullif('L9,999.99','L9,999.99') from dual

    coalesce:coalesce(a,b,c,...,n)从左向右找参数中第一个不为空的值
    SQL>select comm,sal,coalesce(comm,sal) 结果值 from emp;

    3.7条件表达式

    例子,老板打算给员工涨工资,要求
    总裁(PRESIDENT)涨1000,经理(MANAGER)涨800,其他人涨400,请将涨前,涨后的薪水列出

    select ename,job sal 涨前薪水,涨后薪水 from emp
    思路:
    if 是总裁('PRESIDENT') then +1000
    else if 是经理('MANAGER') then +800
    else +400
    
    但是在SQL中无法实现if else逻辑,当有这种需求的时候,可以使用case获得decode
    
    case:是一个表达式,其语法为
    CASE expr WHEN comparison_expr1 THEN return_expr1
             [WHEN comparison_expr2 THEN return_expr2
              WHEN comparison_exprn THEN return_exprn
              ELSE else_expr]
    END
    
    SQL>select ename,job sal 涨前薪水,case job when 'PRESIDENT' then sal+1000
                                              when 'MANAGER' then sal+800
                                              else sal+400
                                      end 涨后薪水
    from emp;
    
    注意语法:when then 与下一个when then 以及end之间没有","分隔符
    

    decode:是一个函数,其语法为:

    DECODE(col|expression,search1,result1
                        [,search2,result2,...,]
                        [,default])
    
    除第一个和最后一个参数外,中间的参数都是成对呈现的(参1,条件,值,条件,值,...,条件,值,尾参)
    
    SQL>select ename,job,sal 涨前薪水,decode(job,'PRESIDENT',sal+1000,
                                                'MANAGER',sal+800,
                                                 sal+400) as 涨后薪水
        from emp
    

    4.分组函数

    4.1分组函数

    多行函数也叫组函数

    • 了解组函数
    • 描述组函数的用途
    • 使用GROUP BY 子句数据分组
    • 使用HAVING 子句过滤分组结果集

    分组函数作用域一组数据,并对一组数据返回一个值。如:AVG、COUNT、MAX、MIN、SUM操作的是一组数据,返回一个结果

    求员工的工资总和
    SQL>select sum(sal) from emp;  sum()对指定列的各行求和
    
    员工人数
    SQL>select count(*) from emp ; count()统计指定列的非空行数(自动虑空)
    
    平均工资
    SQL>select sum(sal)/count(*) 方式一,avg(sal) 方式二 from emp
    
    方式一和方式二结果一样,当有空值的时候结果有可能不一样
    
    ※NULL空值:组函数都有自动虑空功能(忽略空值),所以)
    SQL>select count(*),count(comm) from emp;  执行结果不相同
    
    如何屏蔽 组函数 的虑空功能:
    SQL>select count(*),count(nvl(comm,0)) from emp;
    但是实际应用中,结果为14和结果为4都有可能对,看问题本身是否要求统计空值
    
    count函数:求个数,如果要求不重复的个数,要使用distinct
    SQL>select count(distinct job) from emp;
    
    4.2分组数据

    group by

    按照group by给定后的表达式,将from后面的table进行分组。针对每一组,使用组函数

    查询“部门”的平均工资
    分析:结合select * from emp order by deptno 结果分析分组
    SQL>select deptno,avg(sal) 
    from emp 
    group by deptno 
    order by deptno;
    
    上SQL语句可以抽象成:select a,组函数(x) from 表 group by a;这样的格式
    

    如果select a,b ,组函数(x).......group by 应该怎么写

    注意:在SELECT 列表中所有没有包含在组函数中的列,都必须在group by 的后面出现。所以上问应该写成group by a,b;没有b语法就会出错,不会执行SQL语句。但,反之可以。Group by a,b,c;c可以不出现在select语句中

    group by后面有多列的情况:
    SQL>select deptno,job,avg(sal) from emp group by deptno,job order by 1;
    
    分析该SQL的作用:
    因为deptno,job两列没有在组函数里面,所以必须同时在group by后面。
    该SQL的语义:按部门,不同的职位统计平均工资。先按第一列分组,如果第一列相同,再按第二列分组。
    所以查询结果中,同一部门中没有重复的职位
    

    常见的非法使用组函数的情况,主要出现在缺少group by子句。如hr用户执行查询语句

    SELECT department_id,COUNT(last_name)
    FROM employees;
    会显示如下错误
    SELECT department_id,COUNT(last_name)
      *
    ERROR at line 1:
    ORA-00937:not a single-group group function
    意为:GROUP BY子句中缺少列
    
    4.3HAVING

    使用HAVING过滤分组

    1. 行已经被分组
    2. 使用了组函数
    3. 满足HAVING子句中条件的分组江北显示
    其语法:
    SELECT column,group_function
    FROM   table
    [WHERE  condition]
    [GROUP BY group_by_expression]
    [HAVING  group_condition]
    [ORDER BY column]
    
    查询平均薪水大于2000的部门
    分析,该问题实际上是在分组的基础上过滤分组
    SQL>select deptno,avg(sal) from emp group by deptno having avg(sal)>2000;
    
    • 不能再WHERE子句中使用组函数(注意)
    • 可以在HAVING子句中使用组函数

    从功能上讲,where和having都是将满足条件的结果进行过滤。但是差别是where子句中不能使用组函数!

    求10号部门的平均工资
    分析:在上一条的基础上,having deptno=10;此时where也可以做这件事。
    SQL>select deptno,avg(sal) from emp where deptno=10 group by deptno; 因为没有组函数
    

    在子句中没有使用组函数的情况下,where,having都可以,应该怎样选择?

    ※SQL优化:尽量采用where
    如果有分组的话,where是先过滤再分组,而having是先分组再过滤。当数据量庞大如1亿条,where有事明显。

    4.4group by 的增强(扩展知识)

    问题:按照部门统计各部门不同工种的工资情况,要求按如下格式输出

    4

    分析SQL执行结果。

    第一部分数据是按照deptno和job分组;select查询deptno,job,sum(sal)
    第二部分数据是直接按照deptno分组即可,与job无关;select只需要查询deptno,sum(sal)
    第三部分数据不按照任何条件分组,即group by null;select查询sum(sal)

    所以,整体查询结果应该=group by deptno, job + group by deptno + group by null
                        =group by rollup(deptno,job); --group by 语句的增强
    
    抽象成表达式:
    group by rollup(a,b) = group by a,b,group by a + group by null
    
    SQL>select deptno,job,sum(sal) from emp group by rollup(deptno,job);
    =========等价于================
    select deptno,job,sum(sal) from emp group by deptno,job;
    select deptno,sum(sal) from by deptno
    select sum(sal) from emp group by null
    

    5.多表查询

    理论基础——笛卡尔积

    笛卡尔积的行数 = table1的行数 x table2的行数
    笛卡尔积的列数 = table1的列数 + table2 的列数

    5

    多表查询就是按照给定条件(连接条件),从笛卡尔全集中选出正确的结果

    根据连接条件的不同可以划分为:等值链接、不等值链接、外链接、自链接

    • Oracle 连接:

      • Equijoin:等值链接
      • Non-equijoin:不等值链接
      • Outer join:外链接
      • Self join:自链接
    • SQL:1999

      • Cross joins
      • Natural joins
      • Using clause
      • Full or two sided outer joins
    5.1等值链接

    从概念上,区分等值链接和不等值链接非常简单,只需要辨别where子句后面的条件,是“=”为等值连接,不是“=”为不等值链接

    如果:selecte.empno,e.ename,e.sal,dname from emp e,dept d
    直接得到的是笛卡尔全集。其中有错误结果。所以应该加where条件进行过滤
    
    SQL>SQL>select e.empno,e.ename,e.sal,dname
    from emp e,dept d
    where e.deptno = d.deptno;
    如果有N个表,where后面的条件至少应该有N-1个
    
    5.2不等值链接

    将上满的问题稍微调整下,查询员工信息:员工号 姓名 月薪 和薪水级别(salgrade表)

    分析:SQL>select * from salgrade;
    查看员工总的薪水级别,共有5级,员工的薪水级别应该满足 >= 当前级别的下线 ,<=该级别的上限
    过滤子句应该: where e.sal >= s.losal and e.sal <= s.hisal
    
    所以:SQL>select e.empno,e.ename,e.sal,s.grade from emp e,salgrade s where e.sal >= s.losal and e.sal <= s.hisal;
    
    更好的写法应该是使用between...and
    SQL>select s.grade,e.empno,e.ename,e.sal,e.job from emp e,salgrade s
        where e.sal between s.losal and s.hisal
        order by 1;
    
    
    5.3外链接

    按部门统计员工数,显示如下信息,部门号 部门名称 人数

    分析:
    人数:一定是在emp表中,使用count()函数统计emp表中任意非空列均可
    部门名称:在dept表dname中,直接读取即可
    部门号:任意两张表都有

    SQL>select e.deptno 部门号,d.dname 部门名称,count(e.empno) 人数 from emp e,dept d
        where e.deptno = d.deptno group by e.deptno,d.dname
    注意:由于使用了组函数count(),所以组函数外的e.deptno和d.dname必须放到group by 后
    
    6

    得到查询结果,但是select * from dept发现40号部门没有显示出来,原因是40号部门没有员工,where没有满足,结果不对,40号部门没有员工个,应该在40号部门位置显示0

    我们希望,在最后的结果中,包含某些对于where条件来说不成立的记录(外链接的作用)

    左外连接:当where e.deptno = d.deptno 不成立的时候,=左边所标示的信息,仍然被包含。
    写法:与叫法相反: where e.deptno=d.deptno(+)
    
    右外链接:当where e.deptno=d.deptno不成立的时候,=右边所表示的信息,仍然被包含。
    写法:依然与叫法相反:where e.deptno(+)=d.deptno
    
    以上我们希望没有员工的部门仍然包含到查询的结果当中。因此应该使用外链接的语法
    SQL>select d.deptno 部门号,d.dname 部门名称,count(e.empno) 人数 from emp e,dept d
       where e.deptno(+)=d.deptno group by d.deptno,d.dname; 右外链接的写法
    
    这样就可以将49号部门包含到整个查询结果中。人数是0
    注意:不能使用count(e.*)或count(*)
    
    5.4自连接

    核心,通过表的别名,将同一张表视为多张表

    查询员工信息:xxx的老板是yyy

    分析:执行select * from emp;发现,员工的老板也在员工表之中,是一张表。要完成多表查询我们可以假设,有两张表,一张表e(emp)只存员工,另一张表b(boss)只存员工的老板。——from e,b

    老板和员工之间的关系应该是:where e.mgr = b.empno(即员工表的老板 = 老板表的员工)
    
    SQL>select e.ename || ' 的老板是 ' ||b.ename from emp e,emp b where e.mgr = b.empno
    执行,发现结果正确,但是KING没有显示出来。KING的老板是他自己。应该怎么显示呢?
    
    SQL>select e.ename || ' 的老板是 ' ||b.ename from emp e,emp b where e.mgr = b.empno(+)
    
    完整应该是
    SQL>select e.ename || ' 的老板是 ' ||nvl(b.ename,'null') relationship from emp e,emp b where e.mgr = b.empno(+)
    
    7
    5.5层次查询

    以上自连接存在一个问题。我们将一张表视为两张表来操作。会产生笛卡尔积问题(14 X 14 = 196条记录),当我将该表视为3,4,5或更多张表看待呢?当表内数据有上亿条呢?自连接可以满足需求,但不是最好的。

    SQL>select count(*) from emp e,emp b;可以看出,笛卡尔集是平方的关系。自连接操作至少是一个平方的关系,表越大,笛卡尔集就平方性的在告诉增长。所以,自连接不适合操作大表。

    根据SQL语句输出结果【画图】出员工老板关系图。发现是一个树状结构,共有4层

    8

    对于这种树状结构,我们提出层次查询的概念,用来取代操作大表时,自连接有可以能带来的问题。所以,在层次查询中,只有一张表。否则就会出现笛卡尔集的问题

    level伪列是Oracle数据库帮助我们在表中添加的隐形列,差则有,不查则无

    关键:前一层的员工号=下一层的老板号

    connect by prior empno=mgr(prior即指定前一层)

    遍历一棵树:应该指定起始点(start with)

    起始:start with mgr is null (king之上再没有老板了)
    
    SQL>select level,empno,ename,mgr from emp
        connect by prior empno=mgr
        start with mgr is null
        order by 1
    

    总结:层次查询使用场景,一定只有一张表,同一张表的前后多次操作,进行连接,避免笛卡尔集

    层次查询也有缺点,没有自连接那么直观

    9 image.png

    6.子查询

    子查询语法很简单,就是select 语句的嵌套使用

    查询工资比scrott高的员工信息
    分析,两步即可完成
    1.查出SCOTT的工资 SQL>select ename,sal from emp where ename='SCOTT' 
    2.查出比3000高的员工 SQL>select * from emp where sal > 3000
    
    通过两部可以将问题结果得到。子查询,可以将两步合成一步
    ——子查询解决的问题:问题本身不能一步求解的情况
    
    SQL>select * 
        from emp 
        where sal > (select sal
                     from emp 
                     where ename='SCOTT')
    
    

    本节学习目标:

    • 描述子查询可以解决的问题
    • 定义子查询(子查询的语法)
    • 列出子查询的类型
    • 书写单行子查询和多行子查询
    6.1定义子查询 需要注意的问题
    1. 合理的书写风格
    2. 小括号()
    3. 主查询和子查询可以是不同表,只要子查询返回的结果主查询可以使用即可
    4. 可以在主查询的where、select、having、from后都可以放置子查询
    5. 不可以在主查询的group by后面放置子查询(SQL语句的语法规范)
    6. 强调:在from后面放置的子查询(***) from后面放置是一个集合(表、查询结果)
    7. 一般先执行子查询(内查询),再执行主查询(外查询);但是相关子查询除外
    8. 一般不在子查询中使用order by,但在Top-N分析问题中,必须使用order by
    9. 单行子查询只能使用单行操作符;多行子查询只能使用多行操作符
    10. 子查询中的null值
    6.2主、子查询在不同表间进行

    查询部门名称是“SALES”的员工信息

    主查询:查询员工信息。select * from emp;
    子查询:负责得到部门名称(dept表中)、部门号对应关系。select deptno from dept where dname='SALES'

    SQL>select  *
         from emp
         where deptno=(select deptno
                       from dept
                       where dname='SALES')
    主查询,查询的是员工表emp,子查询,查询的是部门表dept.是两张不同的表
    
    将该问题使用“多表查询”解决
    SQL>select e.* from emp e,dept d where e.deptno=d.deptno and d.dname='SALES'
    

    两种方式哪种好呢?
    ※SQL优化:理论上,既可以使用子查询,也可以使用多表查询,尽量使用“多表查询”。子查询有2次from

    不同数据库处理数据的方式不尽相同,如Oracle数据库中,子查询地位比较重要,做了深入的优化,有可能实际看到结果是子查询快于多表查询

    6.3在主查询的where、select、having、from 放置子查询

    子查询可以放在select后,但,要求该子查询必须是单行子查询;(该子查询本身只返回一条记录,2+叫多行子查询)

    SQL>select empno,ename,(select dname from dept where deptno=10) 部门 from emp

    注意:SQL中没有where是不可以的,那样是多行子查询

    进一步理解查询语句,实际上是在表或集中通过列名来得到行数据,子查询如果是多行,select无法做到这一点

    在having后和where类似。但需注意在where后面不能使用组函数

    6.4在from后面放置子查询

    表,代表一个数据集合、查询结果(SQL)语句本身也代表一个集合。

    查询员工的姓名、薪水和年薪

    SQL>select * from (select ename,sal,sal*12 年薪 from emp);

    将select语句放置在from后面,表示将select语句的结果,当成表来看待。这种查询方式在Oracle语句中使用比较频繁

    6.5一般先执行子查询,再执行主查询

    含有子查询的SQL语句执行的顺序是,先子后主。但,相关子查询例外

    6.6一般不在子查询中使用Order by

    一般情况下,子查询使用order by或者不适用order by 对主查询来说没有什么意义。子查询的结果给子查询当成集合来使用,所以没有必要将子查询order by

    但,在Top-N分析问题中,必须使用order by

    6.6.1找到员工表中工资最高的前三名, 要求按如下格式输出
    image1.png

    ——涉及Top-N分析问题。
    一般不在子查询中使用order by, 但在Top-N分析问题中,必须使用order by

    SQL>select rownum,empno,ename,sal
        from emp
        where rownum<=3
        order by sal desc
    
    结果并不正确
    
    补充知识:rownum 行号(伪列)  
        SQL> select rownum, empno, ename, sal  from emp   
    
    借助行号将薪水降序排列。前三条即是我们想要的内容。  
        SQL> select * from emp order by sal desc        但问题是如何取出前三行。
        SQL> select * from emp where rownum <= 3 order by sal           发现取出的结果不正确。
    
    
    rownum顺序不正确
    --------釜底抽薪,正确的做法--------------
    SQL>select rownum,empno,ename,sal
        from (select empno,ename,sal
              from emp
              order by sal desc)
        where rownum<=3
        order by sal desc
    
    因为rownum是对from中源进行自动编号的,所以可以先对from中的源进行排序!
    
    行号rownum需要注意的问题:
    1.  rownum永远按照默认的顺序生成。
    SQL> select rownum, empno, ename, sal  from emp order by sal desc     
    ——发现行号是跟着行走的。查询结果顺序变了,行号依然固定在原来的行上。
    行号始终使用默认顺序:select * from emp所得到的顺序,没有排序,没有分组等。
    只要能使行号随着重新排序,发生改变,那么取前三条记录,就是我们想要的结果。
            
    2.rownum只能使用<, <=符号,不能使用>,>=符号。
    想将现有的表进行分页。1-4第一页,5-8第二页……
    SQL> select rownum, empno, ename, sal  from emp  where rownum >=1 and rownum<=4   
    SQL> select rownum, empno, ename, sal  from emp  where rownum >=5 and rownum<=8    
    执行,发现结果:未选定行。原因是rownum不能使用>=符号。Where永远为假。
    
    与行号生成的机制有关:Oracle中的行号永远从1开始——取了1才能取2,取了2才能取3,…… <=8可以是因为1234567挨着取到,而>=5不行,因为没有1234,不能直接取5。
    
    ---------------分页唯一办法---------------------
    分析:我只能1-4,不能5-8,所以看下面的select语句
    我可以取到1-8的数据集合,我剔除1-4,剩下的,就是5-8了。就可以避开rownum的限制了,让rownum不再是伪列
    
    select rownum,empno,ename,sal
        from (select empno,ename,sal
              from emp
              order by sal desc)
        where rownum<=8
    
    --------------------------
    select *
    from (select rownum r,empno,ename,sal
          from (select empno,ename,sal
                from emp
               order by sal desc)
          where rownum<=8)
    where r >= 5
    #不能使用rownum>=5,因为解析器会解析成伪列
    #所以要利用别名机制!
    
    6.6.2找到emp表中薪水大于本部门平均薪水的员工
    image2.png
    select e.empno,e.ename,e.sal,d.avgsal
    from emp e,(select deptno,avg(sal) avgsal
                from emp
                group by deptno ) d
    where e.deptno = d.deptno and e.sal > d.avgsal
    
    #组函数avg()不能再where句中使用
    #利用别名机制
    
    6.6.3统计每年入职的员工个数
    image3.png
    ——员工的入职年份是已知条件——1980、1981、1982、1987这4个。
    要统计每年入职的人数,一定要知道每个员工的入职日期,可以通过查询hiredate列来得到。
    
    SQL> select hiredate from emp; 
    
    结合查询结果,以1981年为例,如何统计出81年入职的有多少个人呢?可以从写C程序的角度入手。
    
    思路:定义一个计数器count=0; 有一个81年的员工,就+1,不是81的就+0;最后查看count的值就可以了。
          求和,使用sum函数,内部逻辑:sum(if 是81年 then  +1  else  +0)
          也就是取员工的hiredate列的“年”那一部分,与81比较,进行判断。
          to_char(hiredate, ‘yyyy’) 得到年,与‘1981’进行比较。
    
    select count(*) Total,
    sum(decode(to_char(hiredate,'yyyy'),'1980',1,0)) "1980",
    sum(decode(to_char(hiredate,'yyyy'),'1981',1,0)) "1981",
    sum(decode(to_char(hiredate,'yyyy'),'1982',1,0)) "1982",
    sum(decode(to_char(hiredate,'yyyy'),'1987',1,0)) "1987"
    from emp
    
    image4.png
    6.7 单行子查询只能使用单行操作符;多行子查询只能使用多行操作符
    6.7.1单行子查询

    单行子查询就是该条子查询执行结束时,只返回一条记录(一行数据)。

    使用单行操作符:

    = 、>、 >=、< 、<=、 <>

    • 单行子查询,只能使用单行操作符(=号、>号)
    • 在一个主查询总可以有多个子查询
    • 子查询里面可以嵌套多层子查询
    • 子查询也可以使用组函数。子查询也是查询语句,适用于前面所有知识

    非法使用子查询的例子

    select employee_id,last_name
    from employees
    where salary = 
                  (select MIN(salary)
                   from  employees
                    group by department_id);
    #group by 可以在子查询中使用
    #但是返回的是多条数据
    #单行操作符“=”连接了返回多条记录的子查询,查询语句执行会报错
    
    6.7.2多行子查询

    子查询返回2条记录以上就叫多行

    多行操作符有:

    IN    等于列表中的任意一个
    ANY 和子查询返回的任意一个值比较
    ALL  和子查询返回的所有值比较
    

    IN(表示集合中):
    查询部门名称为SALES和ACCOUNTING的员工信息
    分析:
    部门名称在dept表中,员工信息在emp表中
    子查询应先去dept表中将SALES和ACCOUNTING的部门号得好,交给主查询员工信息

    SQL>select *
        from emp
        where deptno in (select deptno
                         from dept 
                         where dname='SALES' or dname='ACCOUNTING');
    
    使用多表查询 来解决问题
    SQL>select e.*
        from emp e,dept d
        where e.deptno=d.deptno and (d.dname='SALES' or d.dname='ACCOUNTING');
    

    ANY(表示和集合中的任意一个值比较,这里理解为比最小的大)

    查询薪水比30号部门任意一个员工高的员工信息

    分析:首先查出30号部门的员工薪水的集合,然后 > 它就得到该员工信息

    SQL>select *
        from emp
        where sal > ANY(select sal
                        from emp
                        where deptno = 30);
    

    ALL(表示和集合中的所有制比较):

    查询薪水比30号部门所有员工高的员工信息

    SQL>select *
        from emp
        where sal > all(select sal from emp where deptno=30);
    
    同样,将该题改成单行子句查询
    SQL>select *
        from emp 
        where sal >(select max(sal)
                    from emp
                    where deptno=30);
    

    对于any和all来说,究竟取最大值还是取最小值,不一定。将上面的两个例子中的“高”换成“低”,any和all就各取相反的值了

    6.8子查询中Null

    判断一个值等于、不等于空,不能使用=和!=号,而应该使用is和not

    如果集合中有NULL值,不能使用not in 如:not in(10,20,NULL),但是可以使用in。为什么呢?

    先看一个例子:
    查询不是老板的员工信息:
    分析:不是老板就是树上的叶子节点。在emp表中有列mgr,该列表示该员工的老板的员工号是多少。那么,如果一个员工的员工号在这列中,那么说明这员工是老板,如果不在,说明他不是老板

    SQL>select * from emp where empno not in (select mgr from emp); 但是运行没有结果,因为有NULL
    
    查询是老板的员工的员工信息:
    SQL>select * from emp where empno in (select mgr from emp);
    
    
    还是我们之前Null的结论:in(10,20,null)可以,not in(10,20,null)不可以
    
    例如:a not in(10,20,null)等价于(a != 10) and (a != 20) and (a != NULL)
    
    因为,not in 操作符等价于 !=ALL ,最后一个表达式为假,整体假。如
    
    select emp.last_name
    from employees emp
    where emp.employee_id NOT IN
                             (select mgr.manager_id
                              from employees mgr);
    a in(10,20,null) 等价于 (a = 10) or (a=20) or (a = null)只要有一个为真即为真
    
    in 操作符等价于 = Any
    
    所以在子查询中,如果有Null值,主查询使用where xxx=子查询结果集。永远为假
    

    继续,查询不是老板的员工信息。只要将空值去掉即可

    SQL>select * 
        from emp 
        where empno not in
                          (select mgr 
                           from emp 
                           where mgr is not null)
    

    7.集合运算

    查询部门号是10和20的员工信息:

    1. SQL>select * from emp where deptno in(10,20);
    2. SQL>select * from emp where deptno=10 or deptno=20;
    3. 集合运算
        select * from emp where deptno = 10 加上
        select * from emp where deptno = 20
    集合运算所操作的对象是两个或者多个集合,而不再是表中的列(select 一直在操作表中列)
    
    7.1集合运算符

    集合运算的操作符。A∪B、A∩B、A-B

    image5.png
    select * from emp where deptno=20
    union
    select * from emp where deptno=10
    这是一条SQL语句
    
    7.2集合运算需要注意的问题
    1. 参与运算的各个集合必须列数相同,且类型一致
    2. 采用第一个集合的表头作为最终使用的表头
    3. 如果要order by,必须在每个集合后使用相同的order by,最后结果会按要求排序
    4. 可以使用括号()先执行后面的语句
    select deptno,job,sum(sal) from emp group by deptno,job
    union
    select deptno,to_char(null),sum(sal) from emp group by deptno,job
    union
    select to_number(null),to_char(null),sum(sal) from emp;
    #这个是4.4group by增强例子的另一种解法,代替了rollup
    
    #因为deptno是数字类型,所以是to_number(null)
    

    相关文章

      网友评论

        本文标题:一、Oracle(1)

        本文链接:https://www.haomeiwen.com/subject/nrhrvftx.html