MySQL笔记SQL经典实例(上)

Posted 可持续化发展

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了MySQL笔记SQL经典实例(上)相关的知识,希望对你有一定的参考价值。

 

小明写这篇文章的目的是为了记录阅读《SQL经典实例》(安东尼-莫利纳罗著,刘春辉译)中的收获。这本书里面有mysql、Oracle、Postgresql的语法实例。但本人目前用的是MySQL,所以,只摘录了MySQL的相关内容。样例SQL都在Navicat+MySQL8中测试过。我的文章只会收录我觉得有用的东西。对于一些特别基础的知识点和百度能解决的工具类知识点,我会略过或简单提一下。由于篇幅太长了,分为上下两部。

 

表的结构和数据

检索记录

1、实际开发中,不推荐select *,为了可读性,请指明select的每一列。(我这里是为了方便才写select * 的)

2、

select * from emp
    -> where deptno=10 or comm is not null or sal <= 2000 and deptno=20;

  • deptno等于10,或
  • comm不空,或
  • deptno等于20,并且,工资不高于2000的员工

select * from emp 
    -> where (deptno=10 or comm is not null or sal <= 2000) and deptno=20;

  • deptno=10,或,comm不空,或,工资小于等于2000
  • 并且,deptno=20

3、select指定特定的列名,可以过滤无关的数据。避免将时间浪费在检索不需要的数据上。

4、创建有意义的列名,别名

select sal as salary, comm as commission

from emp;

5、在where子句中引用别名列

例如,

select sal as salary, comm as commission from emp where salary < 5000;【1】

这样会报错。

select * from

(select sal as salary, comm as commission from emp) x

where salary < 5000;【2】

OK了!将查询包装为一个内嵌视图。where子句会比select子句先执行。所以【1】报错。因为from子句比where子句先执行,所以【2】OK。当表里的某些列命名不规范的时候,可以用这个技巧。

6、串联多列的值

将多列值合并为一列。

select concat(ename, ' works as a ', job) as msg 
from emp 
where deptno=10;

7、在select语句中使用条件逻辑

在select语句中针对查询结果值执行if-else操作。

select ename, sal,
  case when sal <= 2000 then 'underpaid'
       when sal >= 4000 then 'overpaid'
       else 'OK'
  end as status
from emp;

8、限定返回行数

限定查询结果的行数,不关心排序

select * from emp limit 5;

9、随机返回若干行记录

select ename, job from emp 
order by rand() limit 5;

10、将null值转换为实际值

有一些行里面有null,但想在返回结果中将其替换为非null值。

select empno,coalesce(comm, 0) from emp limit 5;

11、查找匹配项

select ename, job from emp
where deptno in (10,20);

select ename, job from emp
where deptno in (10,20) and 
(ename like '%I%' or job like '%ER');

查询结果排序

 

1、以指定顺序返回查询结果

可以不指定用于排序的列名,而用一个数值来代替该列。数值从1开始,从左到右匹配select列表里的列。

select ename, job, sal 
from emp where deptno=10
order by sal desc;
等价于
select ename,job,sal
from emp where deptno=10
order by 3 desc;

2、多字段排序

select empno, deptno, sal, ename, job
from emp
order by deptno asc, sal desc;

3、依据子串排序

想按照一个字符串的特定部分排序查询结果。例如,从emp表中检索员工的名字和职位,并按照职位字段的最后3个字符对查询结果进行排序。

利用数据库中的子串函数,substr截取从指定起始位置开始到字符串结束的所有字符。

select ename, job
from emp
order by substr(job, length(job)-2);

4、排序时对null值的处理

emp表的comm列进行排序,但comm可能为null。想把null的行放在后面。

/*非null的comm升序,null的comm放在最后面*/
select ename, sal, comm
from 
(select ename, sal, comm, 
   case when comm is null then 0 else 1 end as is_null
 from emp) x
order by is_null desc, comm asc;

5、依据条件逻辑动态调整排序项

希望按照某个条件逻辑来排序,例如,如果job=salesman,就按照comm来排序,否则,就按照sal排序。

