00_Oracle mysql学习笔记整理

1.为什么要学习数据库

-- 市面上99%的软件都会用到数据库

-- 查询、增、删、改、创表删表等操作

2.Oracle数据库

2.1 登录

-- 数据库启动关闭    
    -- 以oracle用户登录linux

    -- sqlplus / as sysdba
        -- 以数据库管理员的方式登录oracle数据库 ,默认用户名是sys
    -- sql>  startup 
        -- 启动数据库实例  (在sqlplus中执行)
        -- 实例:oracle的一组服务

    -- sql>  shutdown   
        -- 关闭数据库实例
    -- exit  退出sqlplus
-- 数据库监听服务启动和关闭

    -- lsnrctl start
        -- 启动数据库的监听服务
        -- 如果不启动监听服务,oracle远程没有办法连接上的

    -- lsnrctl stop
        -- 关闭数据库监听服务

    -- lsnrctl status
        -- 查看监听服务的状态
-- 修改scott用户密码
    -- 必须要oracle的管理员权限去修改
    -- sqlplus / as sysdba 
    -- sql> alter user scott identified by "123456" ;

-- 登录scott用户

    -- sqlplus 用户名/密码
        -- 本地登录
        -- sqlplus scott/11

    -- sqlplus 用户名/密码@//服务器ip/实例名(orcl)
        -- 远端登录

    -- sqlplus scott/11@//192.168.177.233/orcl

-- 查看scott用户方案所拥有的表
    -- 使用scott用户登陆之后
    -- select * from tab;

2.2 简单查询:

select * 
from tableName;

select 列名1 as "别名1",列名2,表达式.... 
from 表名
-- 查询emp表的所有数据  
	select * 
	from emp;

-- 设置行宽 
	set linesize 140;

-- 设置页高 
	set pagesize 50;

-- 查询员工号,姓名,月薪,奖金,年薪,年收入 并修改列名为中文
    select 
    	empno as "工 号",
    	ename as 姓名,
    	sal 月薪,
    	comm 奖金 ,
    	sal*13 年薪, 
    	sal*13 + nvl(comm,0)  年收入
    from emp;

-- 小结:
    -- 1 sql查询可以按照列名来查询
    -- 2 可以使用表-- 达式作为列名,数据库会自动计算出每一列每一行表达式的值
    -- 3 可以对列起别名

-- nvl 函数:nvl(f1,f2)
	-- 如果参数1(表达式)为null,则返回参数2

-- null 数据类型 
	-- 做任何的数值运算结果都为null

-- 查看员工表所有部门编号并去除重复
    select distinct deptno 
    from emp;
-- 说明:
	-- distinct 的作用:就是去除重复的行

-- 计算表达式3+2
    select 3+2 
    from dual
-- 说明:
	-- dual是一张虚表,我们可以从这张表里边去计算表达式

2.3 单条件查询:

-- select ...
from ...
where cond

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

-- 查询员工KING的信息
    select * 
    from emp 
    where ename = 'KING';

-- 注意:数据中的字符串是大小写敏感的

-- 单引号和双引号的说明:
    -- 数据中的字符串,使用单引号
    -- 单引号用于数据的字符串
    -- 双引号用于数据库对象名 ,表名 列名 ...

-- 查询薪水不等于1250的员工信息
-- 大于      小于      不等于       等于
> >=     <  <=      !=  <>      =
    select * 
    from emp 
    where sal <> 1250;

-- 查询入职日期为1981年11月17日的员工信息
    -- 日期的比较可以直接使用字符串来比较,数据库会自动做一个隐式的转换
        select * 
        from emp 
        where hiredate = '1981-11-17';
    -- ORA-01861: 文字与格式字符串不匹配
    -- 当用字符串跟日期进行比较的时候,注意日期格式
    
    -- 查询当前会话的环境变量
    select * 
    from v$nls_parameters;    
      
    select * 
    from emp 
    where hiredate = '17-11月-81'
    
    -- 修改当前会话的时间日期格式字符串
    alter session 
    set NLS_DATE_FORMAT='yyyy-mm-dd' 

ed 命令

-- sqlplus 中 使用 ed 命令,打开一个编辑器,用来编辑上一条执行过的sql语句
-- 修改这条语句(最后不要使用分号),换行之后添加一个单独的 / 表示结束  ,保存退出,
-- 然后敲入/回车执行上一条指令

2.4 多条件查询:

--       -- 与       或       非
    C   &&       ||       !
    sql and      or       not

-- 查询10号部门中月薪为1300的员工
    select * 
    from emp 
    where deptno=10 and sal =1300

-- 查询部门号是10或者20的员工信息
    select * 
    from emp 
    where deptno = 10 or deptno = 20

    select * 
    from emp
    where deptno in (10,20)

-- 查询部门不是10和20的员工信息
    select * 
    from emp
    where deptno not in (10,20)

-- 查询工资介于1000到2000之间的员工
    select *
    from emp
    where sal >=1000 and sal <=2000

    select *
    from emp
    where sal between 1000 and 2000
    -- between and 是一个闭区间

-- 查询1981年2月(含2月)到82年2月(不含2月)入职的员工信息
    1981-2-1   1982-1-31

    select * 
    from emp
    where hiredate between '1981-02-01' and '1982-01-31'

-- 查询没有奖金的员工信息
    select * 
    from emp
    where comm = null

    -- 结论:
    --     null做任何逻辑运算,结果都为假

	-- 用下面这个
    select *
    from emp
    where comm is null

-- 查询有奖金的员工信息
    select *
    from emp
    where comm is not null

2.5 模糊查询

-- 每次掉线之后重新连接之后都要设置行宽页高的问题
-- 配置 C:\app\itcast\product\11.2.0\client_1\sqlplus\admin\glogin.sql
--         每次登录sqlplus之后都会马上执行的一些指令
select ..-- .
from ..
where 列名 like '模式字符串'

--     %   表示:任意字符 任意次数
--     _   表示:任意字符 单次

--     sql 的模式匹配没有默认转义字符,可以指定转义字符
--     like '模式子串' escape '转义字符'

-- 查询员工首字母是S的员工信息
    select *
    from emp
    where ename like 'S%'

-- 查询名字是四个字母的员工信息
    select *
    from emp
    where ename like '____'

-- 查询姓名带下划线的员工信息
insert into emp values(8000,'A_B','CLERK',7902,sysdate,900,null,20);
    select * 
    from emp
    where ename like '%\_%' escape '\'

2.6 排序

--     select ...
    from ....
    where ...
    order by 列名1,列名2... desc|asc
    
--  asc  从小到大 升序  (默认)
--  desc 从大到小 降序
--  desc和asc 只对单列生效;对多列分别指定 会对指定的这些列分别生效

-- 员工信息按先后入职日期排序
    select * 
    from emp 
    order by hiredate

-- 员工信息按薪水从大到小排序 
    select *
    from emp
    order by sal desc

-- 员工信息按部门号和薪水排列
    select *
    from emp
    order by deptno , sal

--     排序的规则,先排第一列,第一列相同的情况下再按照第二列来排序
--     以此类推

-- 员工信息按部门和薪水排列,降序 
-- desc和asc 只对单列生效
    select *
    from emp
    order by deptno desc, sal desc

-- 员工信息按奖金倒序
    select *
    from emp
    order by comm desc nulls last|first

--     结论:
--         null值会影响排序

-- 员工信息按第2列排序
-- 按照第几列来排序是指结果集的第几列,不是表的第几列
    select ename , empno
    from emp
    order by 2
	

-- 员工信息按别名排序
    select ename , empno "工号"
    from emp
    order by "工号"

2.7 字符函数:单行函数

-- 	lower   将字符串转为小写
--  upper   将字符串转为大写
--  initcap 将首字母变成大写
select lower('hELLO'),upper('heLlo'),initcap('hEllO') 
from dual
        LOWER UPPER INITC
		----- ----- -----
        hello HELLO Hello
        
concat
	select concat('hello ','world') 
    from dual
-- concat 默认只接收两个参数
    select concat('hello ',concat('world','aaa')) 
    from dual;
-- oracle还提供另一种方法,使用 ||
    select 'hello'||'world'||123||456 
    from dual

substr
	select substr('helloworld',3),substr('helloworld',3,5) 
    from dual
substr(字符串,起始位置)    从起始位置开始一直取到字符串的结尾
substr(字符串,起始位置,n) 从起始位置开始取n个字符

instr  用来查找A字符串在B字符串中的位置
    select instr('helloworld','llo') 
    from dual

lpad,rpad   左右填充
-- 将字符串进行填充到10的长度,如果不够就填充#号
    select lpad('hello',10,'#') 
    from dual

-- trim  裁剪字符串两边空白的字符
    select trim('   Hello   ') 
    from dual;
-- 指定裁剪字符串两边特定的字符
-- 裁剪字符串两边大写的H
    select trim('H' from 'HHHHhelloHHHHH') 
    from dual;

replace
    select replace('hello','l','*') 
    from dual;

2.8 数值函数

    round   四舍五入
    trunc   截取数值
    ceil、floor  向上取整、向下取整
    mod     取模 , %
	
	select 
		round(45.926,2),
		trunc(45.926,2),
		ceil(45.926),
		floor(45.926),
        mod(1000,600)
	from dual

2.9 转换函数

-- to_char :数字转字符串 
-- 		to_char(数值表达式,'格式字符串')
--         格式字符串:
--             L 本地货币符号
--             9 表示一位数值
--  将薪水转化为本地货币字符型
	select empno,ename,sal,to_char(sal,'L9999.99') 
	from emp
        
to_number:字符串转数字 
    select to_number('¥800.00','L9999.99') 
    from dual;

to_char:日期转字符串 
    select empno,ename,to_char(hiredate,'dd,mm,yyyy') 
    from emp;

to_date:字符串转日期 
    select to_date('17,12,1980','dd,mm,yyyy') 
    from dual

说明:
	to_char用什么格式字符串,to_number或者to_date就可以使用该格式字符串转化回来

2.10 日期函数

sysdate
	-- 显示昨天、今天、明天
	select sysdate - 1 昨天,sysdate 今天 , sysdate +1 明天 
	from dual
	
-- 时间日期数据类型计算单位是1天

    -- 计算员工工龄,按照日、周、月、年显示
	select sysdate-hiredate 日,
           (sysdate-hiredate)/7,
           (sysdate-hiredate)/30,
           (sysdate-hiredate)/365from emp


months_between 
-- 计算两个时间日期的数值相差多少个月
	select sysdate-hiredate 日,
           (sysdate-hiredate)/7,
           (sysdate-hiredate)/30,
           months_between(sysdate,hiredate)2,
           (sysdate-hiredate)/365from emp

add_months
-- 计算明年今日
	select add_months(sysdate,12) 
	from dual

last_day
-- 计算月份的最后一天
	select last_day(sysdate) 
	from dual

next_day
-- 计算下一个星期几
	select next_day(sysdate,'星期五') 
	from dual

2.11 通用函数

