0.引

在笔试面试过程中,后端开发的岗位难免会问到数据库方面的知识,理论性的东西当然需要熟知,当然实际操作也不能落下。

在春招的几次面试中,被问到几个关于SQL方面的问题,因为很久没看,复杂一点的SQL语句都不会写了,实在惭愧。

这几天又重新复习了一下,SQL的重难点还是在于查询语句,所以本水文就针对SELECT语句做一下总结(复杂的部分如缓存/分表没深入研究,暂时跳过),同时末尾还有几道常见的面试题。

全部示例已在Ubuntu16.10上Mariadb10.1.22成功运行,原则上与MySQL无差别。

1.基本查询

SELECT语句根据官网的 Reference Manual,语法格式如下:

SELECT
    [ALL | DISTINCT | DISTINCTROW ]
      [HIGH_PRIORITY]
      [STRAIGHT_JOIN]
      [SQL_SMALL_RESULT] [SQL_BIG_RESULT] [SQL_BUFFER_RESULT]
      [SQL_CACHE | SQL_NO_CACHE] [SQL_CALC_FOUND_ROWS]
    select_expr [, select_expr ...]
    [FROM table_references
      [PARTITION partition_list]
    [WHERE where_condition]
    [GROUP BY {col_name | expr | position}
      [ASC | DESC], ... [WITH ROLLUP]]
    [HAVING where_condition]
    [ORDER BY {col_name | expr | position}
      [ASC | DESC], ...]
    [LIMIT {[offset,] row_count | row_count OFFSET offset}]
    [PROCEDURE procedure_name(argument_list)]
    [INTO OUTFILE 'file_name'
        [CHARACTER SET charset_name]
        export_options
      | INTO DUMPFILE 'file_name'
      | INTO var_name [, var_name]]
    [FOR UPDATE | LOCK IN SHARE MODE]]

先创建两个测试表:

create table s(
    id integer primary key,
    student_id integer,
    score integer
);

create table t(
    id integer primary key,
    student_id integer,
    score integer
);

INSERT INTO s(id,student_id,score) values(1,1,80);
INSERT INTO s(id,student_id,score) values(2,1,80);
INSERT INTO s(id,student_id,score) values(3,2,88);
INSERT INTO s(id,student_id,score) values(4,2,90);
INSERT INTO s(id,student_id,score) values(5,3,92);
INSERT INTO s(id,student_id,score) values(6,4,78);
INSERT INTO s(id,score) values(7,78);

INSERT INTO t(id,student_id,score) values(1,2,82);
INSERT INTO t(id,student_id,score) values(2,5,99);
INSERT INTO t(id,student_id,score) values(3,2,88);
1.1.ALL/DISTINCT

ALL (the default):返回查询结果的所有行,无论查询结果中是否有重复项,ALL是默认的修饰符

DISTINCT/DISTINCTROW: 自动过滤重复的行,DISTINCTROW和DISTINCT作用相同

注:ALL和DISTINCT只能修饰一个查询结果

例:查询s表中的所有记录,包括重复项

SELECT  ALL *  FROM s;

mariaDB获取shell mariadb select_sql

查询s表中的所有记录,不包括重复项

SELECT  DISTINCT *  FROM s;

mariaDB获取shell mariadb select_SQL_02

1.2.LIMIT

截取查询结果,如截取前N条,格式为 LIMIT 起始位置,长度;

例:查询s表中的前三条记录

SELECT * FROM s LIMIT 0,3;

mariaDB获取shell mariadb select_mysql_03

1.3. WHERE

WHERE可以对查询结果进行过滤,其中:

1.3.1. OR / AND

例:查询id大于2并且成绩大于80的学生记录

SELECT * FROM s WHERE id>2 AND score>80;

mariaDB获取shell mariadb select_数据库_04

1.3.2. BETWEEN

语法格式:BETWEEN A AND B 取值在A到B之间

例:查成绩在90到100的学生记录

SELECT * FROM s WHERE score BETWEEN 90 AND 100;