select ename, sal, job, comm
from emp
order by case when job = 'SALESMAN' then comm else sal end;

select ename, sal, job, comm,
case when job='SALESMAN' then comm else sal end as ordered 
from emp order by 5;

select ename, sal, job, comm from emp;

 

多表查询

1、叠加两个行集

想返回多个表的数据,这些表可以没有相同的键,但它们的列的数据类型必须相同。union all,将多个表的行并入一个结果集中。但此时select列表中的列必须保持数目相同且类型相同。union all会纳入重复项,如果希望过滤重复项,可以用union。大体而言,union等同于针对union all的输出结果再执行一次distinct。除非有必要,否则不要在查询中使用distinct,除非有必要,否则不要用union替代union all。如下:

select distinct deptno
from(
select deptno from emp 
union all
select deptno from dept
);

 

select ename as ename_and_dname, deptno 
from emp
where deptno=10
union all
select '--------',null
from t1
union all
select dname, deptno
from dept;

2、合并相关行

想根据一个共同的列或具有相同值的列做连接查询,并返回多个表中的行。可以用内连接中的相等连接。连接查询是一种把来自两个表的行合并起来的操作。相等连接,其连接条件依赖于某个相等条件(例如,一个表的部门编号和另一个表的部门编号相等)。

select e.ename, d.loc
from emp e, dept d
where e.deptno = d.deptno and e.deptno=10;

理论上,连接操作首先会依据from子句里列出的表生成笛卡尔积(列出所有可能的行组合),如下:

select e.ename, d.loc, e.deptno as emp_deptno, d.deptno as dept_deptno
from emp e, dept d
where e.deptno=10;

然后再通过where子句里e.deptno和d.deptno做连接操作,限定了只有emp,.deptno和dept.deptno相等的行才会返回。

select e.ename, d.loc, e.deptno as emp_deptno, d.deptno as dept_deptno
from emp e, dept d
where e.deptno=d.deptno and e.deptno=10;

另一种写法是inner join。inner join = join。inner是可选项关键字。

select e.ename, d.loc
from emp e inner join dept d
on e.deptno=d.deptno 
where e.deptno=10;

3、查询只存在于一个表中的数据

想从一个表(源表)中找出那些在某个目标表里不存在的值。例如,想找出在dept表中存在但在emp表中不存在的部门编号。

select deptno 
from dept
where deptno not in (select deptno from emp);
使用这种方式时,需要考虑排除结果集的重复项。这里的示例中deptno是dept表的主键,所以可以不加distinct。如果deptno不是dept表的主键,可以使用distinct。
select distinct deptno 
from dept
where deptno not in (select deptno from emp);

使用not in 时要注意null值问题。例如,

create table new_dept(deptno integer);
insert into new_dept values (10);
insert into new_dept values (50);
insert into new_dept values (null);

如果试着用not in子查询来检索存在于dept表但不存在于new_dept表中的deptno,会发现查不到如何值。

select * from dept
where deptno not in (select deptno from new_dept);

原因在于:new_dept表里有null值。in和 not in本质上是 or 运算,由于null值参与or逻辑运算的方式不同,in 和 not in 将会产生不同的结果。

select deptno 
from dept
where deptno in (10,50, null);

select deptno 
from dept
where deptno=10 or deptno=50 or deptno=null;

select deptno
from dept
where deptno not in (10,50,null);

select deptno 
from dept
where not (deptno=10 or deptno=50 or deptno=null);

deptno not in (10,50,null)等价于  not (deptno=10 or deptno=50 or deptno=null)

当deptno是50的情况时,

not (deptno=10 or deptno=50 or deptno=null)

== (false or false or null)

== (false or null)

==null

在SQL中,TRUE or null的运算结果为TRUE,但FALSE or null 的运算结果是null!必须谨记,当使用 in 以及 执行 or 的时候,考虑 null 值问题。

为了避免 not in 和null值带来的问题,需要结合使用 not exists 和关联子查询。关联子查询是指外层查询执行后的结果集会被内层子查询引用。下面是一个免受 null 值影响的替代方案。