-- nvl(exp,val)
-- 	如果exp 为null ,就返回val
-- 
-- nvl2(exp,val1,val2)
-- 	如果exp为null,就返回val2,否则返回val1

	-- 查询员工信息,有奖金就显示'有奖金',没奖金就显示'没奖金'
    select ename,nvl2(comm,'有奖金','没奖金') 
    from emp

2.12 条件语句 case、decode

--     case 
--     		when 
--     		then 
--     		else 
--     end
--     总裁决定给大家涨工资,主管涨1000,销售涨500,其他涨200
        switch(job)
        {
            case 'MANAGER':  sal+1000; break;
            case 'SALESMAN' : sal + 500 ;break;
            default : sal+200 ; break;
        }
-- case 是sql标准
    select ename,job,sal "涨前工资",
        case job
            when 'MANAGER' then sal + 1000
            when 'SALESMAN' then sal + 500
            else sal+200
        end "涨后工资"
    from emp

-- decode 不是sql标准,oracle自身拓展出来
--         decode(expr,val1,val2,val3,val4 ....,default)
--             判断表达式expr 的值,如果为val1,就返回val2
--             如果为val3,就返回val4 依次类推,如果都不是,就返回最后一个default
   select ename,job,sal "涨前工资",
          decode(job,
                 'MANAGER',sal+1000,
                 'SALESMAN',sal+500,
                 sal+200)
           "涨后工资"
   from emp

2.13 统计函数:多行计算函数

-- 多行函数相对于单行的函数来说,必须遍历整个表或者部分数据才可以计算出结果

-- sum
	-- 求员工工资总和
    select sum(sal) 
    from emp

-- count 只要某一行某一列有值,count就会+1
    -- 求员工数量,有奖金的员工数
    select 
    	count(empno),
    	count(*),
    	count(comm) 
    from emp
    
    select sum(1) 
    from emp;
    
-- 说明:
-- 	null不会参与统计函数的计算

	-- 求工作岗位数量
    select count(distinct job) 
    from emp

-- max/min
--     求员工最高工资和最低工资

-- avg
    -- 求员工平均工资
    select avg(sal) 
    from emp
    
    -- 求员工平均奖金(三种方式)
    select 
    	   sum(comm)/count(*),
           sum(comm)/count(comm),
           avg(comm)
	from emp

2.14 分组统计

-- -- select ..
-- -- from ...
-- -- where cond1
-- -- group by 列1,列2...       --根据某一列或者多列来分组
-- -- having cond2

	-- 查询各部门平均工资
    select * -- 此处是错误示范
    from emp
    group by deptno

-- 注意:如果使用了group by来进行分组,select后面的列有限制
-- 能填的只能是group by 中出现过的列, 或者是 统计函数
    
    select deptno,avg(sal)
    from emp
    group by deptno

-- 小结:
-- 	   多行函数(统计函数)在没有分组的前提下,统计的是全表
-- 	   (有where就是通过where筛选)的数据,
-- 	   在有分组的前提下,统计的就是分组


	-- 查询平均薪水大于2000的部门
    select deptno,avg(sal)
    from emp
    where avg(sal)>2000 --第3行出现错误:ORA-00934: 此处不允许使用分组函数
    group by deptno
--注意:在where条件中不允许使用统计函数
-- 使用having
    select deptno,avg(sal)
    from emp
    group by deptno
    having avg(sal) > 2000

	-- 求10号部门员工的平均薪水
    select deptno , avg(sal)
    from emp
    where deptno = 10
    group by deptno
-- 换成having
    select deptno , avg(sal)
    from emp
    group by deptno
    having deptno = 10

havingwhere的区别
--结论:能够用where 就不要用having

-- sql查询先后顺序:
    select ...
    from ...
    where cond1
    group by ....
    having cond2
    order by ...
-- 1 遍历表,查询数据,每一行数据都要进行严格筛选,筛选条件就是where cond1,得到结果集1
-- 2 对结果集1求分组 ,计算统计函数的表达式的值,得到结果集2
-- 3 对结果集2 进行筛选,筛选条件就是 having cond2  ,得到结果集3
-- 4 排序
-- 5 之后筛选出我们想要的列,返回最终结果集

-- where筛选是先于having筛选的,
-- 先在where进行筛选,能够减少后边计算的数据量,从而提高查询语句性能

2.15 多表查询

1. 多表查询

--多表查询:要获取的数据不在同一个表中,要使用多表查询
--	select ...
--  from 表1,表2....
--  where ...

	--查询员工信息:员工号,姓名,月薪和部门名称
    --员工、姓名、月薪 在 EMP表
    --部门名称 在 dept
    select empno,ename,sal,dname,e.deptno
    from emp e,dept d
    where e.deptno = d.deptno

--join on:另外一个连接的语法(SQL标准),使用 join on
--将from 后面的逗号,换成join ,where 换成on
    select empno,ename,sal,dname,e.deptno
    from emp e join dept d
    on e.deptno = d.deptno 

2. 表的连接:等值连接和不等值连接

--多表查询也称为表的连接,where筛选的条件就称为连接条件

--根据连接条件里边使用的是等号还是不等号,可以称为等值连接和不等值连接
	
	--查询员工信息:员工号,姓名,月薪和月薪级别(salgrade表)
    select empno,ename,sal,grade 
    from emp,salgrade
    where sal between losal and hisal

3. 表的连接:外连接

	--按部门统计员工人数,显示如下信息:部门号,部门名称,人数(注意统计40号部门)
    select d.deptno,d.dname,count(empno)
    from dept d , emp e
    where d.deptno = e.deptno(+) -- 左外连接
    group by d.deptno,d.dname
-- sql标准:
    select d.deptno,d.dname,count(empno)
    from dept d left join emp e-- 左外连接
    on d.deptno = e.deptno
    group by d.deptno,d.dname
    
--外连接:如果想保留没有通过连接条件而被筛选掉的数据,那么就使用外连接

--分方向,这是oracle的特有的玩法
--     想保留哪一张表的额外的数据,就在连接条件等号的另一边使用(+)

--左外连接
--     想保留左边表的数据,使用左外连接
--     就在等号右边使用(+)

--右外连接
--     想保留右边表的数据,使用右外连接
--     就在等号左边使用(+)

2.16 自连接

