记一道颠覆认知的SQL面试题
目录
今天遇到了一道mysql的题目,看上去非常简单然而一次次的颠覆了我的认知,就像这个世界一样远远没有想象的那么简单,故做个笔记纪念一下。
题目要求
- 自定义商品表,有name,time,product3个字段,分别表示用户名,时间和商品名称;
- 插入指定的数据;
- 找出每个用户第一次购买的商品
建表
按照mysql基本的语法创建goods表即可,注意字段最好不要用NULL,每个字段最好写上注释信息,示例如下:
CREATE TABLE IF NOT EXISTS goods(
name varchar(20) not null DEFAULT '' comment '用户名',
time date not null DEFAULT '0000-00-00' comment '购买时间',
product varchar(20) not null DEFAULT '' comment '商品名称'
)ENGINE=InnoDB DEFAULT CHARSET=utf8 comment '商品表';
插入数据
INSERT INTO goods values('张三','2016-01-02','MDES');
INSERT INTO goods values('李四','2018-11-22','ASX');
INSERT INTO goods values('李四','2017-07-13','BGM');
INSERT INTO goods values('张三','2016-01-01','XSS');
INSERT INTO goods values('王五','2019-11-11','NFJ');
INSERT INTO goods values('王五','2019-11-10','DBCK');
INSERT INTO goods values('张三','2019-12-12','DHS');
错误示范
最本能的想法就是按姓名分组,在每一组中找到time字段的最小值,然后select显示出的name字段和product的字段不就可以了吗?我们试试看:
mysql> select name, min(time) ,product from goods group by name;
并不能得到想要的结果,因为group by分组后取的是组内第一条记录,也就是每个name第一次插入到表中时写入的product,然而这并不是我们想要的。
正确解法
既然一张表搞不定那就尝试用2张表做关联,过程如下:
- 先按name字段分组,找到每个用户第一次购买的时间,得到一张临时表,记做A;
- 然后再选择goods表中所有的数据得到一张临时表,记做B;
- 将表A和B通过time字段进行关联即可。
实现以上过程,可以有一下几种方式:
方法一:子查询
SELECT * FROM goods WHERE time in (SELECT min(time) FROM goods GROUP BY name);
方法二:内连接
SELECT g.name,g.time,g.product FROM goods as g
INNER JOIN (
SELECT MIN(time) as t from goods GROUP BY name)
as tmp
ON g.time = tmp.t;
方法三:右连接
SELECT g.name,g.time,g.product FROM goods as g
RIGHT JOIN (
SELECT MIN(time) as t from goods GROUP BY name)
as tmp
ON g.time = tmp.t ;
方法四:左连接(一定出乎你意料的)
一般我们经常说左连接和右连接是可以转换的,我们就直接转换,将RIGHT JOIN改为LEFT JOIN ,执行结果如下:
不知道你有没有诧异,反正我是挺吃惊,竟然报语法错!!!
原因就是:当group by用在子查询中的时候,要加上括号,而且前面需要再加一层select明确groupby是个子查询,否则有语法歧义,就会提示语法错误。
正确写法:
SELECT tmp.name, g.product from
(SELECT name,MIN(time) as t from goods GROUP BY name) as tmp
LEFT JOIN
( SELECT time as t,product FROM goods )as g
ON tmp.t= g.t ;
搞定~
总结
解决问题的方法有很多,如何把我们所学的知识融汇贯通,如何把这些知识真正的理解透彻,还是需要多多练习,不断的试错,在错误中成长。