select d.deptno 
from dept d
where not exists
(select null from emp e where d.deptno=e.deptno);

SQL过程:

上述查询会遍历并评估dept表的每一行。针对每一行,会有如下操作:

(1)执行子查询并检查当前的部门编号是否存在于emp表。注意关联条件 d.deptno = e.deptno ,它通过部门编号把两个表连接起来。

(2)如果子查询有结果返回给外层查询,那么 exists (...)  的评估结果是TRUE,这样 not exists (...) 就是false,如此外层查询就会舍弃当前行。

(3)如果子查询没有返回如何结果,那么not exists(...)的评估结果就是TRUE,由此外层查询就会返回当前行(因为它是一个不存在于emp表中的部门编号)。

exists子句不在乎返回的是什么,而是在乎有没有结果集返回, 比如:
  select name from student where sex = 'm' and mark exists(select 1 from grade where ...) ,只要
exists引导的子句有结果集返回,那么exists这个条件就算成立了,大家注意返回的字段始终为1,如果改成“select 2 from grade where ...”,那么返回的字段就是2,这个数字没有意义。

not exists 和not in 分别是exists 和 in 的 对立面。

如果查询的两个表大小相当,那么用in和exists差别不大;如果两个表中一个较小一个较大,则子查询表大的用exists,子查询表小的用in;参考链接

exists (sql 返回结果集为真) 
not exists (sql 不返回结果集为真)

in 或 not  in 后面的结果集只能是对应一个字段的结果集,否则报错。

关联子查询和普通子查询的区别在于参考链接【可以看看《SQL基础教程》(第2版)[日] MICK著;孙淼,罗勇译》】如何正确理解SQL关联子查询

1,关联子查询引用了外部查询的列。2,执行顺序不同。对于普通子查询,先执行普通子查询,再执行外层查询;而对于关联子查询,先执行外层查询,然后对所有通过过滤条件的记录执行内层查询。

在关联子查询中,对于外部查询返回的每一行数据,内部查询都要执行一次。另外,在关联子查询中是信息流是双向的,外部查询的每行数据传递一个值给子查询,然后子查询为每一行数据执行一次并返回它的记录。然后,外部查询根据返回的记录做出决策。

关联子查询的用途:

①在细分的组内进行比较

例子:查询各个商品种类中高于该商品种类平均销售价格的商品信息

SELECT * 
FROM t_commodity AS t1
WHERE sell_unit_price > (
       SELECT AVG(sell_unit_price) 
       FROM t_commodity
       WHERE category = t1.category
)
 

②和EXISTS或NOT EXISTS配合使用,查询存在或不存在的记录

例子:查询没有下过订单的所有顾客的信息

SELECT customer_id
FROM customers AS c
WHERE NOT EXISTS (
      SELECT customer_id
      FROM orders
      WHERE customer_id = c.customer_id
)

 

5、从一个表检索与另一个表不相关的行

问题:两个表有相同的列,想在一个表里查找与另一个表不匹配的行。例如,想找出哪些部门没有员工。

如果想找到每一个员工的就职部门,需要基于emp表和dept表的deptno列进行inner join 。

select d.*
from dept d left join emp e 
on d.deptno=e.deptno
where e.deptno is null;

这里使用了外连接,并且只保留了不匹配的行。这种操作有时候被称为反连接。为了更好地理解反连接,我们先来看看没有过滤null的结果集。

select e.ename, e.deptno as emp_deptno, d.*
from dept d left join emp e
on d.deptno = e.deptno;

6、新增连接查询而不影响其他的连接查询

已有查询

select e.ename, d.loc
from emp e, dept d
where e.deptno=d.deptno;

想将奖金信息合并到一起。使用外连接既能得到额外信息,又不会丢失原有的信息。外连接会返回一个表中的所有行,以及另一个表中与之匹配的行。

select e.ename, d.loc, eb.received
from emp e join dept d 
on e.deptno=d.deptno
left join emp_bonus eb
on e.empno=eb.empno
order by 2;

7、确定两个表是否有相同的数据