-- 自连接:要查询的数据在同一张表,但是不在同一行,表自己跟自己做表连接
	
	--查询员工信息:将员工的主管名字也显示出来(KING的主管是他自己)
	select e.ename || '''s manager is '|| nvl(e2.ename,'his wife')
    from emp e ,emp e2
    where e.mgr = e2.empno (+)

-- sql中的字符串,使用两个单引号来转义输出单个单引号

2.17 子查询

-- 子查询:一个select语句里边可以嵌套其他select语句,就是子查询
-- 	
-- 	查询比scott工资高的员工信息 
--     1 先查询出scott的工资
        select sal 
        from emp 
        where ename = 'SCOTT'  -- 3000 
--     2 查询工资大于3000的员工信息
        select * 
        from emp 
        where sal > 3000

    select * 
    from emp 
    where sal >(
        --查询出scott的工资
        select sal 
        from emp 
        where ename = 'SCOTT'
        )

--注意事项
-- 	注意书写风格
-- 	父查询和子查询可以是不同的表,子查询返回的结果父查询可以使用即可

--  查询部门名称是 'SALES' 的员工信息
--  1 先查询出salse部门编号(先根据部门名称查询出部门编号)
--  2 查询出是该部门编号的所有员工信息(然后根据部门编号查询出所有的员工信息)
    select *
    from emp
    where deptno =
    (
        --查询出sales的部门编号
        select deptno
        from dept
        where dname = 'SALES'
    )
--父查询的select、from、where、having都可以嵌套子查询
    select ...
    from ...
    where ...
    group by ...   --不能后置子查询
    having ...
    order by ...   --不能后置子查询

--  select 后置子查询:
--  查询10号部门的员工号、员工姓名、部门编号、部门名称
--  select empno,ename,deptno ,
            (
            --将10号部门的名称查询出来
			select dname 
            from dept
            where deptno = 10
            ) "部门名称"
	from emp
    where deptno = 10

--  from 后置子查询: 
--  查询员工的姓名、月薪和年薪(使用select * from _________)
        select *
        from (
            select ename,sal,sal*13 年薪
            from emp
        )

--  where后置子查询: 
--  查询与ward(雇员名字)相同岗位并且月薪比他高的员工信息
--      1 查询ward的岗位
            select job 
            from emp 
            where ename = 'WARD'
--      2 查询ward的工资
            select sal 
            from emp 
            where ename= 'WARD'
--      3 拼凑,查询岗位相同,工资比其高的员工的信息
            select *
            from emp
            where job= 
            (
                --ward的岗位
                select job 
                from emp 
                where ename = 'WARD'
            )
            and sal > 
            (
                --ward的工资
                select sal 
                from emp 
                where ename= 'WARD'
            )
    
-- having后置子查询:
-- 查询部门最低月薪高于30号部门的部门以及其最低月薪
--     1 查询30号部门的最低月薪
            select min(sal)
            from emp
            where deptno = 30
            --950
--     2 确定哪个部门的最低月薪比30号部门要高
            select deptno,min(sal)
            from emp
            group by deptno
-- 		3 组合使用
            select deptno,min(sal)
            from emp
            group by deptno
            having min(sal) > 
            (                
                select min(sal)
                from emp
                where deptno = 30
            )
-- 单行子查询只能使用单行操作符,
-- 单行操作符,就是对某个单一数据的比较操作
--    =  >  >=  <  <= 等
-- 多行子查询只能使用多行操作符 (这里都是指单列)    
-- 多行操作符,就是对一个集合的比较操作
-- IN  ANY  ALL
 
    --查询部门名称为SALES和ACCOUNTING的员工信息 (IN)
    --in 跟单行操作符 = 对应
    --1 先查询出 sales 和 accounting 的部门编号
        select deptno 
        from dept 
        where dname ='SALES' or dname = 'ACCOUNTING'
        --10,30
    --2 再查询出是这两个部门编号的员工信息
        select *
        from emp
        where deptno in (10,30)
    --3 组合使用
        select *
        from emp
        where deptno in 
        (
            select deptno 
             from dept 
             where dname ='SALES' or dname = 'ACCOUNTING'
         )
            
--  查询月薪比30号部门任意一个(某一个any)员工高的员工信息
--  any
--  ANY(表示和集合中的任意一个值比较): 
--      查询比30号部门最低月薪高的员工信息

--      1 先查询出 30部门的最低月薪   --950
--      2 查询比950 高的员工信息

        select *
        from emp
        where sal > any
        (
            --先将30号部门的所有员工的月薪都查询出来
            select sal
            from emp
            where deptno = 30
        )
--  查询比30号部门所有员工工资都高的员工信息
--  all
--      查询比30号部门最高月薪高的员工信息

        select *
        from emp
        where sal > all
        (
            --先将30号部门的所有员工的月薪都查询出来
            select sal
            from emp
            where deptno = 30
        )
-- 注意子查询中返回的null值影响最终计算结果
-- 查询不是主管的员工信息
-- 1 先查询是主管的empno
        select mgr 
        from emp
-- 2 取反
        select *
        from emp
        where empno not in
        	( 
            	select mgr 
                from emp
        	)
-- 假设 
-- 	select mgr 
-- 	from emp  
-- 结果是 (7902,7698,null)

    where empno 
    not in ( 7902,7698,null)
-- 换算成C的代码
--     empno!=7902 && empno!=7698 && empno!=null
-- 注意:null做任何逻辑运算,结果都为假

-- 如果题设是查询是主管的信息
        where empno in ( 7902,7698,null)
            empno==7902 || empno==7698 || empno==null

-- SQL解析:
--		一般先执行子查询(内查询),
--		再执行父查询(外查询);
--		关联子查询除外
--      一般子查询只执行一次,先于父查询来执行,关联子查询除外

2.18 集合运算

交集:             intersect
    多个集合都有的那部分
    
并集:             union
    将多个集合合在一起 (去重)
    
全并集:            union all
    将多个集合合在一起(不去重)
    
差集:分方向         minus
    求一个集合对于另一个集合没有的那部分
-- 部门号是10的员工和部门号是20的员工信息做并集(以及全并集)
    select *
    from emp
    where deptno = 10

    union

    select *
    from emp
    where deptno = 20

-- 用10号部门的员工信息和 10,20号部门的员工信息做交集
    select *
    from emp
    where deptno in (10,20)

    intersect

    select *
    from emp
    where deptno = 10

-- 用10,30的员工减去10,20的员工信息
    select *
    from emp
    where deptno in (10,30)

    minus

    select *
    from emp
    where deptno in (10,20)
--  查询三个部门的工资信息并分组统计,格式如下:
        DEPTNO JOB                  SUM(SAL)
    ---------- ------------------ ----------
            10 CLERK                    1300
               MANAGER                  2450
               PRESIDENT                5000
                                        8750

            20 ANALYST                  6000
               CLERK                    1900
               MANAGER                  2975
                                       10875

            30 CLERK                     950
               MANAGER                  2850
               SALESMAN                 5600
                                        9400

                                       29025
--  提示:
--      SQL plus中使用以下命令来去掉分组重复的deptno
            break on deptno skip 2;
--      使用以下命令来恢复
            break on null;

--以上的结果是使用3个集合拼凑在一起

--    1 每个部门,每个岗位的工资总和
        select deptno,job,sum(sal)
        from emp
        group by deptno , job
        
            DEPTNO JOB         SUM(SAL)
        ---------- --------- ----------
                20 CLERK           1900
                30 SALESMAN        5600
                20 MANAGER         2975
                30 CLERK            950
                10 PRESIDENT       5000
                30 MANAGER         2850
                10 CLERK           1300
                10 MANAGER         2450
                20 ANALYST         6000
--    2 每个部门的工资总和
        select deptno,sum(sal)
        from emp
        group by deptno

            DEPTNO   SUM(SAL)
        ---------- ----------
                30       9400
                20      10875
                10       8750
--    3 整个公司的工资总和
        select sum(sal)
        from emp
                  SUM(SAL)
                ----------
                     29025
--  将以上3个集合拼在一起
        select deptno,job,sum(sal)
        from emp
        group by deptno , job

        union

        select deptno,null,sum(sal)
        from emp
        group by deptno

        union

        select null,null,sum(sal)
        from emp

  • 集合运算注意事项:
    • 参与运算的各个集合必须列数相同,且类型一致
    • 采用第一个集合的表头作为最终使用的表头,(别名也只能在第一个集合上起)
    • 可以使用括号修改各个sql执行的优先级

2.19 新增数据:

-- insert into 表名 values(val1,val2,val3....)
--     根据表的设计,将val1,val2对应上每一列的定义,进行插入
--
-- insert into 表名(列名1,列名2,....) values(val1,val2 ....)
--     根据前面列的顺序来调整values里边这些字段值的顺序
--
-- 往部门表里边插入以下几行信息
    DEPTNO DNAME                        LOC
---------- ---------------------------- --------------------------
        50 SUPPORT                      WASHINGTON
        60 TEST
        70 PURCHASING
  
    insert into dept values(50,'SUPPORT','WASHINGTON')
    insert into dept(deptno,loc,dname) values(60,null,'TEST')
    insert into dept(deptno,dname) values(70,'PURCHASING')

2.20 修改数据

-- update 表名 
-- set 列名1=值1 , 列名2 = 值2...  
-- [where cond]
-- 
-- 将60号部门的LOC改成MIAMI
    update dept 
    set loc='MIAMI' 
    where deptno=60

-- 将50号部门的部门名字改成 SUPPORT1 ,LOC改为NULL
    update dept 
    set dname='SUPPORT1' , loc = null 
    where deptno = 50

2.21 删除数据

-- delete 
-- from 表名 
-- where cond
-- 删除数据都是以行为单位

-- 删除部门号为50的部门信息
    delete 
    from dept 
    where deptno = 50
    
-- 删除部门号大于40的部门信息
    delete 
    from dept 
    where deptno > 40

2.22 事务

银行转账
    A   100  --如果A向B转账100
    B    0   --此时端断点了或者B销户了
        update  将 A - 100 
            机房断电 / B销户了
        update  将 B + 100

    A   100  --如果A向B转账100(事务1)
    B    0   --此时端断点了或者B销户了
    C   100  --同时C也向B转账(事务2)
    -- 多个事务并行执行时,要和串行执行时结果一致
-- 事务由以下部分组成
-- 一个或多个 DML 语句 
-- 一个 DDL(Data Definition Language – 数据定义语言) 语句 
-- 一个 DCL(Data Control Language – 数据控制语言) 语句 
-- DML语句:增删查改  -- 数据操作语言
-- 增 insert
-- 删 delete
-- 改 update
-- 查 select
-- DDL语句 -- 数据定义语言 -- 一个DDL语句就是一个事务
-- create table(创建表) 
-- alter table(修改表) 
-- truncate table(清空表) 
-- drop table(删除表) 
-- create view(视图) 
-- create index(索引) 
-- create sequence(序列) 
-- create synonym(同义词) 
-- DCL语句:对事务进行操作  --数据控制语言(Data Control Language) 
-- commit(提交) 
-- rollback(回滚) 

2.23 隔离级别

隔离级别
    某表某一行某一列有一个值  0

1 读未提交
    事务A 开启
                            事务B 开启
                            读取该值 0
    修改该值为100                                
                            读取该值 100
    提交事务

2 读已提交  (Oracle默认)
    事务A 开启
                            事务B 开启
                            读取该值 0
    修改该值为100                                 
                            读取该值 0
    提交事务commit
                            读取该值 100
                            结束事务

3 可重复读  (MySQL默认)
    事务A 开启
                            事务B 开启
                            读取该值 0
    修改该值为100                                
                            读取该值 0
    提交事务commit
                            读取该值 0
                            结束事务
                                            事务C 开启
                                            读取该值 100
                                            结束事务
4 串行化   
    相当于单线程 ,服务器上同一时刻只有一个事务在跑

    事务A 开启
    修改该值为100    
    提交事务commit            
                            事务B 开启
                            读取到 100
                            结束事务

2.24 事务的控制

在事务的过程中可以使用 : savepoint  保存点名字 创建一些保存点

下次如果发现之前某一步做错了,不至于直接 rollback回到原始事务开始的地方
可以:rollback to savepoint 保存点名字

一旦当前事务提交或者回滚,在当前事务建立的savepoint将失效

2.25 习题

1. 子查询和行号

-- 【第一题】:找到员工表中工资最高的前三名, 要求按如下格式输出
-- 补充知识:rownum 行号(伪列)   
    select rownum , emp.* 
    from emp 
-- 在结果中看到行号,行号是筛选数据的过程中自动生成的,并不真正存在于表中

    select rownum , ename, sal 
    from emp
    order by sal desc -- 问题是如何取出前三行
--  行号是乱的,工资的降序是对的
--  结论:行号在排序前已经生成好了

--  解决思路:
--      1.先排序,
--      2.再生成行号 
--      3.再筛选 行号 < 3
--  1.子查询先排序,
--  2.父查询生成行号
--  3.父查询最后进行一次筛选
    select rownum, e.*
    from 
        (
            select empno,ename,sal
            from emp
            order by sal desc 
            -- 一般不在子查询中使用order by,但在Top-N问题中,必须使用order by
        ) e
    where rownum <=3

-- 注意:行号只能用 <  <= 不能用 > >=

--  延伸的问题: 分页问题,按照工资降序排序,取5-8 名 
--      也是依赖行号
--      rownum不能直接用 > 来比较  ,除了 >=1 
--  	与行号的生成逻辑有关
--  	行号生成在where条件判断的时候
--
--  where rownum <=3
--      1      king          where 1<=3 为真   通过筛选
--      2      ford          where 2<=3 为真   通过筛选
--      ...
--
--  where rownum >=5
--      1      king          where 1>=5 为假   没通过筛选
--      1      ford          where 1>=5 为假   没通过筛选
--      ...
--          因为king没有通过筛选,1号的行号保留
--
--  (分页问题的解决)先让行号固定下来,不要筛选的时候再生成
    select *
    from 
    (
        select rownum r, e.*
        from 
        (
            select empno,ename,sal
            from emp
            order by sal desc
        ) e
    )
    where r >=5 and r <=8

2.自连接和关联子查询

--【第二题提示】:找到emp表中薪水大于本部门平均薪水的员工
--    empno ename sal avgsal
--	  多表查询   ,自行构造另一张表 
--    from 后置子查询

--1 查询每个部门的平均工资
    select deptno,avg(sal)
    from emp
    group by deptno 

--2 用以上的结果和emp表,进行多表连接
    select *
    from emp a,
    (
        select deptno,avg(sal) avg_sal
        from emp
        group by deptno 
    ) b
    where a.deptno = b.deptno and a.sal > b.avg_sal

--关联子查询 更适合用来解决这道题!!!

关联子查询

--关联子查询
--    子查询不能独立的执行,必须依赖父查询

    select *
    from emp outer
    where sal > (
        --查询出该员工所在部门的平均工资
        select avg(sal)
        from emp inner
        where inner.deptno = outer.deptno
    )

--    一般先执行子查询(内查询),再执行父查询(外查询);关联子查询除外
--    关联子查询原理,每次父查询迭代,都会执行一次子查询
--    例子:
--        allen   部门号 30
--        执行子查询 ,查询出30号部门的平均工资
--            select avg(sal) from emp where deptno = 30
--        最后再通过父查询的where条件来判断
--
--        jones   部门号是20
--        执行子查询,查询出20号部门的平均工资
--			select avg(sal) from emp where deptno = 20
--        ...
--
--        结论:
--            关联子查询中,where条件迭代多少次,子查询就执行多少次,导致效率不高
--
--            慎用关联子查询!!!!

3. 01标记法

--【第三题提示】:统计每年入职的员工个数
--    1980 1
--    1981 2 
--    .....
    select hire_year,count(*)
    from 
    (
        select to_char(hiredate,'yyyy') hire_year
        from emp
    )
    group by hire_year

--    1980 1
--    1981 2 
--    .....
-- 思路一可以使用01 标记法(依赖 条件语句)
--             1980   1981   1982  ....
--     1980      1      0      0
--     1981      0      1
--     1981      0      1
--     1981
--     1981
--     1981
--     1981
--     1987
--     1981
--     1981
--     1987
--     1981
--     1981
--     1982
select  
        count(*) total,
        sum("year_1980") "1980",
        sum("year_1981") "1981",
        sum("year_1982") "1982",
        sum("year_1987") "1987"
from 
    (
        select hire_year,
            decode(hire_year,'1980',1,0) "year_1980",
            decode(hire_year,'1981',1,0) "year_1981",
            decode(hire_year,'1982',1,0) "year_1982",
            decode(hire_year,'1987',1,0) "year_1987"
        from 
            (
                select to_char(hiredate,'yyyy') hire_year
                from emp
            )
    )

2.26 oracle表操作

1.创建表

--    create table 表名
--    (
--        列名1 类型1,
--        列名2 类型2,
--        .....
--    )
    
    
    
--另一种创建表的方式
--    创建一张表emp2,数据跟emp表一样
        create table emp2 
        as 
        select * from emp
--创建一个员工表t1:员工号id(整数),和姓名name(字符串)
   create table t1
   (
       id number,
       name varchar2(30)
   )
   sql 标准:varchar  可变长的字符串
   oracle 自身拓展了一下:varchar2
       
--char    定长的字符串
--char(30):跟C中char[30]差不多,定长,不管存储什么数据,就固定占用30个字符空间
--varchar(30):最多也只能存储30个字符的空间,但是如果只存储hello,只会占用约5个字符空间
--char查询性能是要高于varchar,明确数据的长度就是固定的,可以直接用char
--例子:身份证号18位   



--    创建一张表emp3,数据跟emp表一样,只有表结构,没有数据
        create table emp3 
        as select * from emp;
        delete from emp3;
        
        create table emp4 
        as 
            select * 
            from emp 
            where 1=2 -- 恒为假,所以数据不会拷贝过去

2.修改表

    alter table tableName xxxxx

--    1 往t1表里边添加一列 email varchar(40)
        alter table t1 add email varchar(40)
        
--    2 修改t1表email列名为address
        alter table t1 rename column email to address
        
--    3 修改t1表address列的类型为varchar(50)
        alter table t1 modify address varchar(50)
        
--    4 删除t1表address列
        alter table t1 drop column address
        
--    5 修改t1表名为t2
        rename t1 to t2

3.清空表

    delete from 表名 
    
    truncate table t1 
1.  delete 逐条删除表“内容”,
    truncate 先摧毁表再重建。 
    (由于delete使用频繁,Oracle对delete优化后delete快于truncate)
    
2.  delete 是DML语句,truncate 是DDL语句。
    DML语句可以闪回(flashback)和回滚rollback,
    DDL语句不可以闪回和回滚。
    (闪回:做错了一个操作并且commit了,对应的撤销行为。了解)
    
3.  由于delete是逐条操作数据,所以delete会产生碎片,
    truncate不会产生碎片。
    (同样是由于Oracle对delete进行了优化,让delete不产生碎片)。
    两个数据之间的数据被删除,删除的数据——碎片,整理碎片,数据连续,行移动。
    
4.  delete不会释放空间,
    truncate 会释放空间。
    用delete删除一张10M的表,空间不会释放。
    而truncate会。
    所以当确定表不再使用,应使用truncate。

4.删除表

    drop table 表名
        oracle提供了回收站机制,删表的时候先移动到回收站

    purge table 表名
        从回收站中彻底删除某张表

    drop table 表名 purge
        彻底删除表,不移动到回收站

    oracle 提供一个闪回的技术,能够将表还原到以前的某个时间点
        flashback table 表名 to before drop

    show recyclebin
    purge recyclebin 
        清空回收站

5. 表的约束

    1 非空
        not null
        
    2 唯一
        unique

    3 检查
        check
        
    4 主键
        primary key
        一个表中只能有一个主键(一列或者多列组合而成)
		主键约束隐含:not null + unique
	5 外键
    	foreign key
        一张表的某一列(或多列)参照另一张表的某一列(或多列)
        在这张表填入的数据必须要在另一张表中能够找到,或者为null
        涉及的问题:父表的数据删除(更新)了子表数据怎么办
            1 不允许父表执行删除
            2 父表删除数据之后,子表对应的数据全部设置为null
            3 父表删除数据之后,子表对应的数据也跟着删除,级联删除
        
-- create table 表名
-- (
-- 	   列名1 类型1 [constraint 约束名 约束类型] constraint 约束名2 约束类型2 ..,
--     列名2 类型2 [constraint 约束名 约束类型] ...  ,
--     ....
-- )  

create table student 
(
    sid number constraint pk_student primary key, --学生Id主键约束
    sname varchar2(20) constraint nn_student_name not null,--学生姓名非空约束
    email varchar2(20) constraint un_student_email unique --学生邮件唯一约束
    constraint nn_student_email not null,   --同时邮件可再设非空,没有“,”
    age number constraint chk_student_age_min check(age > 10),  --学生年龄设置check约束
    gender varchar2(6) constraint chk_student_gender check(gender in ('男', '女')),
    deptno number constraint fk_student references dept (deptno) ON DELETE SET NULL 
    -- references dept (deptno) -- 外键约束(参照dept表中的deptno列)
    -- ON DELETE SET NULL -- 父表对应的列删除,子表对应的列值设置为空
) 


--可以查看指定表(如 student)的约束,注意表名必须大写。 
select 
	constraint_name, 
	constraint_Type, 
	search_condition      
from user_constraints 
where table_name='STUDENT' 

2.27 视图

视图提供的目的就是为了简化查询

视图的优点                      
    1. 简化复杂查询: 
        原来分组、多表、子查询等可以用一条 select * from xxxview 代替。      
        视图可以看做是表的复杂的 SQL 一种封装。 
    2. 限制数据访问: 
        只看视图的结构和数据是无法清楚视图是怎样得来的。
        可以限制数据的访问。 
        例如: 银行项目,所谓的各个“表”都是“视图”,并有可能只是“只读视图” 
注意: 
	1. 视图不能提高性能 
    2. 不建议通过视图对表进行修改。 


​ create view 视图名称
​ as
​ 子查询

​ 要注意,创建视图需要权限,需要oracle管理员对scott赋予权限
​ 在centos 上 使用 sqlplus / as sysdba
​ grant create view to scott

删除视图
drop view 视图名

创一个视图,用来观看10号部门的员工信息
    create view v_emp_10
    as 
        select * 
        from emp 
        where deptno = 10

    create table t_emp_10
    as 
        select * 
        from emp 
        where deptno = 10

    视图是不存在数据,只是一个查询语句的结果
    每次执行 select 去查看视图的时候,都会执行视图的sql语句
    视图其实也是一条查询sql语句的封装 

2.28 序列

创建一张表
    create table t1
    (
        id number primary key,
        name varchar2(30) not null
    )
插入数据
	insert into t1 values(1,'name-1')
	insert into t1 values(2,'name-2')
人工去记住这个idd递增,不靠谱,让计算机自身去记住这个id,每次自增


​ 创建序列:create sequence sequenceName
​ 删除序列:drop sequence sequenceName
​ currval
​ 当前的序列的值
​ select myseq.currval from dual
​ 第 1 行出现错误:
​ ORA-08002: 序列 MYSEQ.CURRVAL 尚未在此会话中定义
​ 注意:获取currval 不能早于第一次获取nextval
​ nextval
​ 从序列获取下一个值
​ select myseq.nextval from dual
​ 注意:每次sql执行,nextval只会获取一次值
​ (一条SQL语句中多次使用nextval,但是取到的是相同的值,因为只获取一次值)

​ 使用序列进行插入数据
​ insert into t1 values(sequenceName.nextval,‘name-’||sequenceName.nextval)
​ insert into t1 values(sequenceName.nextval,‘name-’||sequenceName.nextval)
​ …

2.29 索引

原理,对数据某一列(或者多列)进行排序,将排序好的数据以及对应的行地址存起来,下次查询数据的时候先查询索引,然后再提取数据
    
创建索引    
	create index 索引名 on 表名(列名)
	create index 索引名 on 表名(列名1,列名2 ....)
删除索引 
	drop index 索引名字

    create index inx_emp_deptno on emp(deptno)
    
    索引创建完成之后,不用我们去管,正常做查询,
    数据库自动判断我们查询条件有没有用到索引,有用到就自动使用


​ 什么情况下用到索引
​ create index inx_emp_deptno on emp(deptno)
​ where deptno = 10 – 用到了索引

    create index idx2 on emp(deptno,job)   复合索引
        where job = 'CLERK'              -- 没有用到索引
        where deptno>10 and job = 'xxxx' -- 使用到了索引

2.30 同义词

为hr.employees表创建同义词
    create synonym 别名 for 某数据库对象
    create synonym hremp for hr.employees;

删除同义词
    drop synonym 别名

3.mysql

3.0 创建scott表 并插入数据

-- 00_scott_MySQL.sql
-- source /home/lwh/Desktop/study/test/00_scott_MySQL.sql


drop database if exists lwh_scott;
create database lwh_scott character set utf8;

use lwh_scott;

create table bonus
(
  ename VARCHAR(10),
  job   VARCHAR(9),
  sal   int,
  comm  int
);

create table dept
(
  deptno int not null,
  dname  VARCHAR(14),
  loc    VARCHAR(13)
);
alter table dept
  add constraint PK_DEPT primary key (deptno);

create table emp
(
  empno    int not null,
  ename    VARCHAR(10),
  job      VARCHAR(9),
  mgr      int,
  hiredate DATE,
  sal      int,
  comm     int,
  deptno   int
)
;
alter table emp
  add constraint PK_EMP primary key (EMPNO);
alter table emp
  add constraint FK_DEPTNO foreign key (DEPTNO)
  references dept (DEPTNO);


create table salgrade
(
  grade int,
  losal int,
  hisal int
)
;


insert into dept (deptno, dname, loc)
values (10, 'ACCOUNTING', 'NEW YORK'),
(20, 'RESEARCH', 'DALLAS'),
(30, 'SALES', 'CHICAGO'),
(40, 'OPERATIONS', 'BOSTON');

insert into emp (empno, ename, job, mgr, hiredate, sal, comm, deptno)
values (7369, 'SMITH', 'CLERK', 7902, '1980-12-17', 800, null, 20),
(7499, 'ALLEN', 'SALESMAN', 7698, '1981-02-20', 1600, 300, 30),
(7521, 'WARD', 'SALESMAN', 7698, '1981-02-22', 1250, 500, 30),
(7566, 'JONES', 'MANAGER', 7839, '1981-04-02', 2975, null, 20),
(7654, 'MARTIN', 'SALESMAN', 7698, '1981-09-28', 1250, 1400, 30),
(7698, 'BLAKE', 'MANAGER', 7839, '1981-05-01', 2850, null, 30),
(7782, 'CLARK', 'MANAGER', 7839, '1981-06-09', 2450, null, 10),
(7788, 'SCOTT', 'ANALYST', 7566, '1987-04-19', 3000, null, 20),
(7839, 'KING', 'PRESIDENT', null, '1981-11-17', 5000, null, 10),
(7844, 'TURNER', 'SALESMAN', 7698, '1981-09-08', 1500, 0, 30),
(7876, 'ADAMS', 'CLERK', 7788, '1987-05-23', 1100, null, 20),
(7900, 'JAMES', 'CLERK', 7698, '1981-12-03', 950, null, 30),
(7902, 'FORD', 'ANALYST', 7566, '1981-12-03', 3000, null, 20),
(7934, 'MILLER', 'CLERK', 7782, '1982-01-23', 1300, null, 10);

insert into salgrade (grade, losal, hisal)
values (1, 700, 1200),
(2, 1201, 1400),
(3, 1401, 2000),
(4, 2001, 3000),
(5, 3001, 9999);





--  mysql> show tables;
--  +---------------------+
--  | Tables_in_lwh_scott |
--  +---------------------+
--  | bonus               |
--  | dept                |
--  | emp                 |
--  | salgrade            |
--  +---------------------+
--  4 rows in set (0.00 sec)
--  
--  mysql> select * 
--      -> from bonus;
--  Empty set (0.00 sec)
--  
--  mysql> select * from dept;
--  +--------+------------+----------+
--  | deptno | dname      | loc      |
--  +--------+------------+----------+
--  |     10 | ACCOUNTING | NEW YORK |
--  |     20 | RESEARCH   | DALLAS   |
--  |     30 | SALES      | CHICAGO  |
--  |     40 | OPERATIONS | BOSTON   |
--  +--------+------------+----------+
--  4 rows in set (0.00 sec)
--  
--  mysql> select * from emp;
--  +-------+--------+-----------+------+------------+------+------+--------+
--  | empno | ename  | job       | mgr  | hiredate   | sal  | comm | deptno |
--  +-------+--------+-----------+------+------------+------+------+--------+
--  |  7369 | SMITH  | CLERK     | 7902 | 1980-12-17 |  800 | NULL |     20 |
--  |  7499 | ALLEN  | SALESMAN  | 7698 | 1981-02-20 | 1600 |  300 |     30 |
--  |  7521 | WARD   | SALESMAN  | 7698 | 1981-02-22 | 1250 |  500 |     30 |
--  |  7566 | JONES  | MANAGER   | 7839 | 1981-04-02 | 2975 | NULL |     20 |
--  |  7654 | MARTIN | SALESMAN  | 7698 | 1981-09-28 | 1250 | 1400 |     30 |
--  |  7698 | BLAKE  | MANAGER   | 7839 | 1981-05-01 | 2850 | NULL |     30 |
--  |  7782 | CLARK  | MANAGER   | 7839 | 1981-06-09 | 2450 | NULL |     10 |
--  |  7788 | SCOTT  | ANALYST   | 7566 | 1987-04-19 | 3000 | NULL |     20 |
--  |  7839 | KING   | PRESIDENT | NULL | 1981-11-17 | 5000 | NULL |     10 |
--  |  7844 | TURNER | SALESMAN  | 7698 | 1981-09-08 | 1500 |    0 |     30 |
--  |  7876 | ADAMS  | CLERK     | 7788 | 1987-05-23 | 1100 | NULL |     20 |
--  |  7900 | JAMES  | CLERK     | 7698 | 1981-12-03 |  950 | NULL |     30 |
--  |  7902 | FORD   | ANALYST   | 7566 | 1981-12-03 | 3000 | NULL |     20 |
--  |  7934 | MILLER | CLERK     | 7782 | 1982-01-23 | 1300 | NULL |     10 |
--  +-------+--------+-----------+------+------------+------+------+--------+
--  14 rows in set (0.00 sec)
--  
--  mysql> select * from salgrade;
--  +-------+-------+-------+
--  | grade | losal | hisal |
--  +-------+-------+-------+
--  |     1 |   700 |  1200 |
--  |     2 |  1201 |  1400 |
--  |     3 |  1401 |  2000 |
--  |     4 |  2001 |  3000 |
--  |     5 |  3001 |  9999 |
--  +-------+-------+-------+
--  5 rows in set (0.00 sec)

3.1 登录mysql

--直接在shell环境中 使用以下命令
	mysql -u用户名 -p密码
	mysql -uroot -proo123

--其中root是用户名,该用户跟linux中的root不是同一个用户
--mysql有自己的账号体系,其中的数据库管理员也叫root
oracle 账号体系,一个用户对应一个方案,用户名就是方案名
在mysql中一个用户是可以拥有多个方案

show databases|schemas  
    查看当前用户拥有哪些数据库(方案)

use databaseName
    切换到某个数据库进行操作

show tables 
    查看当前该数据库中有什么表


结论:
    mysql账号体系比oracle要多一层,要操作方案(数据库)
    
Oracle一个用户对应一个方案(数据库 database);该方案下有多张表(table)
MySQL一个用户用多个方案(数据库 database);每个方案下都有多张表(table)

3.2 库操作

--创建数据库d1 并指定默认字符集为UTF8
create database d1 default charset= 'UTF8';
--查看创建数据库的语句
show create database d1;
--删库 !!!危险,请勿操作
drop database 库名

3.3 表操作

  • 创建表
-- 创建表
--     t1
--     ( id 整形 
--       name 可变字符串30长度
--     )
create table t1
(
    id int,
    name varchar(30)
)

edit打开编辑器

-- "edit" 命令可以打开编辑器,默认是使用v,来编辑上一次输入的SQL语句
-- 把MySQL的SQL语句写进去,":wq "保存退出,不要以;结尾
-- 回到主界面,输入 ";" 开始执行SQL语句
  • 查看表结构
--desc 表名
--    查看表结构
  • 修改表中的列
-- 添加一列
-- 添加一列email varchar(30)
	alter table t1 add email varchar(30)

--修改列的属性
--修改name(姓名这列) 增加长度到40字符(修改属性)
    alter table t1 modify name varchar(40)

--修改列名,修改列的属性
--修改email 列重命名 到 address ,并且修改类型为 varchar(40)
    alter table t1 change email address varchar(40)
  
--显示创建该表的sql语句
	show create table t1;
--删除列
--删除列address
    alter table t1 drop column address
  • 修改表
--修改表名
--修改表t1表名为T2,注意大小写(MySQL中,表名是大小写敏感的)
    rename table t1 to T2

--删除表T2
    drop table t2
--注意:mysql中表名等数据库对象名大小写敏感

3.4 操作表中的数据

--首先创建表employee
create table employee
(
    id int,
    name varchar(20),
    sex int,
    birthday date,
    salary double,
    entry_date date,
    resume text
);

--添加数据
insert into employee values(1,'张三',1,'1983-04-27',15000,'2012-06-24','一个大牛');
insert into employee(id,name,sex,birthday,salary,entry_date,resume) values(2,'李四',1,'1984-02-22',10000,'2012-07-24','一个中牛');
insert into employee(id,name,sex,birthday,salary,entry_date,resume) values(3,'王五',0,'1985-08-28',7000,'2012-08-24','一个小虾');


--  mysql> select * from employee;
--  +------+--------+------+------------+--------+------------+--------------+
--  | id   | name   | sex  | birthday   | salary | entry_date | resume       |
--  +------+--------+------+------------+--------+------------+--------------+
--  |    1 | 张三   |    1 | 1983-04-27 |  15000 | 2012-06-24 | 一个大牛     |
--  |    2 | 李四   |    1 | 1984-02-22 |  10000 | 2012-07-24 | 一个中牛     |
--  |    3 | 王五   |    0 | 1985-08-28 |   7000 | 2012-08-24 | 一个小虾     |
--  +------+--------+------+------------+--------+------------+--------------+
--  3 rows in set (0.00 sec)

--1 将所有员工薪水都增加500元。
    update employee 
    set salary = salary+500 

--2 将王五的员工薪水修改为10000元,resume改为也是一个中牛
    update employee 
    set salary = 10000 ,resume = '也是一个中牛' 
    where name = '王五'

--3 查询员工的月薪以及年薪
    select 
    	name,
    	salary 月薪, 
    	salary * 13 年薪
    from employee

--删除一行记录
--4 删除表中姓名为王五的记录
    delete from employee 
    where name = '王五'

--5 删除表中所有记录
    delete from employee
    --mysql 中 默认 没有打开事务,所有没法回滚

--6 使用truncate删除表中记录
    truncate table employee

3.5 综合查询

--综合查询:
--    创建学生表,记录学生的id、姓名、语文、英语、数学课程的成绩
    create table student
    (
        id int,
        name varchar(20),
        chinese int,
        english int,
        math int
    );
    往表里边插入数据
    insert into student(id,name,chinese,english,math) values(1, '黄真',80,85,90);
    insert into student(id,name,chinese,english,math) values(2,'归辛树',90,95,95);
    insert into student(id,name,chinese,english,math) values(3,'李寻欢',80,96,96);
    insert into student(id,name,chinese,english,math) values(4,'叶开',81,97,85);
    insert into student(id,name,chinese,english,math) values(5,'袁承志',85,84,90);
    insert into student(id,name,chinese,english,math) values(6,'何红药',92,85,87);
    insert into student(id,name,chinese,english,math) values(7,'何铁手',75,81,80);
    insert into student(id,name,chinese,english,math) values(8,'夏雪宜',77,80,79);
    insert into student(id,name,chinese,english,math) values(9,'任我行',95,85,85);
    insert into student(id,name,chinese,english,math) values(10,'岳不群',94,85,84);
--1.查询表中所有学生的信息
	select * from student;
--2.查询表中所有学生的姓名和对应的英语成绩
	select name,english 
    from student;
--3.查询所有出现的英语成绩(去掉重复)
	select distinct english 
    from student
    order by english;
--4.在所有学生英语分数上加10分特长分
	update student 
    set english = english+10;
--5.统计每个学生的总分(要用别名)
	select name , (chinese+english+math) as "总分"
    from student ;
--6.查询姓名为何铁手的学生成绩
	select *  
	from student 
	where name = "何铁手";
--7.查询英语成绩大于90分的同学
	select * 
    from student
    where english > 90;
--8.查询英语分数在 85-95之间的同学
	select *  
	from student 
	where english between 85 and 95;
--9.查询数学分数为84,90,91的同学
	select *  
	from student 
	where math in (84,90,91) 
	order by math ;
--10.查询数学分>85,语文分>90的同学
	select * 
    from student
    where math>85 and chinese>90
    order by math,chinese;
--11.对总分排序后输出,然后再按从高到低的顺序输出
	select name,(chinese+english+math) zongfen
    from student
    order by zongfen desc;
--12.对姓何的学生总成绩倒序输出
	select name,(chinese+math+english)
    from student
    where name like "何%"
    order by (chinese+math+english) desc;
--13.统计总分大于250的人数有多少?
	select count(*)
    from student
    where (chinese+math+english) > 250
--14.求最高分和最低分
	select max(zongfen),min(zongfen)
	from
	(
    	select (chinese+math+english) as zongfen
        from student
        order by (chinese+math+english) desc
    )s;
--------------------------以上自主完成--------------------------------------
--15.给学生表增加一列,class_id(班级) int类型
    alter table student 
    add class_id int

--16.将学生id为1-5的修改class_id为1班,将学生id为6-10的修改class_id为2班
  一个语句修改完成。
    update student 
    set class_id = ceil(id/5)  
    -- ceil 向上取整 0-1取整到1 1-2取整到2

--17.求各个班英语的平均分 
    select class_id , avg(english)
    from student
    group by class_id

--18.求各个班的总成绩 
    select class_id , sum(chinese+english+math)
    from student
    group by class_id

--19.求总成绩大于1300的班级
    select class_id , sum(chinese+english+math)
    from student
    group by class_id
    having sum(chinese+english+math) > 1300

3.6 数据库的函数

--用sql 求3+5*2表达式的值
       select 3+5*2;
       select 3+5*2 from dual;
       --from dual 语句在mysql中不是必须的
--今天、明天、明年今日
--  在mysql中,没有sysdate, 
--  在mysql中使用now()函数获取当前的时间
--  	日期数据+1-1 单位是秒,会自动做一个隐式转换,转成数值型

    select 
    	date_add(now(),interval -1 day) 昨天 , 
    	now() 今天 , 
    	date_add(now(),interval 1 day) 明天

--求下一分钟,下一小时(addtime函数实现)
    select 
    	now(),
    	addtime(now(),'00:01:00') 下一分钟,
    	addtime(now(),'01:00:00') 下一小时
--使用字符串拼接,输出'hello'和 'mysql'拼接值
    select concat('hello','mysql','中国')
--  不能使用oracle 的 ||

--输出'hello中国'字符串长度
    select length('hello中国')
--将当前日期以'dd-mm-yyyy' 形式转换成字符串输出
    select date_format(now(),'%d-%m-%Y');
    select date_format(now(),'%Y-%m-%d');

--将字符串'31-01-2017' 以 'dd-mm-yyyy'形式转换回时间类型
    select str_to_date('31-01-2017','%d-%m-%Y')

3.7 多表查询

--在mysql中创建scott方案(数据库)以及对应表(参照3.0创建scott表)

--查询员工号、姓名、部门编号、部门名称
    select empno,ename,e.deptno,d.dname
    from emp e, dept d
    where e.deptno = d.deptno

    select empno,ename,e.deptno,d.dname
    from emp e join dept d
    on e.deptno = d.deptno

--显示员工号,姓名,薪水,薪水级别
    select empno,ename,sal,grade
    from emp e join salgrade s
    on e.sal between s.losal and s.hisal

--显示部门编号、部门名称、人数        
    select d.deptno,d.dname , count(empno)
    from emp e right join dept d
    on e.deptno = d.deptno
    group by d.deptno,d.dname

    select d.deptno,d.dname , count(empno)
    from emp e ,dept d
    where d.deptno= e.deptno(+)--mysql不支持这种操作 
    group by d.deptno,d.dname

--显示员工姓名以及主管姓名
--  自连接
    select a.ename,a.mgr,b.empno,b.ename
    from emp a left join emp b
    on a.mgr = b.empno

    select concat(a.ename,'''s manager is ',ifnull(b.ename,'himself'))
    from emp a left join emp b
    on a.mgr = b.empno
    
