sql进阶部分

本文基于oracle

函数

1. 数值函数

  • 四舍五入 round(n [, m])
    m > 0 : 保留小数点后m位
    m < 0 : 保留小数点前m位
    m省略事即m=0的情况
  • 向上取整 ceil(n)
  • 向下取整 floor(n)
  • 取绝对值 abs(n)
  • 取余 mod(n, m)
    如果n,m中有个为null,则结果为null
  • 求n的m次幂 power(n, m)
  • 求平方根 sqrt(n)
  • 三角函数(很少用) sin(n)、asin(n)、cos(n)、 acos(n) 、tan(n)、 atan(n)

2. 字符函数

  • 转大写 upper(char)
  • 转小写 lower(char)
  • 获取子字符串 substr(str [,m [, n]])
    str–源字符串
    m–取字符串的开始位置,m为负数表示从字符的尾部算起
    n–截取字符串的位数
  • 求字符串长度 length(str)
  • 连接字符串 concat(str1, str2)
    oracle 与操作符||的作用一样
  • 去除子字符串 trim(str1 from c2)
  • ltrim(c1 [,c2])
  • rtrim(c1, [,c2])
  • trim(str1)
    去取str1左右两端空格
  • replace(str1, s_str1 [, rstr ])
    str1–为原始字符串
    s_str1–为str1中需要替换的字符串
    rstr–要替换为的字符 ,省略rstr参数表示用空间替换

3. 日期函数

  • 系统时间 sysdate
    oracle 日期默认格式 DD-MM-RR
    mysql中使用now()
  • 指定日期上添加月份 add_months(date, i)
    i > 0 在date的月份上加i
    i < 0 在date的月份上减i
  • 求指定日期的下个day的日期 next_day(date,day)
    如果day=’星期一’,返回的结果就是date后的星期一的日期
  • 日期所在月的最后一天 last_day(date)
  • 间隔多少月 month_between(date1, date2)
  • extract(date from datetime)
    date-值可以年、月、日、时、分、秒
    – mysql经常使用的两个函数
    TIMESTAMPDIFF(interval,datetime_expr1,datetime_expr2);
    TIMESTAMPADD(interval,int_expr,datetime_expr)

4. 转换函数

  • 日期转成字符to_char(date [,fmt [, param]])
    date–要转换的日期
    fmt–要转换的格式
    param–日期的语言,一般省略
select to_char(sysdate,'YYYY-MM-DD HH24:MI:SS') from dual;
  • 字符转成日期 to_date(datestr [,fmt [, param]])
    不能真正转成想要的格式
  • 数字转成字符 to_char(num[,fmt])
    9:显示数字并忽略前面的0
    0:显示数字,位数不知用0补齐
    .或D:显示小数点
    ,或G:显示千位符
    $:美元符号
    S:加正负号(前后都可以)
select to_char(1234.56,'$999.99') from dual;
  • 字符转成数字 to_number(num[,fmt])
select to_number('$1,000','$9999') from dual;

高级查询

1. 分组查询

  • nvl函数使分组函数无法忽略空值
select count(nvl(comm, 0)) from emp;
--mysql中
select count(ifnull(comm, 0)) from emp;
  • wx_concat 行转列
select deptno, wx_concat(name) from emp grounp by deptno;
--mysql中
select deptno, grounp_concat(name) from emp group by deptno;
  • grounp by…having…order by
select deptno, avg(sal) from emp group by deptno having avg(sal)>3000 order by avg(sal) ;
  • group by语句的增强
select deptno, job, sum(sal) from emp group by rollup(deptno, job);
--mysql中
select deptno, job, sum(sal) from emp group by deptno, job with rollup;

上面语句的查询结果相当于下面语句结果相加

select deptno, job, sum(sal) from emp group by deptno, job;
select deptno, sum(sal) from emp group by deptno;
select sum(sal) from emp;

2. 连接查询

笛卡尔积是多表查询的基础

  • (不)等值连接