想确定V视图中是否和emp表有完全相同的数据?如果有不同的记录,请找出不同的数据,以及重复的数据。

原理:

(1)先找出存在于emp表而不存在于视图v的行

(2)然后与存在于视图v而不存在于emp表的行合并 (union all)

如果两个表完全相同,则不会返回任何数据。如果两个表有不同之处,将返回哪些不同的行。比较两个表是否完全相同,我们在比较数据之前,可以单独比较一下行数。

select count(*) from emp

union 

select count(*) from dept;

select * from (
select e.empno, e.ename,e.job, e.mgr,e.hiredate,e.sal,e.comm, e.deptno,count(*) as cnt
from emp e
group by e.empno, e.ename,e.job, e.mgr,e.hiredate,e.sal,e.comm, e.deptno
) e
where not exists(
  select null from
(
select v.empno, v.ename,v.job, v.mgr,v.hiredate,v.sal,v.comm, v.deptno,count(*) as cnt
from v 
group by v.empno, v.ename,v.job, v.mgr,v.hiredate,v.sal,v.comm, v.deptno
)v
where v.empno = e.empno
and v.ename  = e.ename
and v.job= e.job
and v.mgr= e.mgr
and v.hiredate= e.hiredate
and v.sal= e.sal
and v.deptno= e.deptno
and v.cnt= e.cnt
and coalesce(v.comm,0)=coalesce(e.comm,0)
)
  union all
select * from (
select v.empno, v.ename,v.job, v.mgr,v.hiredate,v.sal,v.comm, v.deptno,count(*) as cnt
from v
group by empno, ename,job, mgr,hiredate,sal,comm, deptno
) v
where not exists(
  select null from
(
select e.empno, e.ename,e.job, e.mgr,e.hiredate,e.sal,e.comm, e.deptno,count(*) as cnt
from emp e 
group by empno, ename,job, mgr,hiredate,sal,comm, deptno
)e
where v.empno = e.empno
and v.ename  = e.ename
and v.job= e.job
and v.mgr= e.mgr
and v.hiredate= e.hiredate
and v.sal= e.sal
and v.deptno= e.deptno
and v.cnt= e.cnt
and coalesce(v.comm,0)=coalesce(e.comm,0)
);

8、识别并消除笛卡尔积

想找出部门编号为10的所有员工的名字及其部门所在的城市。

错误的SQL是这样的:

select e.ename, d.loc
from emp e, dept d
where e.deptno = 10;

应该这么写:

select e.ename, d.loc
from emp e, dept d
where e.deptno=10 and d.deptno=e.deptno;

9、组合使用连接查询与聚合函数

想执行一个聚合操作,但查询语句涉及多个表。希望确保表之间的连接查询不会干扰聚合操作。例如,希望计算部门编号为10的员工的工资总额以及奖金总和。因为有部门员工多次获得奖金,所以在emp表和emp_bonus表连接之后再执行聚合函数sum,就会出错。

错误示例,奖金总额(total_bonus)是正确的,但工资总额(total_sal)是错误的,错误原因是连接查询导致某些行的 sal 列出现了两次:

select deptno, sum(sal) as total_sal, sum(bonus) as total_bonus
from (
select e.empno, e.ename, e.sal, e.deptno, 
e.sal*case when eb.type = 1 then .1
           when eb.type = 2 then .2
           else .3
      end as bonus
from emp e, emp_bonus eb
where e.empno = eb.empno 
and e.deptno = 10
) x
group by deptno;

错误原因:MILLER的工资被加了两次。

select *
from emp e, emp_bonus eb
where e.empno = eb.empno
and e.deptno = 10;

解决方案:

在连接查询里进行聚合运算时,必须十分小心。如果连接查询产生了重复行,通常有两种方法来使用聚合函数,避免计算结果出错。

①调用聚合函数时直接使用 distinct ,这样每个值都会先去掉重复项再参加计算。

②在进行连接查询之前,先执行聚合运算(以内嵌视图的方式),这样可以避免错误的结果。因为聚合运算发生在连接查询之前。

distinct的方式,解决办法:只计算不同的emp.sal。