--交叉连接 叉集 即笛卡尔积
	cross join

--内连接 -- inner 可以省略
	inner join
	on
	
--左外连接 -- outer可以省略(保留右边表的数据)
	left outer join
	on
	
--右外连接 -- outer可以省略(保留右边表的数据)
	right outer join
	on

--全外连接(保留左边表的数据,也,保留右边表的数据)
	full outer join 
	on

3.8 Top-N

-- 使用limit解决 Top-N 问题

--查询emp表中工资前三名的员工信息
        select *
        from emp
        order by sal desc 
        limit 3



--翻页问题:使用limit解决,非常轻松

--查询emp表中工资排5-8名的员工信息
    limit [m,]n -- []括起来,代表可省略
--        m 表示跳过前面多少条数据
--        n 表示返回多少条数据
	select * 
	from emp 
	order by sal desc limit 4,4;

3.9 表的约束

    1、唯一  unique
    2、非空  not null
    3、主键(自增)  primary key
    4、外键  foreign key
	5、没有检查约束 check ,语法上还保留,实际不生效
		但是新版本,已经生效,起码我现在这个版本是生效的!!!!
[root@lwh test]# mysql  --version
mysql  Ver 8.0.19 for Linux on x86_64 (MySQL Community Server - GPL)
-- 创建一个学生表:
-- (
--     id      学生的id  数值型  作为主键 自增,
--     name    学生的姓名 字符串型 非空,
--     email   电子邮箱  字符串类型  唯一,
--     sex     性别    字符串类型   只能填 '男' 或 '女',
--     deptno  学生所在部门号   以  dept表的deptno为外键参照
-- )