select e.deptno, e.ename, e.sal, d.dname
from emp e, dept d where e.deptno = d.deptno;
select e.deptno, e.ename, e.sal, s.grade
from emp e, salgrade s where e.sal between s.lowsal and s.hisal;
  • 外连接
    核心:通过外连接,把对于连接条件不成立的记录,仍然包含在最后的结果中
    左外连接:当连接条件不成立时,等号左边的表仍然被包含
    右外连接:当连接条件不成立时,等号右边的表仍然被包含
    按部门统计员工
select d.deptno, d.dname, count(e.empno) from emp e, dept d where e.deptno = d.deptno group by d.deptno, d.name;

上面的语句是不对的,对于没有人员的部门不满足上面的where,所以这时我们需要使用右外连接将部门表中不满足条件的记录包含到最后的结果当中。

--注意:此处是右连接
select d.deptno, d.dname, count(e.empno) from emp e, dept d where e.deptno(+) = d.deptno group by d.deptno, d.name;
  • 自连接
    通过别名,将同一张表视为多张表
select e.ename, p.ename
from emp e, emp p where e.mgr = p.empno

注意:自连接不适用于大表,因为根据笛卡尔积,会产出庞大的记录,这时我们需要使用层次查询。层次查询还是在一张表中查询,层次查询中会建立一颗树,层次查询可以从书中的任意节点开始往下查找

--level是伪列,表示节点在树中的层级,根节点在第一层
select level, empno, ename, sal, mgr from emp connect by prior empno = mgr start with empno = 7563 order by 1;
-- start with mgr is null order by 1;

3. 子查询

  • 可以使用子查询的位置:from where select having
  • 不可以使用子查询的位置:group by
  • 子查询排序问题
    rownum 行号,oracle中的伪列
    –行号永远按照默认的顺序生成
    –行号只能使用<、<=,不能使用>、>=
    查询员工工资最高的三位员工的信息
select rownum, empno, ename, sal from emp where rownum <=3 order by sal desc;

执行这条查询语句最终得不到正确的结果,原因就是rownum永远按照默认的顺序生成。得到正确结果,需要使用下面的语句:

select rownum, empno, ename, sal from (select * from emp order by sal desc) where rownum <=3; 

mysql没有rownum伪列,可以使用下面的方式生成行号

set @rownum = 0;
select empno, ename, sal from (select @rownum:=@rownum+1 as rownum, empno, ename, sal from emp order by sal desc) as temp where temp.rownum <=3 ;
  • 相关子查询
    查询员工工资大于所在部门平均工资的员工信息
select empno, ename, sal, (select avg(sal) from emp where deptno = e.deptno) avgsal from emp e where sal > (select avg(sal) from emp where deptno = e.deptno);
  • 单行子查询和多行子查询
    子查询返回一条记录的成为单行子查询,相应的返回多条记录的子查询称为多行子查询
    单行子查询操作符:=、 <、 >、 <=、 >=、 <>
    多行子查询操作费:in–等于列表中的任何一个;any–和子查询返回的任意一个值比较;all–和子查询返回的所有值比较。
--单行子查询示例
select * from emp where job = (select job from emp where empno = 7566) andr sal > (select sal from emp where empno = 7568);
--多行子查询
select * from emp where sal any (select sal from emp where empno = 30);
  • 子查询中的空值问题
    对于单行子查询,如果返回的结果为空,那么最终的结果也即为空。
    对于多行子查询返回结果中包含空值,看下官方的一段说明:
    sql进阶部分_第1张图片
    对于多行子查询返回结果中包含空值的情况,我们要在子查询中排除空值的情况。

  • 使用exists代替in,使用no exists代替not in

--效率高
select loginname, pwd, vip_id from tb_vip v where exists (select * from tb_terminal where vip_id = v.vip_id group by vip_id having count(vip_id)>100);
--效率低
select loginname, pwd, vip_id from tb_vip where vip_id in (select vip_id from tb_terminal group by vip_id having count(vip_id) > 100);

