在笔试面试过程中,后端开发的岗位难免会问到数据库方面的知识,理论性的东西当然需要熟知,当然实际操作也不能落下。
在春招的几次面试中,被问到几个关于SQL方面的问题,因为很久没看,复杂一点的SQL语句都不会写了,实在惭愧。
这几天又重新复习了一下,SQL的重难点还是在于查询语句,所以本水文就针对SELECT语句做一下总结(复杂的部分如缓存/分表没深入研究,暂时跳过),同时末尾还有几道常见的面试题。
全部示例已在Ubuntu16.10上Mariadb10.1.22成功运行,原则上与MySQL无差别。
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);ALL (the default):返回查询结果的所有行,无论查询结果中是否有重复项,ALL是默认的修饰符
DISTINCT/DISTINCTROW: 自动过滤重复的行,DISTINCTROW和DISTINCT作用相同
注:ALL和DISTINCT只能修饰一个查询结果
例:查询s表中的所有记录,包括重复项
SELECT ALL * FROM s;查询s表中的所有记录,不包括重复项
SELECT DISTINCT * FROM s;截取查询结果,如截取前N条,格式为 LIMIT 起始位置,长度;
例:查询s表中的前三条记录
SELECT * FROM s LIMIT 0,3;
WHERE可以对查询结果进行过滤,其中:
例:查询id大于2并且成绩大于80的学生记录
SELECT * FROM s WHERE id>2 AND score>80;语法格式:BETWEEN A AND B 取值在A到B之间
例:查成绩在90到100的学生记录
SELECT * FROM s WHERE score BETWEEN 90 AND 100;判断字段是否为空,在SELETE时,不能通过a=’NULL’简单判断该字段为空,而应该使用 IS [NOT] NULL
例:查询s表中student_id不为空的学生记录
SELECT * FROM s WHERE student_id IS NOT NULL;多值判断,语法格式:IN (A,B,C) 取值为A或B或C
例:查询s表中成绩为80或92的学生
SELECT * FROM s WHERE score IN (80,92);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';注:
'%...%' 会进行全表扫描,而不走索引 '%...' 无法直接使用索引,会进行全表扫描 '...%' 不会全表扫描对结果进行分组,条件过滤可以使用WHERE和HAVING,但HAVING是在分组后过滤,WHERE在分组前过滤,两者可以一起使用
例:查询s表中每个学生的记录数(通过student_id划分)
SELECT student_id id,count(*) count FROM s GROUP BY student_id;对查询结果进行排序,可以有多个排序条件,以‘,’分开,其中ASC表示正序(可省略),DESC表示倒序
在集合运算中,只能在运算结果使用 ORDER BY ,中间表不能用 ORDER BY
例:查询s表中所有结果,按student_id正序,成绩倒序排列
SELECT * FROM s ORDER BY student_id ASC,score DESC;将查询结果插入到另一个列变量,也可以导出文件
例:导出文件
SELECT * FROM s INTO OUTFILE '/tmp/s.txt';FOR UPDATE:在当前事务内,锁定查询的结果(行级锁),直到当前事务结束
LOCK IN SHARE MODE:对查询结果加共享锁,即其它事务只允许读而不允许写
作为子查询的语句只能查询一个列,需要注意的是,过多的子查询会导致性能下降,因为会重复执行子查询
例:查询s表中student_id存在于t表的记录(相关子查询)
SELECT * FROM s WHERE student_id in (SELECT student_id FROM t);连接查询使用 ‘ON’ 做各表连接前的条件判断,同时使用 ‘WHERE’ 做生成表的条件判断
[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;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;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;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;NATURAL JOIN
对两张表中字段名和数据类型都相同的字段进行等值连接,并返回符合条件的结果 ,NATURAL JOIN 没有条件判断语句
SELECT * FROM s NATUAL JOIN t;UNION 合并多个查询(并集),同时将重复的项去除(如果是ALL则保留重复)
UNION 要求列数必须相同,如果字段名不同,则显示为第一个查询的字段
MYSQL不支持,使用 WHERE 的 IN 或 EXIST 或 JOIN ON 代替
MYSQL不支持,使用 WHERE 的 NOT IN 或 NOT EXIST 代替
两次查表,第一次分组查询某学生的课程数量,子查询查出该学生大于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);对student_id进行分组,再使用count()计算记录数
SELECT student_id FROM s GROUP BY student_id HAVING COUNT(student_id)>1;两次查表,先查出重复的目标字段,in判断条件,然后再选出所需数据
SELECT student_id FROM s GROUP BY student_id,score HAVING COUNT(*) > 1;该方法参考了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;可以用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 不做为判断的条件2.NOT EXISTS
SELECT s.* FROM s WHERE NOT EXISTS (SELECT * FROM t WHERE s.student_id=t.student_id);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;https://dev.mysql.com/doc/refman/5.7/en/sql-syntax-data-manipulation.html