create table student
(
    id int primary key auto_increment,
    name varchar(30) not null,
    email varchar(64) unique,
    sex varchar(10) check(sex in ('男','女')),
    deptno int , 
    -- 上面每一行,后跟的是:列级约束‘
    
    -- 下面这个是:表级约束
    constraint fk_student_deptno 
    	foreign key(deptno ) references dept(deptno)   
        on delete set null -- 父表中删除了,子表设置为null
        on update cascade  -- 父表中更改了,子表也跟着改 -- 级联更新
)

--列级约束,就是定义每一列的时候直接定义约束的类型

--表级约束,就是定义整个表的约束,在所有列都定义完之后,再单独写约束,
--常见于定义:多列的主键、多列外键


insert into student(name,email,sex,deptno) 
            values('aaa','[email protected]','女',40)

3.10 mysql如何打开事务

autocommit:自动提交 、默认为true 1 
	执行一个语句,马上提交,相当于没有事务

set autocommit=0
	将自动提交关闭,其实就是打开手动提交,其实就是打开事务

3.11 mysql中的乱码问题

--查看当前会话的环境变量,观察当前会话每个部分使用的字符集
show variables like 'character%';

+--------------------------+--------------------------------+
| Variable_name            | Value                          |
+--------------------------+--------------------------------+
| character_set_client     | utf8mb4                        |
| character_set_connection | utf8mb4                        |
| character_set_database   | utf8                           |
| character_set_filesystem | binary                         |
| character_set_results    | utf8mb4                        |
| character_set_server     | utf8mb4                        |
| character_set_system     | utf8                           |
| character_sets_dir       | /usr/share/mysql-8.0/charsets/ |
+--------------------------+--------------------------------+