mariaDB获取shell mariadb select_sql_05

1.3.3. IS [NOT] NULL

判断字段是否为空,在SELETE时,不能通过a=’NULL’简单判断该字段为空,而应该使用 IS [NOT] NULL

例:查询s表中student_id不为空的学生记录

SELECT * FROM s WHERE student_id IS NOT NULL;

mariaDB获取shell mariadb select_数据库_06

1.3.4. IN

多值判断,语法格式:IN (A,B,C) 取值为A或B或C

例:查询s表中成绩为80或92的学生

SELECT * FROM s WHERE score IN (80,92);

mariaDB获取shell mariadb select_数据库_07

1.3.5.LIKE/REGEXP

LIKE 表示模糊查询,其条件为:

%  表示匹配0到n个字符(like不会匹配null)

_  匹配1个字符

REGEXP ‘正则表达式’

SELECT * FROM s WHERE score LIKE '%8';

SELECT * FROM s WHERE score LIKE '8_';

SELECT * FROM s WHERE score REGEXP '^[789]8';

mariaDB获取shell mariadb select_SQL_08

注:

'%...%' 会进行全表扫描,而不走索引

'%...' 无法直接使用索引,会进行全表扫描

'...%' 不会全表扫描
1.4.GROUP BY

对结果进行分组,条件过滤可以使用WHERE和HAVING,但HAVING是在分组后过滤,WHERE在分组前过滤,两者可以一起使用

例:查询s表中每个学生的记录数(通过student_id划分)

SELECT student_id id,count(*) count FROM s GROUP BY student_id;

mariaDB获取shell mariadb select_sql_09

1.5.ORDER BY

对查询结果进行排序,可以有多个排序条件,以‘,’分开,其中ASC表示正序(可省略),DESC表示倒序

在集合运算中,只能在运算结果使用 ORDER BY ,中间表不能用 ORDER BY

例:查询s表中所有结果,按student_id正序,成绩倒序排列

SELECT * FROM s ORDER BY student_id ASC,score DESC;

mariaDB获取shell mariadb select_SQL_10

1.6.INTO

将查询结果插入到另一个列变量,也可以导出文件

例:导出文件

SELECT * FROM s INTO OUTFILE '/tmp/s.txt';

mariaDB获取shell mariadb select_mysql_11

1.7.FOR UPDATE | LOCK IN SHARE MODE

FOR UPDATE:在当前事务内,锁定查询的结果(行级锁),直到当前事务结束

LOCK IN SHARE MODE:对查询结果加共享锁,即其它事务只允许读而不允许写

2.子查询(嵌套查询)

作为子查询的语句只能查询一个列,需要注意的是,过多的子查询会导致性能下降,因为会重复执行子查询

例:查询s表中student_id存在于t表的记录(相关子查询)

SELECT * FROM s WHERE student_id in (SELECT student_id FROM t);

mariaDB获取shell mariadb select_mariaDB获取shell_12

3.连接查询

连接查询使用 ‘ON’ 做各表连接前的条件判断,同时使用 ‘WHERE’ 做生成表的条件判断

3.1.内连接

[INNER | CROSS] JOIN

使用’,’或 INNER JOIN 或 CROSS JOIN 连接多个表,如果不加条件,则产生笛卡尔积

例:查询s表与t表student_id相同的记录

SELECT s.* FROM s INNER JOIN t ON s.student_id = t.student_id;

mariaDB获取shell mariadb select_mariaDB获取shell_13

3.2.左外连接

LEFT [OUTER] JOIN

包含的左表的所有行,如果左表的某行在右表中没有匹配行,则在相关联的结果集行中右表的所有选择列表列均为NULL。

例:查询s表与t表student_id相同的记录,如t表无数据,则t所有数据列为空

SELECT * FROM s LEFT OUTER JOIN t ON s.student_id=t.student_id ORDER BY s.id;

mariaDB获取shell mariadb select_mysql_14

3.3.右外连接

RIGHT [OUTER] JOIN