select deptno, sum(distinct sal) as total_sal, sum(bonus) as total_bonus
from(
select e.empno, e.ename, e.sal, e.deptno, 
e.sal*case when eb.type = 1 then .1
           when eb.type = 2 then .2
           else .3
      end as bonus
from emp e, emp_bonus eb
where e.empno = eb.empno
and e.deptno = 10
) x
group by deptno;

另一种解决方法:先计算部门编号为10的全部员工的工资总额,然后连接emp表和emp_bonus表。

select d.deptno, d.total_sal, 
sum(e.sal*case when eb.type = 1 then .1
               when eb.type = 2 then .2
               else .3 end) as total_bonus
from emp e, emp_bonus eb,(
select deptno, sum(sal) as total_sal
from emp
where deptno = 10
group by deptno
) d
where e.deptno = d.deptno
and e.empno = eb.empno
group by d.deptno, d.total_sal;

10、组合使用外连接查询和聚合函数

部门编号为10的员工中只有部分人获得了奖金。想计算部门编号为10的员工的工资总额和奖金总额。

错误示例:

select deptno, sum(sal) as total_sal, sum(bonus) as total_bonus
from(
select e.empno, e.ename, e.sal, e.deptno, 
e.sal*case when eb.type = 1 then .1
           when eb.type = 2 then .2
           else .3 end as bonus
from emp e, emp_bonus eb
where e.empno = eb.empno
and e.deptno = 10
) x
group by deptno;

错误原因,奖金总额对了,但工资总额错误。因为没有计算部门编号为10 的所有员工的工资总额,实际上只有MILLER的工资被加入总和,而且被错误计算了两次:

select e.empno, e.ename, e.sal, e.deptno, 
e.sal*case when eb.type = 1 then .1
           when eb.type = 2 then .2
           else .3 end as bonus
from emp e, emp_bonus eb
where e.empno = eb.empno
and e.deptno = 10

解决方法:

本示例“问题部分”第二个查询语句连接了emp和emp_bonus表,却只返回了员工MILLER的两行数据,这导致emp表的工资总额计算出错(部门编号为10的其他员工没有奖金,他们的工资没有被计入总和)。解决方法:把emp表外连接到emp_bonus表,这样哪些没有奖金的员工也会被计算进来。

①外连接emp_bonus表,然后去掉部门编号为10的员工的重复项,再计算工资总和。

select deptno, sum(distinct sal) as total_sal, sum(bonus) as total_bonus
from(
select e.empno, e.ename, e.sal, e.deptno, 
e.sal*case when eb.type is null then 0
           when eb.type = 1 then .1
           when eb.type = 2 then .2
           else .3 end as bonus
from emp e left join emp_bonus eb
on e.empno = eb.empno
where e.deptno = 10
) x
group by deptno;

②先计算部门编号为10的员工的工资总额,然后再连接emp表和emp_bonus 表(这样就避免了使用外连接)。

select d.deptno, d.total_sal, 
sum(e.sal*case when eb.type = 1 then .1
               when eb.type = 2 then .2
               else .3 end) as total_bonus
from emp e, emp_bonus eb,(
select deptno, sum(sal) as total_sal
from emp
where deptno = 10
group by deptno
) d
where e.deptno = d.deptno
and e.empno = eb.empno
group by d.deptno, d.total_sal;

11、从多个表中返回缺失值

找到存在于dept表而不存在于emp表的数据(即没有员工的部门)需要使用外连接。

select d.deptno, d.dname, e.ename
from dept d left outer join emp e
on d.deptno = e.deptno;

插入一个新的员工

insert into emp (empno, ename, job, mgr, hiredate, sal, comm, deptno)
select 1111, 'YODA', 'JEDI', null, hiredate, sal, comm, null
from emp
where ename = 'KING';

select d.deptno, d.dname, e.ename
from dept d right outer join emp e
on d.deptno = e.deptno;

但我想要的是在同一个查询语句中,既外连接到emp表又外连接到dept表。

解决方法:使用全外连接(full outer join),基于一个共同值从两个表中返回缺失值。全外连接查询的本质是合并两个表的外连接查询的结果集。