--1、最终解决乱码问题的方案:将以上这些字符集调整统一即可

--2、终端软件 xshell secure crt 等里面的字符集也要进行一个调整

--3、linux shell 环境下的环境变量,也会影响到最终的一个显示
--    locale 去查看这些编码

3.12 mysql 编程的基础

--mysql提供了很多api ,支持不同语言

--以下包需要通过一些方式来安装
--    apt-get install libmysqlclient-dev
--    yum install limysqlclient

--头文件 	mysql.h
--库文件 	libmysqlclient.a

-- /usr/include/mysql/mysql.h
-- /usr/local/include/fakemysql.h

-- /usr/lib64/mysql/libmysqlclient.so
--C语言的API,三种类型

--1 初始化:环境初始化,建立连接

        MYSQL *mysql_init(MYSQL *mysql)
--        返回值
--            返回的句柄,句柄不直接使用,而是给其他API来调用
--            失败:返回NULL 
            
        mysql_real_connect() --连接到MySQL服务器。
        MYSQL *mysql_real_connect(
            MYSQL *mysql, 
            const char *host,            //服务器ip
            const char *user,            //root
            const char *passwd,          //root23
            const char *db,              //lwh_scott 
            unsigned int port,           //3306
            const char *unix_socket,     //本地套接字
            unsigned long client_flag    //0
        )
--        返回值
--        	连接成功,返回MYSQL*连接句柄。返回值与第1个参数的值相同。
--       	连接失败,返回NULL。

--2 业务处理:执行sql , curd的操作

------------------------1、执行 增 删 改 查---------------------------
--      -- 执行 增 删 改 查
        int mysql_query(MYSQL *mysql, const char *query)
--        返回值:
--            查询成功:返回0
--            出现错误:返回非0值           
------------------------------------------------------------------  

------------------------2、取得结果集-----------------------------------------           
--      -- 结果集
        MYSQL_RES *mysql_store_result(MYSQL *mysql)
--        返回值:
--            成功:返回结果集的指针
--            失败:返回NULL
------------------------------------------------------------------ 

--------------1、获得列数 2、获得表头 3、根据列数 for循环输出表头---------       
        -- 列数:
        unsigned int mysql_num_fields(MYSQL_RES *result)
--      返回值:
--          返回结果集的列数
		
		-- 表头
        MYSQL_FIELD *mysql_fetch_fields(MYSQL_RES *result)
--      返回值:
--          返回每一列的字段信息,包括名字
------------------------------------------------------------------


--------------从结果集中取出一行数据----------------------------------
		
		-- 该函数返回结果集中的一行数据
		-- 连续调用就会连续遍历每一行数据,直到返回NULL		
        MYSQL_ROW mysql_fetch_row(MYSQL_RES *result)
        -- MYSQL_ROW中存储了 完整的一行数据;结合列数,才完整输出一行数据
        --结合行数,输出 所有行;解开结果集里边的数据获取每一行每一列的内容
--      返回值:
--        	成功:结果集中的一行
--        	失败:NULL
	
            typedef char **MYSQL_ROW
--            返回的一行数据:就是字符串数组
------------------------------------------------------------------

--3 垃圾回收:环境销毁,断开连接

		-- 使用结果集之后要释放资源
        void mysql_free_result(MYSQL_RES *result)
         
        -- 关闭服务器连接。
        mysql_close() 
        void mysql_close(MYSQL *mysql)

#include <mysql.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define DB_HOST "127.0.0.1"
#define DB_USER "root"
#define DB_PASSWD "root123"
#define DB_NAME "lwh_scott"
#define DB_PORT 3306