右向外连接是左向外联接的反向联接。将返回右表的所有行。如果右表的某行在左表中没有匹配行,则将为左表返回空值。

例:查询s表与t表student_id相同的记录,如t表无数据,则t所有数据列为空

SELECT * FROM s RIGHT OUTER JOIN t ON s.student_id=t.student_id ORDER BY s.id;

mariaDB获取shell mariadb select_mysql_15

3.4.完整外连接

FULL JOIN 或 FULL OUTER JOIN

完整外连接返回左表和右表中的所有行。当某行在另一个表中没有匹配行时,则另一个表的选择列表列包含空值。

MySQL不支持FULL JOIN,可以使用UNION 拼接 LEFT [OUTER] JOIN 和 RIGHT [OUTER] JOIN

SELECT * FROM s LEFT OUTER JOIN t ON s.student_id=t.student_id
UNION
SELECT * FROM s RIGHT OUTER JOIN t ON s.student_id=t.student_id;

mariaDB获取shell mariadb select_mysql_16

3.5.自然连接

NATURAL JOIN

对两张表中字段名和数据类型都相同的字段进行等值连接,并返回符合条件的结果 ,NATURAL JOIN 没有条件判断语句

SELECT * FROM s NATUAL JOIN t;

mariaDB获取shell mariadb select_数据库_17

4.集合操作

4.1.并集 UNION [ALL]

UNION 合并多个查询(并集),同时将重复的项去除(如果是ALL则保留重复)

UNION 要求列数必须相同,如果字段名不同,则显示为第一个查询的字段

4.2.交集 INTERSECT

MYSQL不支持,使用 WHERE 的 IN 或 EXIST 或 JOIN ON 代替

4.3.差集 EXCEPT

MYSQL不支持,使用 WHERE 的 NOT IN 或 NOT EXIST 代替

5.一些例子

5.1.查出s表中所有分数都大于80的学生id

两次查表,第一次分组查询某学生的课程数量,子查询查出该学生大于80分的课程数量,两者比较,如果相等,则该学生符合条件

SELECT a.student_id FROM s a GROUP BY student_id HAVING COUNT(*) = (SELECT COUNT(*) FROM s b WHERE b.score>80 AND b.student_id=a.student_id);

mariaDB获取shell mariadb select_数据库_18

5.2.查询s表中student_id重复的数据(单条数据重复)

对student_id进行分组,再使用count()计算记录数

SELECT student_id FROM s GROUP BY student_id HAVING COUNT(student_id)>1;

mariaDB获取shell mariadb select_mysql_19

5.3.查询s表中student_id和score重复的数据(多条数据重复)

两次查表,先查出重复的目标字段,in判断条件,然后再选出所需数据

SELECT student_id FROM s GROUP BY student_id,score HAVING COUNT(*) > 1;

mariaDB获取shell mariadb select_数据库_20

5.4.删除重复数据,保留id最大那一项

该方法参考了stackoverflow一位大神的写法,链接忘记了,也是两次查表

delete dupes FROM s dupes,s full WHERE dupes.student_id=full.student_id AND dupes.score=full.score AND dupes.id < full.id;

mariaDB获取shell mariadb select_数据库_21

5.5.查询A表中有而B表中没有的数据(如果是SQL Server可以使用 EXCEPT)

可以用NOT IN或NOT EXISTS或LEFT JOIN实现

例:查询student_id存在于s而不存在于t的数据

1.NOT IN

SELECT s.* FROM s WHERE student_id NOT IN(SELECT student_id FROM t); # NULL 不做为判断的条件

mariaDB获取shell mariadb select_mysql_22

2.NOT EXISTS

SELECT s.* FROM s WHERE NOT EXISTS (SELECT * FROM t WHERE s.student_id=t.student_id);

mariaDB获取shell mariadb select_mysql_23

3.LEFT JOIN

SELECT s.* FROM s LEFT JOIN t ON s.student_id=t.student_id WHERE t.id IS NULL AND  t.student_id IS NULL AND  t.score IS NULL;

mariaDB获取shell mariadb select_mysql_24