PL/SQL(对SQL的扩展)

PLSQL是在SQL语言中增加过程处理语句,如分支、循环,使SQL语言具有过程处理能力。
PLSQL基本结构:

declare
    声明部分(变量、光标、例外)
begin 语句序列(DML语句) exception 例外处理语句 end;
/

1. 基本语法

  • 变量
    (1) 基本变量类型:char, varchar2, date, number, boolean, long
    示例:psal number(7,2); pname varchar2(20)
    (2) 引用型变量和记录型变量
    引用型变量其类型由引用的表中字段的类型决定
--引用型变量
set serveroutput on declare pename emp.ename%type;
    psal emp.sal%type;
begin select ename, sal into pename, psal from emp where empno = 7838;
    dbms_output.put_line(pename || '的薪水是' || psal);
end;
/

记录型变量保存了整条记录的值

--记录型变量
set serveroutput on declare emp_rec emp%rowtype;
begin select * into emp_rec from emp where empno = 7838;
    dbms_output.put_line(emp_rec.ename || '的薪水是' || remp_rec.sal);
end;
/
  • if语句
    判断用户从键盘输入的数字
set serveroutput on --接受一个键盘输入值 --num:地址量 accept num prompt '请输入一个数字' declare pnum number := &num;--取地址值
begin if pnum = 0 then dbms_output.put_line('输入数字是0');
    elsif pnum = 1 then dbms_output.put_line('输入数字是0');
    else dbms_output.put_line('其他数字');
    end if;
end;
/
  • 循环语句
    (1) while循环
while total <= 3000 loop
    循环体
end loop;

(2) loop循环

loop
exit when 退出条件
    循环体
end loop;

(3) for循环

for i in 1..3 loop
    循环语句
end loop;

示例

set serveroutput on declare pnum number := 0;
begin loop exit when pnum > 10;
        pnum := pnum+1;
    end loop;
end;
/

2. 光标(游标)

  • 光标就是一个结果集

光标在declare关键字下声明
cursor 光标名[(参数名 参数类型[,参数名 参数类型]…)] is select 语句
打开光标:open 光标名;
取光标中一行值: fetch 广播名 into 变量名;
关闭关闭:close 光标名;

set serveroutput on declare cursor c1 is select ename, sal from emp;
    pename emp.ename%type;
    psal emp.sal%type;
begin open c1;
    loop
        fetch c1 into pename, psal;
        exit when c1%notfound;
        dbms_output.put_line(pename||'的薪水是'||psal);
    end loop;
    close c1;
end;
/
  • 光标的属性和限制
    %found 在光标中取得值为true,否则为false
    %notfound 未在光标中取得值为true,否则为false
    %isopen 判断关闭是否打开
    %rowcount 影响的行数
    oracle中默认在同一个会话中最多只能开启300个光标,我们可以在管理员账户下修改这个默认值,使打开的最多光标数设置成合适值。
    sql进阶部分_第2张图片

3. 异常(exception)

  • 系统异常
    系统异常包括:no_data_found、two_many_rows、zero_divide、value_error等
set serveroutput on
declare
    pename emp.ename%type;
begin
    select ename into pename from emp where empno = 1234;
exception
    when no_data_found then dbms_output.put_line('没有找到该员工');
    when others then dbms_output.put_line('其它例外');
end;
/
  • 自定义异常
    在declare下声明一个变量,变量类型为exception,这样就自定义了一个异常,后面使用raise抛出自定义异常
set serveroutput on declare cursor cemp is select ename from emp where deptno = 30;
    pename emp.ename%type;
    --自定义异常
    no_emp_found exception;
begin open cemp;
    fetch cemp into pename;
    if cemp%notfound then raise no_emp_found;
    end if;
    close cemp;
exception
    when no_emp_found then insert into emp values('fetch语句没有获得数据或者数据已经处理完');