int main()
{
    
    
	//1、初始化
	MYSQL *mysql = mysql_init(NULL);
	if (mysql == NULL)
	{
    
     //报错
		fprintf(stderr, "Error in mysql_init()\n");
		exit(-1);
	}

	//2、建立连接
	if (mysql_real_connect(
			mysql,
			DB_HOST,
			DB_USER,
			DB_PASSWD,
			DB_NAME,
			DB_PORT,
			NULL,
			0) == NULL)
	{
    
     //报错
		fprintf(stderr, "Error in mysql_real_connect():%s\n", mysql_error(mysql));
		exit(-1);
	}
	else
	{
    
     //执行成功
		printf("mysql_real_connect() -- 执行成功\n");
	}

	//3.1.1、执行sql: 插入一条记录
 	const char sql_insert[] = "insert into dept(deptno,dname,loc) values(40,'OPERATIONS','BOSTON')";
	if (0 == mysql_query(mysql, sql_insert))
	{
    
     //执行成功
		printf("Execute succees: insert 执行成功\n");
	}
	else
	{
    
     //执行失败
		fprintf(stderr, "Error in mysql_query() insert :%s\n", mysql_error(mysql));
		exit(-1);
	} 
	//3.1.2、执行sql: 删除一条记录
	const char sql_delete[] = "delete from dept where deptno = 40";
	if (0 == mysql_query(mysql, sql_delete))
	{
    
     //执行成功
		printf("Execute succees: delete 执行成功\n");
	}
	else
	{
    
     //执行失败
		fprintf(stderr, "Error in mysql_query() delete :%s\n", mysql_error(mysql));
		exit(-1);
	}


	//4、垃圾回收
	mysql_close(mysql);
	return 0;
}

3.13 mysql C-API-Code-Demo1

开发需求:
	实现一个类似于 mysql的客户端 ,
	能够读取用户的输入的数据,
	拿去给服务器执行,
	获取结果集后打印输出
	
开发计划:
        1 实现连接
        2 实现插入/删除/更新
        3 实现查询
            处理返回结果集,打印
        4 实现跟用户的交互 

            使用死循环一直读取标注你输入的数据,每当有数据进来就执行SQL语句

            终端提示符:
                MySQL> 

            退出条件:
                1 ctrl + d  ->  EOF 
                    read 会返回 ,而且 返回值 是 0
                2 quit / exit
#include <mysql.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define DB_HOST "127.0.0.1"
#define DB_USER "root"
#define DB_PASSWD "root123"
#define DB_NAME "lwh_scott"
#define DB_PORT 3306

// 1、初始化 连接mysql 设置当前连接的字符集
void mysql_init_connect_setCharacter(MYSQL *mysql)
{
    
    
	// 1.1、初始化
	if ((mysql_init(mysql)) == NULL)
	{
    
    
		fprintf(stderr, "Error in mysql_init()\n");
		exit(-1);
	}

	// 1.2、建立连接
	if (mysql_real_connect(
			mysql,
			DB_HOST,
			DB_USER,
			DB_PASSWD,
			DB_NAME,
			DB_PORT,
			NULL,
			0) == NULL)
	{
    
    
		fprintf(stderr, "Error in mysql_real_connect():%s\n", mysql_error(mysql));
		exit(-1);
	}
	else
	{
    
    
		printf("mysql_real_connect() -- 执行成功\n");
	}

	// 1.3、设置UTF8编码:因为有些时候显示乱码,所以需要设置
	if (mysql_set_character_set(mysql, "utf8") != 0)
	{
    
    
		fprintf(stderr, "Error in mysql_set_character_set():%s\n", mysql_error(mysql));
		//exit(-1);
	}
	else
	{
    
    
		printf("mysql_set_character_set() utf8 -- 执行成功\n");
	}
}

// 3、打印结果集
void printResultSet(MYSQL_RES *res)
{
    
    

	// 获得列数
	unsigned int num_fields = mysql_num_fields(res);

	//获得表头:各列的名字
	MYSQL_FIELD *fields = mysql_fetch_field(res);

	//输出表头:各列的名字
	unsigned int i = 0;
	for (i = 0; i < num_fields; ++i)
	{
    
    
		printf("%s\t", fields[i].name);
	}
	printf("\n-----------------------------------------------------------------\n");

	//输出表头下面的:各行数据
	MYSQL_ROW row;
	while ((row = mysql_fetch_row(res))) // != NULL
	{
    
    

		for (i = 0; i < num_fields; ++i)
		{
    
    

			printf("%s\t", row[i]);
		}
		printf("\n");
	}
}

// 2、获取用户输入并执行sql
// 调用 3、打印结果集
void getUserInputAndExecute(MYSQL *mysql, char sql[], unsigned int NUM)
{
    
    
	while (1)
	{
    
    
		memset(sql, 0, NUM);

		// 2.1打印提示符
		write(STDOUT_FILENO, "MySQL> ", 7);

		// 2.2 获得用户输入
		int ret = read(STDIN_FILENO, sql, NUM);
		// read读取失败
		if (ret < 0)
		{
    
    
			perror("read");
			break;
		}
		// 按了ctrl+d ,接受了EOF,read就会返回0
		else if (ret == 0)
		{
    
    
			printf("Byebye!\n");
			break;
		}

		// 如果输入的是quit 或者 exit 直接退出
		if ((strcmp(sql, "quit\n") == 0) || (strcmp(sql, "exit\n") == 0))
		{
    
    
			printf("Byebye!\n");
			break;
		}

		// 2.3 执行sql语句,获得结果集
		if (mysql_query(mysql, sql) == 0)
		{
    
    
			//printf("Execute mysql_query() succeed\n");
			MYSQL_RES *res = mysql_store_result(mysql);
			if (res != NULL)
			{
    
    
				// 3、打印结果集
				printResultSet(res);

				//结果集中有多少行数据
				printf("%lld rows in set \n", mysql_affected_rows(mysql));

				// 释放结果集
				mysql_free_result(res);
			}
			else
			{
    
    
				//有多少行数据被affected
				printf("%lld row affected \n", mysql_affected_rows(mysql));
			}
		}
		else
		{
    
    
			//执行失败
			fprintf(stderr, "Error in mysql_query:%s\n", mysql_error(mysql));
		}
	}
}

int main()
{
    
    
	MYSQL mysql;
	char sql[1024] = {
    
    0};

	// 1、初始化 连接mysql 设置当前连接的字符集
	memset(&mysql, 0, sizeof(mysql));
	mysql_init_connect_setCharacter(&mysql);

	// 2、获取用户输入并执行sql
	// 调用 3、打印结果集
	getUserInputAndExecute(&mysql, sql, sizeof(sql));

	//4、垃圾回收
	mysql_close(&mysql);
	return 0;
}
[root@lwh test]# ./execute 
mysql_real_connect() -- 执行成功
mysql_set_character_set() utf8 -- 执行成功
MySQL> select * from employee;
id      name    sex     birthday        salary  entry_date      resume
-----------------------------------------------------------------
1       张三    1       1983-04-27      17000   2012-06-24      一个大牛
2       李四    1       1984-02-22      12000   2012-07-24      一个中牛
3       王五    0       1985-08-28      10000   2012-08-24      也是一个中牛
MySQL> 

改进版代码如下:使得输出格式与MySQL标准格式一致(等宽、+、- )

3.14mysql C-API-Code-Demo1改进版

#include <mysql.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>

#define _HOST_ "127.0.0.1"
#define _PORT_ 3306
#define _USER_ "root"
#define _PASSWD_ "root123"
#define _DB_ ""

#define MAX(a, b) ((a) > (b) ? (a) : (b))
MYSQL mysql;

//1 每次等待用户输入
//2 将用户输入进行查询,并将结果显示出来
//3 如果用户输入的是exit或者quit,就退出

void displayResultSet(MYSQL_RES *result)
{
    
    
	//1 计算每一列的最大宽度
	//	该列每行数据(包括表头)长度作比较,取最大值
	//	这时候就要使用一个数组记录最大长度,同时因为要遍历每一行,所以
	//	也选用哦一个额外的数组来保存每一行每一列的数据免得重复遍历
	//2 输出表格,列与列之间用|分隔,每一格数据补空格使其和最长值相等
	//3 竖线与横线交叉的位置用+表示
	//格式样例
	/*
	+------+----------+--------+
	|deptno|dname     |loc     |
	+------+----------+--------+
	|10    |ACCOUNTING|NEW YORK|
	|20    |RESEARCH  |DALLAS  |
	|30    |SALES     |CHICAGO |
	|40    |OPERATIONS|BOSTON  |
	+------+----------+--------+
	*/

	assert(result);

	//列坐标
	int colIndex = 0;
	//行坐标
	int rowIndex = 0;
	//获取列数
	const unsigned int colCount = mysql_field_count(&mysql);
	//获取行数
	const unsigned int rowCount = mysql_num_rows(result);

	int i;

	//计算每列的最大长度,并用动态数组记录好
	int *colMaxLength = (int *)malloc(sizeof(int) * colCount);
	//用一个动态数组记录结果集的每一个字符串
	char **resultStrArray = (char **)malloc(sizeof(char *) * colCount * rowCount);
	assert(colMaxLength);
	assert(resultStrArray);
	memset(colMaxLength, 0, sizeof(int) * colCount);
	memset(resultStrArray, 0, sizeof(char *) * colCount * rowCount);

	//遍历结果集每一行每一列,记录每个字符串,同时记住每列最长长度
	MYSQL_ROW row = NULL;
	for (rowIndex = 0; (row = mysql_fetch_row(result)); ++rowIndex)
	{
    
    
		unsigned long *lengths = mysql_fetch_lengths(result);
		for (colIndex = 0; colIndex < colCount; ++colIndex)
		{
    
    
			colMaxLength[colIndex] = MAX(colMaxLength[colIndex], lengths[colIndex]);
			resultStrArray[rowIndex * colCount + colIndex] = row[colIndex];
		}
	}

	//获取列信息,用来计算表头每列的长度
	MYSQL_FIELD *fields = mysql_fetch_fields(result);
	for (colIndex = 0; colIndex < colCount; ++colIndex)
	{
    
    
		colMaxLength[colIndex] = MAX(strlen(fields[colIndex].name), colMaxLength[colIndex]);
	}

	//画横线,横线和竖线交叉部分用+号
	for (colIndex = 0; colIndex < colCount; ++colIndex)
	{
    
    
		printf("+");
		for (i = 0; i < colMaxLength[colIndex]; ++i)
		{
    
    
			printf("-");
		}
	}
	printf("+\n");

	//显示表头
	for (colIndex = 0; colIndex < colCount; ++colIndex)
	{
    
    
		printf("|");
		int colLength = colMaxLength[colIndex];
		printf("%s", fields[colIndex].name);
		for (i = 0; i < colLength - strlen(fields[colIndex].name); ++i)
		{
    
    
			printf(" ");
		}
		//printf("%s\t" ,row[i]);
	}
	printf("|\n");

	//划横线
	for (colIndex = 0; colIndex < colCount; ++colIndex)
	{
    
    
		printf("+");
		for (i = 0; i < colMaxLength[colIndex]; ++i)
		{
    
    
			printf("-");
		}
	}
	printf("+\n");

	//显示结果集数据
	for (rowIndex = 0; rowIndex < rowCount; ++rowIndex)
	{
    
    
		for (colIndex = 0; colIndex < colCount; ++colIndex)
		{
    
    
			printf("|");
			int colLength = colMaxLength[colIndex];
			int index = rowIndex * colCount + colIndex;
			const char *str = resultStrArray[index] ? resultStrArray[index] : "";
			int padding = colLength - strlen(str);
			printf("%s", str);
			//每一个格子的数据都要用空格补全,使其长度刚好登录该列最长长度,达到对齐效果
			for (i = 0; i < padding; ++i)
			{
    
    
				printf(" ");
			}
		}
		printf("|\n");
	}

	//画最后一行横线
	for (colIndex = 0; colIndex < colCount; ++colIndex)
	{
    
    
		printf("+");
		for (i = 0; i < colMaxLength[colIndex]; ++i)
		{
    
    
			printf("-");
		}
	}

	printf("+\n");
	//释放动态数组
	free(colMaxLength);
	free(resultStrArray);
}