select d.deptno, d.dname, e.ename
from dept d full outer join emp e
on d.deptno = e.deptno;
但是MySQL居然不支持全连接。

select d.deptno, d.dname, e.ename
from dept d right outer join emp e
on d.deptno = e.deptno
union
select d.deptno, d.dname, e.ename
from dept d left outer join emp e
on d.deptno = e.deptno;

12、在运算和比较中使用null

null不会等于或不等于任何值,甚至不能与自身进行比较。但想找出emp表中comm列比员工WARD低的所有员工。检索结果应该包含comm列为null的员工。

select ename, comm
from emp
where coalesce(comm, 0) < (
select comm from emp where ename = 'WARD'
);

select ename, comm, coalesce(comm,0)
from emp
where coalesce(comm, 0) < (
select comm from emp where ename = 'WARD'
);

 

插入、更新、删除

1、插入

插入默认值

create table D (id integer default 0, foo varchar(10));

insert into D (foo) values ('Bar');

使用null覆盖默认值

create table D (id integer default 0, foo varchar(10));

insert into D (id, foo) values (null, 'Brighten');

2、复制数据到另一个表

例如,想把dept表的部分数据复制到dept_east表中,现在dept_east表已经建好了,结构和dept表相同(列名,数据类型),现在该表中没有数据。

insert into dept_east (deptno, dname, loc)
select deptno, dname, loc
from dept
where loc in ('NEW YORK', 'BOSTON');

3、复制表定义

想创建一个新表,该表和当前已存在的表保持相同的结构定义。例如,为dept表创建一个副本,dept_2,但我只想复制它的表结构,而不复制数据。

create table dept_2
as
select * from dept
where 1=0;

4、禁止插入特定的列

想阻止用户或软件应用程序在某些列中插入数据。例如,一个程序将数据插入emp表中,但只允许它插入empno、ename、job列。

解决方法:创建一个视图,只暴露那些你希望暴露的列。然后,强制所有insert语句都来操作这个视图。

5、更新记录

想更新一个表的部分记录或全部记录。

update emp
set sal = sal*1.10
where deptno = 20;

6、当相关行存在时更新记录

想更新一个表的部分行,但更新条件取决于另一个表中是否有与之相关的行。例如,如果一个员工出现在emp_bonus表中,你希望把他的工资(在emp表中)上涨20%。

解决方法:

update emp
set sal = sal*1.20
where empno in (select empno from emp_bonus);
或者
update emp
set sal = sal*1.20
where exists (
select null from emp_bonus
where emp.empno = emp_bonus.empno
);

7、使用另一个表的数据更新记录

想使用另一个表的值来更新当前的表。new_sal表存储了部分员工调整后的工资。

update emp e 
set (e.sal, e.comm) = (
select ns.sal, ns.sal/2
from new_sal ns
where ns.deptno = e.deptno
)
where exists (
select null from new_sal ns
where ns.deptno = e.deptno
);

8、删除全表记录

delete from emp;

9、删除违反参照完整性的记录

想从表中删除一些记录,因为在另一个表里不存在与这些记录相匹配的数据。例如,一些员工所属的部门其实并不存在。我希望删除这些员工。

delete from emp
where not exists(
select * from dept 
where dept.deptno = emp.deptno
);

或者

delete from emp
where deptno not in (select deptno from dept);

10、删除重复记录

name列有重复值,想让name列的值不重复。
delete from dupes
where id not in (
select min(id) from dupes group by name
);

11、删除被其他表参照的记录

delete from emp
where deptno in (
select deptno from dept_accidents
group by deptno
having count(*) >= 3
);

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

以上是关于MySQL笔记SQL经典实例(上)的主要内容,如果未能解决你的问题,请参考以下文章

MYSQL经典查询语句-笔记

MYSQL经典查询语句-笔记

C语言进阶刷题笔记strlen和sizeof经典笔试题

C语言进阶刷题笔记strlen和sizeof经典笔试题

C语言进阶刷题笔记strlen和sizeof经典笔试题

java图形用户界面编程实例,经典好文