end;
/

存储过程

1. 存储过程

使用下面的语法来创建存储过程:
create [or repalce] procedure 过程名(参数列表)
as 声明变量信息(不可省略)
plsql程序体

create or replace procedure queryinfo(eno in number, pename out varchar2, psal out number, pjob out varchar) as begin select ename, sal, empjob into pename, psal, pjob from emp where empno = eno;
end;
/

2. 存储函数

函数为一命名的存储程序,可带参数,并返回一个计算结果。函数和过程的结果类似,但必须有一个return子句,用于返回函数值。使用下面的语法创建存储函数:
create [or repalce] function 过程名(参数列表) return 返回值类型
as 声明变量信息(不可省略)
plsql程序体
查询员工年收入

create or replace function(eno in number) return number as psal emp.sal%type;
    pcomm emp.comm%type;
begin select sal, comm into psal, pcomm from emp where empno = eno;
    return psal*12+nvl(pcomm, 0);
end;
/

3. 程序包

程序包包含包头和包体,包使得out参数可以返回多行记录。

  • 包头
create or replace package mypackage as
    type empcursor is ref cursor;
    procedure queryEmpList(dno in number, empList out empcursor) end mypackage;
  • 包体
create or replace package body mypackage as procedure queryEmpList(dno in number, empList out empcursor) as begin open empList for select * from emp where deptno = dno;
    end queryEmpList;
end mypackage;
  • 在程序中调用
public void testCursor()
{
    String sql = "{call MYPACKAGE.queryEmpList(?,?)}";
    Connection conn = null;
    CallableStatement call = null;
    ResultSet rs = null;
    try
    {
        conn = JDBCUtils.getConnection();
        call = conn.prepareCall(sql);
        call.setInt(1,10);
        call.registerOutParameter(2, OracleTypes.CURSOR);
        call.execute();
        rs = (OracleCallableStatement)CallableStatement.getCursor(2);
        while(rs.next)
        {
            int empno = rs.getInt("empno");
            String name = rs.getString("ename");
            double sal = rs.getDouble("sal");
            String job = rs.getString("empjob");
        }
    }catch(Exception ex)
    {
        ex.printStackTrack();
    }finally
    {
        JDBCUtils.closeConnection(rs, call, conn);
    }
}

触发器

数据库触发器是一个与表关联的,存储的PL/SQL程序。每当一个特定的数据操作语句(insert、delete、update)在指定的表上发生时,oracle自动的执行触发器中定义的语句序列。
使用下面的语法创建一个触发器
create [or replace] trigger 触发器名称
{before|after} {insert|delete|update [of 列名]}
on 表名
[for each row [when(条件)]]
plsql程序体

应用场景

  • 复杂的安全性检查
  • 数据的确认
  • 数据库审计
  • 数据库的同步和备份

触发器种类

  • 语句级触发器
    在指定的操作语句操作之前或之后执行一次,不管这条操作语句影响多少行,语句级触发器针对的是表。
create or replace trigger securityemp before insert on emp begin if to_char(sysdate, 'day') in ('星期六','星期日') or to_number(to_char(sysdate,'hh24')) no between 9 and 18 then raise_appliction_error(-20001,'禁止在非工作时间插入新员工');
    end if;
end;
/
  • 行级触发器
    操作语句作用的每一条记录都会触发。在行级触发器中使用:old和:new伪记录变量,识别值的状态.
    :old和:new代表同一条记录,:old代表操作之前,这一行的值;:new代表操作之后,这一行的值。
create or replace trigger checksalary before update on emp for each row begin if :old.sal > :new.sal then raise_application_error(-20002,'涨后的薪水不能少于涨后的薪水');
    end if;
end;
/
  • 示例
    备份同步更新
create or replace trigger snyc_salary after update on emp for each row begin update emp_back set sal = :new.sal where empno = :new.empno;
end;
/

你可能感兴趣的:(sql)