void displayAffactRows()
{
    
    
	my_ulonglong rowCount = mysql_affected_rows(&mysql);
	printf("%lld rows affected! \n", rowCount);
}

int main()
{
    
    
	//初始化Mysql
	if (!mysql_init(&mysql))
	{
    
    
		//如果返回值是NULL,那么初始化失败
		printf("mysql init error\n");
		exit(1);
	}
	//建立连接
	if (!mysql_real_connect(
			&mysql,
			_HOST_,
			_USER_,
			_PASSWD_,
			_DB_,
			_PORT_,
			NULL,
			0))
	{
    
    
		//如果连接失败,就打印
		printf("Mysql connect error\n");
		exit(1);
	}
	//修改字符集为UTF8
	if (mysql_set_character_set(&mysql, "UTF8"))
	{
    
    
		printf("Mysql set character set error\n");
		exit(1);
	}

	printf("Welcome to MySQL\n");

	char sql[1024]={
    
    0};
	while (1)
	{
    
    
		memset(sql, 0, sizeof(sql));

		//输出提示
		write(STDOUT_FILENO, "MySQL> ", 7);

		//等待用户输入
		int ret = read(STDIN_FILENO, sql, sizeof(sql));

		//判断用户是否要退出
		if (ret == 0 || strcmp(sql, "exit\n") == 0 || strcmp(sql, "quit\n") == 0)
		{
    
    
			//quit the client
			printf("\n");
			break;
		}

		if (mysql_query(&mysql, sql))
		{
    
    
			printf("Query error: %s \n", mysql_error(&mysql));
			continue;
		}

		//Get result set
		MYSQL_RES *res = mysql_store_result(&mysql);
		if (!res)
		{
    
    
			printf("Result is empty\n");
			//no result set , display affact rows
			displayAffactRows();
		}
		else
		{
    
    
			displayResultSet(res);
			mysql_free_result(res);
			displayAffactRows();
		}
	}

	//关闭连接
	mysql_close(&mysql);
	return 0;
}

3.14 额外的API补充

额外的API补充
    my_bool mysql_autocommit(MYSQL *mysql, my_bool mode)
        打开或者关闭 自动提交 ,关闭自动提交就是为了开启事务
        	mode 1 启用
        	mode 0 禁用
        返回值:
        	成功:0
        	失败:非0值
        	
    mysql_commit()
    
    mysql_rollback()

3.15 预处理语句

--防止sql注入

网页:
    用户名:______        itcast' or '1'='1  
    密码:________       

    select xxx
    from user
    where name = 'xxxxx'  and password = 'xxxxx'

    防御措施:
        1、严格判断用户输入的数据,遇到特殊的符号,就要转义处理
        
       2、对数据库的操作,使用预处理API
        预处理语句使用方式:
            1 先创建预处理语句
            2 先扔给服务器去解析这个语句
            3 将该填的数据传递给服务器,让服务器去执行

3.16mysql C-API-Code-Demo3

// prepare
// 使用 MySQL的预处理语句 编写代码
// 对MySQL数据库的操作,使用预处理API

// 预处理:先解析好你的SQL语句,然后下次客户端直接扔每个字段的值过来,就带入进来
// 优点:
//		对于多次使用的SQL语句(比如插入大量数据时),不用频繁的解析SQL语句(节省了时间);
//  	防止SQL注入

//  预处理语句使用方式:
//		1 先创建预处理语句
//      2 先扔给服务器去解析这个语句
//      3 将该填的数据传递给服务器,让服务器去执行

#include <mysql.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define STRING_SIZE 50
//删除数据表
#define DROP_SAMPLE_TABLE "DROP TABLE IF EXISTS test_table"
//创建数据表
#define CREATE_SAMPLE_TABLE "CREATE TABLE test_table(\
													col1 INT,\
	                                                col2 VARCHAR(40),\
													col3 SMALLINT,\
													col4 TIMESTAMP)"
//插入数据的SQL语句,以?表示待填入的数据
#define INSERT_SAMPLE "INSERT INTO test_table(col1,col2,col3) VALUES(?,?,?)"

#define _HOST_ "127.0.0.1"
#define _PORT_ 3306
#define _USER_ "root"
#define _PASSWD_ "root123"
#define _DB_ "lwh_scott"

MYSQL mysql_instance;
int main(int argc, char **argv)
{
    
    
	MYSQL_STMT *stmt;
	MYSQL_BIND bind[3];
	my_ulonglong affected_rows;
	int param_count;
	short small_data;
	int int_data;
	char str_data[STRING_SIZE];
	unsigned long str_length;
	bool is_null;

	//初始化Mysql
	MYSQL *mysql = &mysql_instance;
	if (!mysql_init(mysql))
	{
    
    
		//如果返回值是NULL,那么初始化失败
		printf("mysql init error\n");
		exit(1);
	}

	//建立连接
	if (!mysql_real_connect(
			mysql,
			_HOST_,
			_USER_,
			_PASSWD_,
			_DB_,
			_PORT_,
			NULL,
			0))
	{
    
    
		//如果连接失败,就打印
		printf("Mysql connect error\n");
		exit(1);
	}

	//修改字符集
	if (mysql_set_character_set(mysql, "UTF8"))
	{
    
    
		printf("Mysql set character set error\n");
		exit(1);
	}

	//如果表存在删除表
	if (mysql_query(mysql, DROP_SAMPLE_TABLE))
	{
    
    
		fprintf(stderr, " DROP TABLE failed\n");
		fprintf(stderr, " %s\n", mysql_error(mysql));
		exit(0);
	}

	//重新创建表
	if (mysql_query(mysql, CREATE_SAMPLE_TABLE))
	{
    
    
		fprintf(stderr, " CREATE TABLE failed\n");
		fprintf(stderr, " %s\n", mysql_error(mysql));
		exit(0);
	}

	/* Prepare an INSERT query with 3 parameters */
	/* (the TIMESTAMP column is not named; the server */
	/*  sets it to the current date and time) */
	//初始化一个准备语句
	stmt = mysql_stmt_init(mysql);
	if (!stmt)
	{
    
    
		fprintf(stderr, " mysql_stmt_init(), out of memory\n");
		exit(0);
	}
	if (mysql_stmt_prepare(stmt, INSERT_SAMPLE, strlen(INSERT_SAMPLE)))
	{
    
    
		fprintf(stderr, " mysql_stmt_prepare(), INSERT failed\n");
		fprintf(stderr, " %s\n", mysql_stmt_error(stmt));
		exit(0);
	}

	fprintf(stdout, " prepare, INSERT successful\n");

	/* Get the parameter count from the statement */
	param_count = mysql_stmt_param_count(stmt);
	fprintf(stdout, " total parameters in INSERT: %d\n", param_count);

	if (param_count != 3) /* validate parameter count */
	{
    
    
		fprintf(stderr, " invalid parameter count returned by MySQL\n");
		exit(0);
	}
	/* Bind the data for all 3 parameters */
	memset(bind, 0, sizeof(bind));

	/* INTEGER PARAM */
	/* This is a number type, so there is no need to specify buffer_length */
	bind[0].buffer_type = MYSQL_TYPE_LONG;
	bind[0].buffer = (char *)&int_data;
	bind[0].is_null = 0;
	bind[0].length = 0;

	/* STRING PARAM */
	bind[1].buffer_type = MYSQL_TYPE_STRING;
	bind[1].buffer = (char *)str_data;
	bind[1].buffer_length = STRING_SIZE;
	bind[1].is_null = 0;
	bind[1].length = &str_length;

	/* SMALLINT PARAM */
	bind[2].buffer_type = MYSQL_TYPE_SHORT;
	bind[2].buffer = (char *)&small_data;
	bind[2].is_null = &is_null;
	bind[2].length = 0;

	/* Bind the buffers */
	if (mysql_stmt_bind_param(stmt, bind))
	{
    
    
		fprintf(stderr, " mysql_stmt_bind_param() failed\n");
		fprintf(stderr, " %s\n", mysql_stmt_error(stmt));
		exit(0);
	}

	//第一次插入数据
	/* Specify the data values for the first row */
	int_data = 10;							 /* integer */
	strncpy(str_data, "MySQL", STRING_SIZE); /* string  */
	str_length = strlen(str_data);

	/* INSERT SMALLINT data as NULL */
	is_null = 1;

	/* Execute the INSERT statement - 1*/
	if (mysql_stmt_execute(stmt))
	{
    
    
		fprintf(stderr, " mysql_stmt_execute(), 1 failed\n");
		fprintf(stderr, " %s\n", mysql_stmt_error(stmt));
		exit(0);
	}

	/* Get the total number of affected rows */
	affected_rows = mysql_stmt_affected_rows(stmt);
	fprintf(stdout, " total affected rows(insert 1): %lu\n",
			(unsigned long)affected_rows);

	//判断数据是否添加成功,只需要判断影响行数是不是1
	if (affected_rows != 1) /* validate affected rows */
	{
    
    
		fprintf(stderr, " invalid affected rows by MySQL\n");
		exit(0);
	}

	//第二次插入数据
	/* Specify data values for second row, then re-execute the statement */
	int_data = 1000;
	strncpy(str_data, "The most popular Open Source database", STRING_SIZE);
	str_length = strlen(str_data);
	small_data = 1000; /* smallint */
	is_null = 0;	   /* reset */

	/* Execute the INSERT statement - 2*/
	if (mysql_stmt_execute(stmt))
	{
    
    
		fprintf(stderr, " mysql_stmt_execute, 2 failed\n");
		fprintf(stderr, " %s\n", mysql_stmt_error(stmt));
		exit(0);
	}

	/* Get the total rows affected */
	affected_rows = mysql_stmt_affected_rows(stmt);
	fprintf(stdout, " total affected rows(insert 2): %lu\n",
			(unsigned long)affected_rows);

	if (affected_rows != 1) /* validate affected rows */
	{
    
    
		fprintf(stderr, " invalid affected rows by MySQL\n");
		exit(0);
	}

	/* Close the statement */
	if (mysql_stmt_close(stmt))
	{
    
    
		fprintf(stderr, " failed while closing the statement\n");
		fprintf(stderr, " %s\n", mysql_stmt_error(stmt));
		exit(0);
	}

	//关闭连接
	mysql_close(mysql);
	return 0;
}

猜你喜欢

转载自blog.csdn.net/liangwenhao1108/article/details/106751868