首页 > 技术文章 > nowcoder-oj【SQL篇】

yppah 2021-09-27 10:35 原文

(99+条未读通知) 牛客题霸_经典高频编程题库_面试备战无压力_牛客网 (nowcoder.com)

(99+条未读通知) yub4by的个人主页_牛客网 (nowcoder.com)


 

1、牛客OJ-SQL篇-非技术快速入门

# 方式1:使用distinct关键字去重
select distinct university from user_profile 

# 方式2:借助分组实现去重
SELECT university FROM user_profile GROUP BY university

 

 

SELECT device_id FROM user_profile WHERE id IN(1,2);

SELECT device_id from user_profile where id='1' or id='2';

SELECT device_id from user_profile LIMIT 0,2;

SELECT device_id from user_profile LIMIT 2;

SELECT device_id user_infors_example from user_profile LIMIT 2;

SELECT device_id as user_infors_example from user_profile LIMIT 0,2;
# as可以省略,0可以省略,as关键字用于起别名
# 分页查询,limit 2等同于limit 0,2 表示从第0条记录后开始选取,限制选取2条(即第一第二条记录)

SELECT device_id,gender,age FROM user_profile WHERE age BETWEEN 20 and 23;

SELECT device_id,gender,age FROM user_profile WHERE age>=20 and age<=23;

SELECT device_id,gender,age FROM user_profile WHERE age IN(20,21,22,23);

 

 

 

 

select device_id,gender,age,university FROM user_profile WHERE university!="复旦大学";

select device_id,gender,age,university FROM user_profile WHERE university not in("复旦大学");

# 延伸:子查询(实际此题没必要,直接用上述两个方法就行,在此只是讲一下子查询简单用法)
select device_id,gender,age,university 
FROM user_profile 
WHERE university not in(select university 
                        from user_profile 
                        WHERE university="复旦大学");

 

 

SELECT device_id,gender,age,university FROM user_profile WHERE age is not null;

SELECT device_id,gender,age,university FROM user_profile WHERE age!="";

 

 

SELECT device_id,gender,age,university,gpa 
FROM user_profile 
WHERE gender='male' and gpa>3.5;


SELECT device_id,gender,age,university,gpa 
FROM user_profile 
WHERE gender in('male') and gpa>3.5;

SELECT device_id,gender,age,university,gpa 
FROM user_profile 
WHERE university in('北京大学','复旦大学','山东大学');


SELECT device_id,gender,age,university,gpa 
FROM user_profile 
WHERE university='北京大学' or university='复旦大学' or university='山东大学';

SELECT device_id,gender,age,university,gpa 
FROM user_profile
WHERE 
    (gpa>3.5 and university='山东大学')
    or
    (gpa>3.8 and university='复旦大学')
;
# 实际上and的优先级大于or,可以省略(),但建议写上,可读性强

SELECT device_id,age,university 
FROM user_profile 
WHERE university LIKE "%北京%";

# 方法一
SELECT MAX(gpa) 
FROM user_profile 
WHERE university='复旦大学';


# 方法2
SELECT gpa 
FROM user_profile 
WHERE university='复旦大学' 
ORDER BY gpa 
LIMIT 1; -- 0,1

 

 

SELECT COUNT(gender) as male_num, ROUND(AVG(gpa),1) as avg_gpa 
FROM user_profile
WHERE gender='male'GROUP BY gender;
# 此题要注意的是暗含条件,平均值保留一位小数
# 使用ROUND()函数,保留一位小数,四舍五入
# 使用COUNT()等聚合函数时必分组GROUP BY
# AVG()求平均值

 

 

 

 

# 审题:请分别计算出每个学校每种性别的用户数、30天内平均活跃天数和平均发帖数量
    # 1.找出分组条件每个学校的每种性别
    # 2.确定要什么
        # 2.1 用户数(id) count()
        # 2.2 30天内平均活跃active_days_within_30 avg()
        # 2.3 平均发帖数量question_cnt avg()


# group by 根据多个条件分组,如本题,University写在gender 之前即可,其他就是count函数和avg函数的用法
SELECT 
    gender,university,
    COUNT(id) as user_num,
    avg(active_days_within_30) as avg_active_day,
    avg(question_cnt) as avg_question
FROM user_profile
GROUP BY university,gender;

 

 

 

 

# 方式1(子查询-复杂方式)
SELECT sp.university,sp.avg_question_cnt,sp.avg_answer_cnt
FROM (
    select university,avg(question_cnt) as avg_question_cnt,avg(answer_cnt) as avg_answer_cnt
    from user_profile
    GROUP BY university) sp
where sp.avg_question_cnt<5 or sp.avg_answer_cnt<20;
# 1.首先用到的是 avg()求平均值
# 2.起一个别名
# 3.做判断
# 3.1用having 比较
# 3.2用子查询在用where



# 方式2(较1,推荐方式2)
SELECT university,avg(question_cnt) as avg_question_cnt,avg(answer_cnt) as avg_answer_cnt
FROM user_profile
GROUP BY university
HAVING avg_question_cnt<5 or avg_answer_cnt<20;
# 按照大学进行分组 group by university; 条件having avg(question_cnt)<5 or avg(answer_cnt)<20
# 本题核心是掌握group by
# group by 代表分组查询(此时可以引用表的别名),having是分组后的筛选。avg()是聚合函数,依赖于group by。
# 执行循序为:from-group by-having-select

 

 

 

 

SELECT university,AVG(question_cnt) as avg_question_cnt
FROM user_profile
GROUP BY university
ORDER BY avg_question_cnt ASC;

# 运营想查看不同大学情况,就用大学分组 group by university
# 查看用户平均发帖情况,avg( question_cnt)
# 升序排列 group by university order by avg_question_cnt asc
# DESC降序  ASC升序

 

 

 

SELECT qpd.device_id,qpd.question_id,qpd.result
FROM question_practice_detail qpd,user_profile
WHERE 
    user_profile.university='浙江大学' 
    and 
    user_profile.device_id=qpd.device_id;


# 参考1(创建一张临时表用,获取浙江大学device_id对用户题目回答明细进行过滤)
SELECT device_id,question_id,result
from question_practice_detail
WHERE device_id=(SELECT device_id 
                 FROM user_profile 
                 WHERE university='浙江大学');


# 参考2(先将两张表关联在一起,然后再筛选出浙江大学的明细数据)
select t1.device_id,t1.question_id,t1.result
from question_practice_detail t1
left JOIN user_profile t2
on t1.device_id = t2.device_id
where university='浙江大学'

 

 

 

 

 

 

SELECT 
    t1.university,
    t3.difficult_level,
#     AVG(t1.answer_cnt) as avg_answer_cnt
    COUNT(t2.question_id)/COUNT(distinct(t2.device_id)) as avg_answer_cnt
FROM 
    user_profile t1,
    question_practice_detail t2,
    question_detail t3
WHERE
    t1.device_id = t2.device_id
    and 
    t2.question_id = t3.question_id
GROUP BY 
    t1.university,t3.difficult_level;
# 参考SQL22题中的用户平均答题题目数求解方法,这道题自己做出来了



# 其他1:三表联,要求求出每个学校,每个难度的平均做题数 group by university,difficult_level,所以以学校,及难度进行分组,用题数/回答数求出,刷题率
select
    u.university,
    q1.difficult_level,
    count(q.question_id)/count(distinct q.device_id)
from 
    user_profile u 
inner join 
    question_practice_detail q 
    on 
    u.device_id=q.device_id
left join 
    question_detail q1 
    on 
    q1.question_id=q.question_id
group by u.university,q1.difficult_level



# 其他2:详细解析见https://blog.nowcoder.net/n/1b1530bcb5694ef382a837bda02d4f54
SELECT
    u.university,
    qd.difficult_level,
    count(q.question_id)/count(distinct(q.device_id)) AS avg_answer_cnt
FROM 
    question_practice_detail AS q
LEFT JOIN 
    user_profile AS u
    ON 
    u.device_id=q.device_id
LEFT JOIN
    question_detail AS qd
    ON 
    q.question_id=qd.question_id
GROUP BY u.university, qd.difficult_level;

 

 

 

 

SELECT 
    t1.university,
    t3.difficult_level,
    COUNT(t2.question_id) / COUNT(DISTINCT(t2.device_id)) as avg_answer_cnt
from 
    user_profile as t1,
    question_practice_detail as t2,
    question_detail as t3
WHERE 
    t1.university = '山东大学'
    and t1.device_id = t2.device_id
    and t2.question_id = t3.question_id
GROUP BY
    t3.difficult_level;

 

 

(SELECT device_id,gender,age,gpa 
FROM user_profile
WHERE university='山东大学')
UNION ALL
(SELECT device_id,gender,age,gpa 
FROM user_profile
WHERE gender="male");
# 结果不去重就用union all,去重就用 union。
# 直接where university='山东大学' or gender="male"的话,也是自动去重的。

# 参考1:if判断
SELECT 
    IF(age<25 OR age IS NULL,'25岁以下','25岁及以上') as age_cut,
    COUNT(device_id) as Number
FROM user_profile
GROUP BY age_cut;


# 参考2:联合查询
SELECT '25岁以下' as age_cut, COUNT(device_id) as Number
FROM user_profile WHERE age<25 OR age is NULL
UNION
SELECT '25岁及以上' as age_cut, COUNT(device_id) as Number
FROM user_profile WHERE age>=25;

 

 

# 参考1
    # 考点 case when then
    # 当年龄在20-24之间,显示20-24岁
    # case when age between 20 and 24 then '20-24岁'
    # 大于等于25,显示25岁以上
    # when age>=25 then '25岁以上'
    # 剩下的数据就是其他
    # else '其他' end
    # 重点:这里主要考察case when 的用法,勿忘end关键字
SELECT 
    device_id,gender,
    (case when age<20 then '20岁以下'
          when age between 20 and 24 then '20-24岁'
          when age>=25 then '25岁以上' 
          else '其他' 
     end) as age_cut
from user_profile;



# 参考2:多重嵌套if
SELECT 
    device_id,gender,
    if(age<20,'20岁以下',
       if(age between 20 and 24,'20-24岁',
          if(age>=25,'25岁以上','其他')
         )
      ) as age_cut
from user_profile;

 

 

# 参考1
SELECT EXTRACT(DAY from date) as day, COUNT(question_id) as question_cnt
FROM question_practice_detail
WHERE EXTRACT(MONTH from date) = 08
GROUP BY day;


# 参考2
SELECT DAY(date) as day, COUNT(question_id) as question_cnt
FROM question_practice_detail
WHERE YEAR(date) = 2021 and MONTH(date) = 08
GROUP BY day;


# 参考3
select right(date,2) as day,count(question_id) as questionc_cnt
from question_practice_detail
where date like'2021-08%'
group by day

 

 

# 此题较难,详解见https://blog.nowcoder.net/n/6c04580282de402fb6d0a43a4cdb0a6d


# 参考1
SELECT 
    COUNT(res2.device_id) / COUNT(res1.device_id) as avg_ret
FROM
    (select DISTINCT device_id,date
    from question_practice_detail) as res1
left join
    (select DISTINCT device_id, DATE_SUB(date,INTERVAL 1 DAY) as date
    from question_practice_detail) as res2
USING(device_id, date); 

# USING必须带括号不然报错;using()这一句可以替换为参考2中的on这一句

# 参考2
SELECT 
    COUNT(res2.device_id) / COUNT(res1.device_id) as avg_ret
FROM
    (select DISTINCT device_id,date
    from question_practice_detail) as res1
left join
    (select DISTINCT device_id, DATE_SUB(date,INTERVAL 1 DAY) as date
    from question_practice_detail) as res2
on res1.device_id = res2.device_id and res1.date = res2.date;

 

 

# 参考1
select 'male' as gender, COUNT(device_id) as number
FROM user_submit WHERE right(profile,6)<>"female"
UNION all
select 'female' as gender, COUNT(device_id) as number
FROM user_submit where right(profile,6)="female";


# 参考2
SELECT if(profile like '%female','female', 'male') as gender,COUNT(device_id) as number
FROM user_submit GROUP BY gender;


# 参考3(使用SUBSTRING_INDEX函数根据逗号将profile字段切分,性别位于最后一位,位置填写-1即可取出性别字段)
select substring_index(profile,',',-1) as gender,count(*) as number
from user_submit group by  gender;



# 1、LOCATE(substr , str ):返回子串 substr 在字符串 str 中第一次出现的位置,如果字符substr在字符串str中不存在,则返回0;
# 2、POSITION(substr  IN str ):返回子串 substr 在字符串 str 中第一次出现的位置,如果字符substr在字符串str中不存在,与LOCATE函数作用相同;
# 3LEFT(str, length):从左边开始截取str,length是截取的长度;
# 4RIGHT(str, length):从右边开始截取str,length是截取的长度;
# 5、SUBSTRING_INDEX(str  ,substr  ,n):返回字符substr在str中第n次出现位置之前的字符串;
# 6SUBSTRING(str  ,n ,m):返回字符串str从第n个字符截取到第m个字符;
# 7REPLACE(str, n, m):将字符串str中的n字符替换成m字符;
# 8、LENGTH(str):计算字符串str的长度。

 

 

select 
    device_id,
    substring_index(blog_url,'/',-1) as user_name
FROM user_submit;


# 其他1
select 
    device_id,
    replace(blog_url,'http:/url/','') as user_name
from user_submit;


# 其他2
select
    device_id,
    SUBSTRING(blog_url,11) as user_name
FROM user_submit;


# 其他3
select
    device_id,
    SUBSTR(blog_url,11) as user_name
FROM user_submit;

 

 

# 参考1:substring_index()
# 详解见https://blog.nowcoder.net/n/44a90eb9828d41d8800c7e160cbc37e8
select 
    substring_index(
        substring_index(profile, ',' ,3),
        ',',
        -1) as age,
    count(device_id) as number
from user_submit a 
group by age;


# 参考2:SUBSTRING()
SELECT 
    SUBSTRING(profile,12,2) as age,
    COUNT(1) as number
FROM user_submit
GROUP BY age;

SELECT device_id,university,gpa
from user_profile
WHERE gpa in (select min(gpa)
          from user_profile
          GROUP BY university)
GROUP BY university   # 结果要求每个大学出一个结果(最低GPA)
ORDER BY university;  # 结果要求按照大学名称首字母排序

 

 

 

 

SELECT
    t1.device_id,
    t1.university,
    COUNT(t1.question_cnt) as question_cnt,
    SUM(
        IF(t2.result='right',1,0)
    ) as right_question_cnt
from 
    user_profile as t1,
    question_practice_detail as t2
WHERE
    t1.university = '复旦大学'
    and 
    t1.device_id = t2.device_id;
# 提交通过;自测结果却不对,可能示例数据不对(下面的参考也是如此)



# 参考1
SELECT
    uu.device_id,
    uu.university,
    count(q.question_id) as question_cnt,
    sum(if(q.result='right',1,0)) as right_question_cnt
from 
    question_practice_detail as q
left outer join 
    user_profile as uu
    on q.device_id=uu.device_id and MONTH(q.date)=8
where  
    uu.university='复旦大学'
group by 
    q.device_id;


# 参考2
select 
    up.device_id,university,
    count(upd.device_id) question_cnt,
    count(case when result='right' then 1 else null end) right_question_cnt
from 
    user_profile as up 
left join 
    question_practice_detail as upd 
    on upd.device_id=up.device_id
where 
    university='复旦大学' and month(date)='08'
group by 
    up.device_id;


# 参考3
SELECT 
    q.device_id, u.university,
    COUNT(question_id) AS question_cnt, 
    COUNT(IF(result='right', 1, NULL)) AS right_question_cnt
FROM user_profile u 
INNER JOIN question_practice_detail q
WHERE u.device_id=q.device_id AND university="复旦大学"
GROUP BY u.device_id;



    

 

 

 

 

SELECT
    t3.difficult_level,
    (
        SUM(if(t2.result='right',1,0)) / COUNT(t2.result)
    ) as correct_rate
FROM    
    user_profile t1,
    question_practice_detail t2,
    question_detail t3
WHERE
    t1.university = '浙江大学'
    and t1.device_id = t2.device_id
    and t2.question_id = t3.question_id
GROUP BY t3.difficult_level
ORDER BY correct_rate ASC;



# 其他1:https://blog.nowcoder.net/n/a61f6faea0f34fb4817bddcbcb76172d
SELECT qd.difficult_level,
       sum(if(qpd.result='right',1,0))/count(qpd.device_id) AS correct_rate
FROM
    (question_practice_detail AS qpd
    LEFT JOIN user_profile AS u 
    ON qpd.device_id = u.device_id 
    LEFT JOIN question_detail AS qd
    ON qpd.question_id = qd.question_id)
WHERE u.university = '浙江大学'
GROUP BY qd.difficult_level
ORDER BY correct_rate ASC;


# 其它2:1.要把三个表连起来;2.用前面提到的sum和if的结合来计算个数;3.记得排序
SELECT
    q.difficult_level,
    sum(if(q.result='right',1,0))/count(q.question_id) as correct_rate
from 
    (SELECT
         q1.question_id,
         q1.device_id,
         q1.result,
         qd1.difficult_level
     from 
         question_practice_detail as q1
     left outer JOIN
         question_detail as qd1
         on q1.question_id= qd1.question_id
    ) as q
    left outer JOIN
        user_profile as u
    on q.device_id = u.device_id
where u.university='浙江大学'
group by q.difficult_level
order by correct_rate asc;

-- 查找后多列排序
select device_id, gpa, age
from user_profile
order by gpa ASC, age ASC;


# 默认以升序排列,以下撒种方式均可
# SELECT device_id,gpa,age from user_profile order by gpa,age;
# SELECT device_id,gpa,age from user_profile order by gpa,age asc;
# SELECT device_id,gpa,age from user_profile order by gpa asc,age asc;

 

 

select 
    COUNT( distinct device_id) as did_cnt,
    COUNT( question_id) as qusetion_cnt
from question_practice_detail
where date like '2021-08%';



# 其他1
select 
    COUNT( distinct device_id) as did_cnt,
    COUNT( question_id) as qusetion_cnt
from question_practice_detail
where 
    year(date)=2021 and MONTH(date)=8;


# 其他2:字符串匹配 LIKE '' ,其中_匹配单个字符,%匹配多个
SELECT  
    COUNT( DISTINCT device_id) as did_cnt,
    count(question_id) as question_cnt
from question_practice_detail
where 
    date like '2021-08-__';

 


 

2、简单

SQL1 查找最晚入职员工的所有信息

 

select * from employees 
order by hire_date desc 
limit 1;

select * from employees 
order by hire_date desc 
limit 0,1;

# 不建议用ORDER BY + LIMIT 1 的原因:最晚日期可能存在多个员工

select * from employees 
where hire_date = (select max(hire_date) from employees);

select * from employees 
where hire_date in (select max(hire_date) from employees);

  


 

3、入门

SQL2 查找入职员工时间排名倒数第三的员工所有信息

# limit n:表示从第0条数据开始,读取n条数据,是limt(0, n)的缩写
# limit m,n:表示从第m条数据开始,读取n条数据
# limit n offset m:表示从第m条数据开始,读取n条数据(mysql5以后支持这种写法)


select * from employees
order by hire_date DESC
limit 2,1;

select * from employees
order by hire_date DESC
limit 1 offset 2;


# 入职时间相同的员工可能不止一人,推荐这样写
SELECT * FROM employees
WHERE hire_date = ( # 此处不能用in,因为mysql不支持一条语句中in和limit一起用
    SELECT DISTINCT hire_date 
    FROM employees
    ORDER BY hire_date DESC
    LIMIT 1 OFFSET 2
);

 

 SQL4 查找所有已经分配部门的员工的last_name和first_name以及dept_no

 

 

 

 

 

select last_name, first_name, dept_no
from employees as e, dept_emp as d
where e.emp_no = d.emp_no and d.dept_no is not null;


# 1、外部表联结
# 左外部联结(LEFT JOIN)- 包含左边表的所有列
# 右外部联结(RIGHT JOIN)- 包含右边表的所有列
SELECT e.last_name, e.first_name, de.dept_no
FROM dept_emp AS de 
LEFT JOIN employees AS e
ON de.emp_no = e.emp_no;


# 2、内部联结
SELECT e.last_name, e.first_name, de.dept_no
FROM dept_emp AS de 
JOIN employees AS e 
ON de.emp_no = e.emp_no
WHERE de.dept_no IS NOT NULL;

 

 

 SQL7 查找薪水记录超过15次的员工号emp_no以及其对应的记录次数t

 

 SQL8 找出所有员工当前薪水salary情况

 

 

 

select distinct salary 
from salaries
order by salary desc;


-- WHERE语句在GROUP BY语句之前,SQL会在分组之前计算WHERE语句。
-- HAVING语句在GROUP BY语句之后,SQL会在分组之后计算HAVING语句。
-- distinct可以去重,group by也可以

select salary
from salaries
group by salary
order by salary DESC;

 

SQL10 获取所有非manager的员工emp_no

 

 

 

 

 

 

SELECT emp_no 
from employees
where 
    emp_no in (select emp_no from employees) -- 这句没必要了
    and emp_no not in (select emp_no from dept_manager);


# 1、NOT IN+子查询
SELECT emp_no 
from employees
where emp_no not in (select emp_no from dept_manager);


# 2、左连接 + 去中
SELECT e.emp_no 
from employees as e
left join dept_manager as d
on e.emp_no = d.emp_no
where d.emp_no is null;

  

 SQL15 查找employees表emp_no与last_name的员工信息

 

select * from employees
where emp_no%2!=0 and last_name!='Mary'
order by hire_date desc;


SELECT * FROM employees
WHERE emp_no%2=1 AND last_name NOT LIKE 'Mary'
ORDER BY hire_date DESC;

  

 SQL17 获取当前薪水第二多的员工的emp_no以及其对应的薪水salary

 

SELECT emp_no, salary from salaries
order by salary DESC
LIMIT 1,1; -- 第一个1表示从第1条记录后面开始取,第二个1表示取1条记录


select emp_no, salary from salaries
where salary = ( select salary from salaries 
                group by salary 
                order by salary desc 
                limit 1,1);

  

 SQL32 将employees表的所有员工的last_name和first_name拼接起来作为Name

 

select concat_ws(' ', last_name, first_name) as Name from employees;


select CONCAT(last_name, ' ', first_name) as Name from employees;

  

 SQL34 批量插入数据

 

insert into actor (actor_id,first_name,last_name,last_update)
values (1,'PENELOPE','GUINESS','2006-02-15 12:34:33'),
       (2,'NICK','WAHLBERG','2006-02-15 12:34:33');


insert into actor
values (1,'PENELOPE','GUINESS','2006-02-15 12:34:33'),
       (2,'NICK','WAHLBERG','2006-02-15 12:34:33');

  

 SQL42 删除emp_no重复的记录,只保留最小的id对应的记录。

 

# 错误
# DELETE FROM titles_test
# WHERE id NOT IN(
#     SELECT MIN(id)
#     FROM titles_test
#     GROUP BY emp_no); 
# MySQL中不允许在子查询的同时删除表数据(不能一边查一边把查的表删了)

# 正确
DELETE FROM titles_test
WHERE id NOT IN(SELECT *
                FROM (SELECT MIN(id)
                     FROM titles_test
                     GROUP BY emp_no)
                as a);  -- 把得出的表重命名为中间表a那就不是原表了

SQL43 将所有to_date为9999-01-01的全部更新为NULL

 

 

 

 

update titles_test
set to_date=null, from_date='2001-01-01'
where to_date='9999-01-01';

  

 SQL44 将id=5以及emp_no=10001的行数据替换成id=5以及emp_no=10005

# 1、使用replace
UPDATE titles_test
SET emp_no = REPLACE(emp_no, 10001, 10005)
WHERE id = 5;


# 2、使用insert(有遇到重复主键了就进行更新emp_no的值)
INSERT INTO titles_test 
    VALUES(5, 10001 ,'Senior Engineer', '1986-06-26', '9999-01-01') 
    ON DUPLICATE KEY UPDATE emp_no = 10005; 


# 3、使用replace into(REPLACE INTO当遇到primary 或者 unique key 的时候,会首先进行update)
REPLACE INTO titles_test 
    VALUES(5, 10005 ,'Senior Engineer', '1986-06-26', '9999-01-01') ;

SQL45 将titles_test表名修改为titles_2017

 

ALTER TABLE titles_test
RENAME TO titles_2017;
# 更改表名语句结构: ALTER TABLE 原表名 RENAME TO/AS 新表名

  

 SQL62 出现三次以上相同积分的情况

 

-- 分组、筛选组
select number from grade
group by number
having count(number)>=3;


SELECT
    e.number
FROM
(
    SELECT
        number, count(number) as cnt
    FROM
        grade
    GROUP BY
        number
) as e
WHERE
    e.cnt >= 3;

SQL64 找到每个人的任务

 

 

select person.id,person.name,task.content -- 这个题目挺简单的,首先肯定先写出,要输出的东西:
from person 
left join task on person.id=task.person_id -- 但是有个坑注意就是,没有任务的也要输出,所以连接task表的时候要使用左连接:
order by person.id; -- 最后按照person的id升序输出

  

 SQL66 牛客每个人最近的登录日期(一)

 

 

 

select user_id, max(date) as d
from login
group by user_id
order by user_id;

SQL72 考试分数(一)

 

 

select job, round(avg(score), 3) as avg
from grade
group by job
order by avg desc;
-- 使用 ROUND(聚合函数,精确到小数点后几位)的方法来满足该需求

SQL77 牛客的课程订单分析(一)

 

 

select *  
from order_info
where 
    date>'2025-10-15' 
    and status='completed'
    and (product_name='C++' 
         or product_name='Java' 
         or product_name='Python')
order by id asc;


select 
    id, user_id, product_name, status, client_id, date
from 
    order_info
where 
    date > '2025-10-15'
    and product_name in ('C++','Java','Python')
    and status = 'completed'
order by 
    id asc;



-- DATEDIFF(d1,d2) 语句,计算日期 d1到d2 之间相隔的天数
select *
from order_info
where 
    datediff(date,"2025-10-15")>0
    and status = "completed"
    and product_name in ("C++","Java","Python")
order by id;

SQL84 实习广场投递简历分析(一)  

 

 

SELECT job, sum(num) as cnt
from resume_info
where YEAR(date)='2025'
GROUP by job
order by cnt desc;

  


 

4、中等

 SQL92 商品交易(网易校招笔试真题)

 

SELECT g.id, g.name, g.weight, sum(t.count) as total
from goods as g, trans as t
where g.weight < 50 and g.id = t.goods_id
group by t.goods_id
order by g.id asc;


-- 其他
select id,name,weight,total 
from 
    goods,
    (select t.goods_id , sum(t.count) as total from trans t group by t.goods_id) as ts
where 
    goods.id = ts.goods_id
    and (goods.weight<50 and ts.total > 20);
    

-- 其他
SELECT a.id, a.name, a.weight, sum(b.count) as total
from goods a
left join trans b on a.id=b.goods_id
WHERE a.weight<50
group by a.name
having total>20;

SQL3 查找当前薪水详情以及部门编号dept_no

 

 

 

select salaries.emp_no, salary, from_date, salaries.to_date, dept_no
from salaries, dept_manager
where salaries.emp_no = dept_manager.emp_no
order by salaries.emp_no;



SELECT s.emp_no, s.salary, s.from_date, s.to_date, d.dept_no
FROM salaries s 
JOIN dept_manager d -- 或者inner join
ON s.emp_no=d.emp_no
ORDER BY s.emp_no ASC;


SELECT s.emp_no, s.salary, s.from_date, s.to_date, d.dept_no
FROM salaries s 
INNER JOIN dept_manager d 
ON s.emp_no=d.emp_no
ORDER BY s.emp_no ASC;

 

 SQL5 查找所有员工的last_name和first_name以及对应部门编号dept_no  

select e.last_name, e.first_name, d.dept_no
from employees as e
left join dept_emp as d
on e.emp_no=d.emp_no;


-- 左连接是以左表为基准匹配右表数据 ,匹配不上的填写null
-- 需要查找所有员工,包括没有分配部门的员工,因此在连接时需要以employees为主表

-- left join 左边的为主表,right join右边的为主表

SELECT e.last_name, e.first_name, d.dept_no
FROM dept_emp AS d
right JOIN employees AS e 
ON d.emp_no = e.emp_no;

 

 

 

左连接 

 

 

 右连接

SQL11 获取所有员工当前的manager  

-- 参考1:两表联结 且 员工不为manager
SELECT de.emp_no, dm.emp_no
FROM dept_emp AS de 
LEFT JOIN dept_manager AS dm 
ON de.dept_no = dm.dept_no
WHERE de.emp_no NOT IN (SELECT emp_no FROM dept_manager);


-- 参考2
SELECT de.emp_no, dm.emp_no
FROM dept_emp AS de
left JOIN dept_manager as dm 
ON de.dept_no = dm.dept_no
WHERE de.emp_no != dm.emp_no

 

SQL16 统计出当前各个title类型对应的员工当前薪水对应的平均工资

 

 

 

select title, avg(salary) as avg_salary
from titles as t, salaries as s
where t.emp_no = s.emp_no
group by title
order by avg_salary;


# 通过group by对各个title类型进行分组
# 对这一题来说,用内连接、左连接和右连接都可以,因为两个表中的emp_no一样,不会出现null的情况

# order by 排序
# group by 进行数据分组
# having 过滤分组
# where 过滤行

 

SQL19 查找所有员工的last_name和first_name以及对应的dept_name

 

 

 

 

-- 参考1:要求连结employees表的全部行(包括暂时没有分配部门的员工)
select e.last_name, e.first_name, d.dept_name
from departments as d
join dept_emp as de on d.dept_no = de.dept_no
right join employees as e on e.emp_no = de.emp_no;


-- 参考2
SELECT a.last_name,a.first_name,b.dept_name
FROM (SELECT e.emp_no, e.last_name, e.first_name, de.dept_no
    FROM employees AS e
    LEFT JOIN dept_emp AS de
    ON e.emp_no = de.emp_no) AS a
LEFT JOIN (SELECT de2.emp_no, de2.dept_no, d.dept_name
    FROM dept_emp AS de2
    INNER JOIN departments AS d 
    ON de2.dept_no = d.dept_no) AS b
ON a.dept_no=b.dept_no AND a.emp_no=b.emp_no;


-- 参考3
select last_name,first_name,dept_name 
from employees e
left join (departments d, dept_emp de)
on e.emp_no=de.emp_no and d.dept_no=de.dept_no;


-- 参考4:https://blog.nowcoder.net/n/0a82d1626be44ab7943a62ed8c7772aa
select last_name, first_name, dept_name
from employees e 
left join dept_emp de on de.emp_no = e.emp_no
left join departments d on de.dept_no = d.dept_no;
# 1、需要所有员工的last_name和first_name, 这两个信息都来自于employees,所以要把employees作为主表
# 2、先连接employees和dept_emp,将employees作为主表,把dept_no信息添加进来
# 3、再连接第2步中的表 和 departments,依然是将包含employees表内容的表作为主表,把dept_name信息添加进来
# 注意:两个join连着用,意味着先把employees和dept_emp连接,然后将连接后的表再与departments表连接,
# 而不是employees表分别与dept_emp、departments表连接

 

 SQL22 统计各个部门的工资记录数  

 

 

 

 

-- 思路:三表连结再分组再排序,使用聚合函数
select d.dept_no, d.dept_name, count(s.emp_no)
FROM departments d, dept_emp de, salaries s
where d.dept_no = de.dept_no and de.emp_no = s.emp_no
group by dept_no
order by dept_no;


-- 其他1
select d.dept_no, d.dept_name, count(*) as sum
from (departments d join dept_emp de on d.dept_no=de.dept_no)
     join salaries s on de.emp_no=s.emp_no
group by d.dept_no
order by dept_no;

 

 

 

 

 

 

 

SQL29 使用join查询方式找出没有分类的电影id以及名称

-- 使用join
select f.film_id, f.title
from film as f left join film_category as fc on f.film_id = fc.film_id
where fc.category_id is null;


-- 使用子查询
select f.film_id, f.title
from film as f
where f.film_id not in (select film_id from film_category);

SQL30 使用子查询的方式找出属于Action分类的所有电影对应的title,description

-- 不用子查询,直接where
select f.title, f.description
from film as f, category as c, film_category as fc
where 
    f.film_id = fc.film_id
    and c.category_id = fc.category_id
    and c.name = 'Action';



-- 子查询
select f.title, f.description
from film as f
where 
    f.film_id in (select fc.film_id 
                  from film_category as fc, category as c
                  where c.category_id = fc.category_id
                        and c.name = 'Action');


-- 参考1:join + 子查询
select f.title,f.description
from film f
left join film_category fc
on f.film_id = fc.film_id
where fc.category_id = (select category_id from category where name = 'Action');
# 1. 先分析题目“找出属于Action分类的所有电影”,按照 name 去找 分类,只能从 category 表去找;
# 2. 但是 category 和 film表没有直接关联,恰好film_category 表和 film有关联,最重要的是,film_category 表和 category 表也有关联(但是film_category 表中不能直接查询name,但是可以通过分类id去查);
# 3. 题目要求使用子查询,所以此时,我们可以先 在category表中查出 name为 Action的分类id,再通过分类id去查询。


-- 参考2:子查询
SELECT f.title, f.description
FROM film AS f, film_category AS fc
WHERE f.film_id = fc.film_id
      AND fc.category_id IN (SELECT category_id
                            FROM category
                            WHERE name = 'Action');

 

SQL33 创建一个actor表,包含如下列信息

 

drop table if exists actor;
create TABLE actor(
    actor_id smallint(5) not null primary key comment "主键id",
    first_name varchar(45) not null comment "名字",
    last_name varchar(45) not null comment "姓氏",
    last_update date not null comment "日期"
);

 

SQL35 批量插入数据,不使用replace操作 

 

insert ignore into actor VALUES('3','ED','CHASE','2006-02-15 12:34:33');



insert into actor(actor_id, first_name, last_name, last_update)
select 3, 'WD', 'GUINESS', '2006-02-15 12:34:33' 
from DUAL
where not exists (select actor_id FROM actor WHERE actor_id = 3);

SQL36 创建一个actor_name表 

 

drop table if exists actor_name;
create TABLE actor_name(
    first_name varchar(45) not null comment "名字",
    last_name varchar(45) not null comment "姓氏"
);
INSERT INTO actor_name
SELECT first_name,last_name
FROM actor;

-- https://blog.nowcoder.net/n/aa94945c76e6427482ef50d6c2b31ef6

SQL37 对first_name创建唯一索引uniq_idx_firstname 

-- MySQL中四种方式给字段添加索引以及删除索引
-- https://blog.nowcoder.net/n/afd028af866a43a389ffdd161dadb0fc

-- 参考1
CREATE INDEX idx_lastname ON actor(last_name);
CREATE UNIQUE INDEX uniq_idx_firstname ON actor(first_name);


-- 参考2
alter table actor add unique uniq_idx_firstname(first_name);
alter table actor add index idx_lastname(last_name);


# 使用ALTER 命令添加索引,有四种方式来添加数据表的索引:
# ALTER TABLE tbl_name ADD PRIMARY KEY (column_list): 该语句添加一个主键,这意味着索引值必须是唯一的,且不能为NULL。
# ALTER TABLE tbl_name ADD UNIQUE index_name (column_list): 这条语句创建索引的值必须是唯一的(除了NULL外,NULL可能会出现多次)。
# ALTER TABLE tbl_name ADD INDEX index_name (column_list): 添加普通索引,索引值可出现多次。
# ALTER TABLE tbl_name ADD FULLTEXT index_name (column_list):该语句指定了索引为 FULLTEXT ,用于全文索引。

 

SQL38 针对actor表创建视图actor_name_view  

 

-- 1、直接在视图名的后面用小括号创建视图中的字段名
create view actor_name_view (first_name_v,last_name_v) 
as
select first_name ,last_name from actor;


-- 2、在select后面对列重命名为视图的字段名
CREATE VIEW actor_name_view
AS 
SELECT first_name AS first_name_v, last_name AS last_name_v FROM actor;

 

SQL39 针对上面的salaries表emp_no字段创建索引idx_emp_no

-- 题目创建表是已经创建索引idx_emp_no了。所以我们只需要按题意使用强索引进行查询
select * from salaries
force index (idx_emp_no)
where emp_no=10005


-- 强制索引的作用https://www.cnblogs.com/ll409546297/p/9060820.html

 

SQL40 在last_update后面新增加一列名字为create_date  

 

alter table actor 
add create_date datetime not null default '2020-10-01 00:00:00';

 

SQL41 构造一个触发器audit_log

-- https://blog.csdn.net/weixin_41177699/article/details/80302987
-- https://blog.nowcoder.net/n/7e02277e6ca04f6bb3168b2bad1f3c7c

# 在MySQL中,创建触发器语法如下:
    # CREATE TRIGGER trigger_name
    # trigger_time trigger_event ON tbl_name
    # FOR EACH ROW
    # trigger_stmt
# 其中:
    # trigger_name:标识触发器名称,用户自行指定;
    # trigger_time:标识触发时机,取值为 BEFORE 或 AFTER;
    # trigger_event:标识触发事件,取值为 INSERT、UPDATE 或 DELETE;
    # tbl_name:标识建立触发器的表名,即在哪张表上建立触发器;
    # trigger_stmt:触发器程序体,可以是一句SQL语句,或者用 BEGIN 和 END 包含的多条语句,每条语句结束要分号结尾。
    
    
create trigger audit_log 
after insert on employees_test
for each row
begin 
    insert into audit values(new.id,new.name); -- 注意这里有一个分号,必须有且只能放在这
end

 

SQL46 在audit表上创建外键约束,其emp_no对应employees_test表的主键id  

 

alter table audit
add constraint foreign key (emp_no)
references employees_test(id);


# 创建外键语句结构:
    # ALTER TABLE <主表名>
    # ADD CONSTRAINT FOREIGN KEY (<主表列名>)
    # REFERENCES <关联表>(关联列)

 

SQL48 将所有获取奖金的员工当前的薪水增加10% 

update salaries
set salary=salary+salary*0.1
where 
    to_date='9999-01-01'
    and salaries.emp_no in (select emp_no from emp_bonus);
    
    

UPDATE salaries AS s
SET s.salary = 1.1 * s.salary
WHERE s.to_date = '9999-01-01'
AND s.emp_no IN(SELECT emp_no 
                FROM emp_bonus);

 

SQL50 将employees表中的所有员工的last_name和first_name通过引号连接起来 

 

 

 

select concat(last_name, "'", first_name) as name from employees;


# concat()函数
    # 1、功能:将多个字符串连接成一个字符串。
    # 2、语法:concat(str1, str2,...)
    # 返回结果为连接参数产生的字符串,如果有任何一个参数为null,则返回值为null。

 

QL51 查找字符串“10,A,B”中逗号,出现的次数cnt

# select count(",") as cnt from '10,A,B'
-- 不对

-- 参考  https://blog.nowcoder.net/n/e3448a4cbd5c45d4abfe524db0b6f33e
select char_length("10,A,B")-char_length(replace("10,A,B", ",", ""));

# char_length('string')/char_length(column_name)
    # 1、返回值为字符串string或者对应字段长度,长度的单位为字符,一个多字节字符(例如,汉字)算作一个单字符;
    # 2、不管汉字还是数字或者是字母都算是一个字符;
    # 3、任何编码下,多字节字符都算是一个字符;
    
    
# length('string')/length(column_name)
    # 1、utf8字符集编码下,一个汉字是算三个字符,一个数字或字母算一个字符。
    # 2、其他编码下,一个汉字算两个字符, 一个数字或字母算一个字符
    
    
# 字符串替换:REPLACE(s,s1,s2),将字符串 s2 替代字符串 s 中的字符串 s1
-- replace("10,A,B", ",", ""):将"10,A,B"中的所有逗号换为空字符,即删掉了两个逗号




-- 直接这句也行,QAQ 
select 2;

 

SQL52 获取Employees中的first_name

 

-- 方法1:自己写的
-- substr(str,start,length)
    # 1.字符串的第一个字符的索引是1。
    # 2.值为正时从字符串开始位置开始计数,值为负时从字符串结尾位置开始计数。
    # 3.长度不填时默认取到结尾。

select first_name
from employees
order by SUBSTR(first_name, -2, 2);




-- 方法2:参考
-- right(str,num) 函数
-- 从右边开始截取str字符串num长度.同理还有left函数

SELECT first_name FROM employees ORDER BY RIGHT(first_name,2);

SQL53 按照dept_no进行汇总  

-- 参考
select dept_no, group_concat(emp_no) as employees
from dept_emp
group by dept_no;

# 知识点总结:group_concat()函数将group by产生的同一个分组中的值连接起来,返回一个字符串结果。
# 语法:group_concat( [distinct] 要连接的字段 [order by 排序字段 asc/desc ] [separator '分隔符'] )
#      通过使用distinct可以排除重复值;
#      如果希望对结果中的值进行排序,可以使用order by子句;
#      separator是一个字符串值,缺省为一个逗号。

 

SQL54 平均工资

-- 参考
SELECT AVG(salary) as avg_salary
FROM salaries
WHERE salary NOT IN(SELECT MAX(salary) 
                    FROM salaries 
                    WHERE to_date = '9999-01-01')
      AND salary NOT IN (SELECT MIN(salary) 
                        FROM salaries 
                        WHERE to_date = '9999-01-01')
      AND to_date = '9999-01-01';


SELECT AVG(salary) as avg_salary
FROM salaries
WHERE salary != (SELECT MAX(salary) 
                    FROM salaries 
                    WHERE to_date = '9999-01-01')
      AND salary != (SELECT MIN(salary) 
                        FROM salaries 
                        WHERE to_date = '9999-01-01')
      AND to_date = '9999-01-01';

# 关于为什么要用多个where子句判断to_date="9999-01-01".
#     题目描述查找排除最大、最小salary之后的当前(to_date = '9999-01-01' )员工的平均工资avg_salary。
#     个人思路先找出当前在职员工的最大、最小薪水。然后在当前在职员工薪水集合里扣除再求均值

 

SQL55 分页查询employees表,每5行一页,返回第2页的数据

-- 参考1
select *
from employees
limit 5,5

-- limit n,m 等于limit m offset n
-- n为偏移量,m为获取数据的个数
-- n=5说明数据取在5行之后(即首页之后,从第六条开始取)。
-- m=5即取五条数据(每5行为一页,所以取第二页数据)。

-- 参考2
select *
from employees
limit 5 offset 5

 

SQL57 使用含有关键字exists查找未分配具体部门的员工的所有信息。  

-- 自己实现;未使用exists关键字,虽能出结果但不符合要求
select * 
from employees
where emp_no not in (select emp_no
                    from dept_emp);



# IN 语句:只执行一次
# 确定给定的值是否与子查询或列表中的值相匹配。in在查询的时候,首先查询子查询的表,然后将内表和外表做一个笛卡尔积,然后按照条件进行筛选。所以相对内表比较小的时候,in的速度较快。
# EXISTS语句:执行employees.length次
# 指定一个子查询,检测行的存在。遍历循环外表,然后看外表中的记录有没有和内表的数据一样的。匹配上就将结果放入结果集中。


-- 参考
select *
from employees
where not exists (select emp_no
                 from dept_emp
                 where employees.emp_no=dept_emp.emp_no);

 

SQL63 刷题通过的题目排名

# select id, number, ROWS as t_rank
# from passing_number
# order by number, id asc;
-- ROWS不对,想找一个可以能对记录依次编号的变量



-- 参考:利用窗口函数计算排名
SELECT 
    id, number, 
    dense_rank() over(ORDER BY number DESC) AS t_rank
FROM passing_number;


-- 窗口函数详解:https://blog.nowcoder.net/n/bb8e0fcbe12a49ee9b83519b3e950d6f
-- 1. rank() over:查出指定条件后的进行排名。特点是相同排名虽然并列,但是下一排名会空出位置
-- 2. dense_rank() over:查出指定条件后的进行排名。特点是相同排名并列,并且下一排名不会空出位置
-- 3. row_number() over:不存在并列排名的情况,只会顺序排名


# mysql 排名函数(搭配窗口函数)
# 1、rank()over()//按照窗口分区,为每一行分配并列排名,通常不是连续的,若有并列的(1 1 3),会直接跳过2
# 2、dense_rank()//并列连续排序
# 3、row_number()//连续排名

 

SQL73 考试分数(二)  

-- 参考
-- 1.先找出每个工作的平均分。
-- 2.用grade表与1进行比较,找出分数大于均分的数据并按id升序排序。
select t1.*
from grade as t1 
join(select job, avg(score) as avg_score
    from grade
    group by job) as t2
on t1.job = t2.job
where t1.score > t2.avg_score
order by t1.id;

 

SQL78 牛客的课程订单分析(二) 

 

 

-- 参考
select user_id
from order_info
where datediff(date,"2025-10-15") > 0
      and status ="completed"
      and product_name in ("C++","Java","Python")
group by user_id
having count(id) >= 2
order by user_id;


# 按题意一步一步写条件即可。
# 注意where子句常见错误之一:
#     where子句中不能使用聚合函数,聚合函数可以在select,having,order by之后出现。
#     where指定分组之前数据行的条件,having子句用来指定分组之后条件。

 

SQL79 牛客的课程订单分析(三) 

 

 

-- 参考:https://blog.nowcoder.net/n/bd5dd0435ca346aa9d701b6b8428961a
-- 与SQL78(二)几乎一样。不过的是因为要显示的所有信息因此不能直接使用group by后的结果。

select *
from order_info
where datediff(date,"2025-10-15")>0
      and product_name in ("C++","Java","Python")
      and status="completed"
      and user_id in (
                      select user_id
                      from order_info
                      where datediff(date,"2025-10-15")>0
                            and product_name in ("C++","Java","Python")
                            and status ="completed"
                      group by user_id
                      having count(id)>1
                     )
order by id;

 

SQL82 牛客的课程订单分析(六)

 

 

 

-- 参考:https://blog.nowcoder.net/n/0bb5b81646c84f7cbcda7e69c11450cd
-- 个人思路:以我的SQL79(三)为基础,右连接+窗口函数。
select t2.id, t2.is_group_buy, t1.name as client_name
from client AS t1 right join
(
    select *, count(id) over(partition by user_id) as number
    from order_info
    where datediff(date,"2025-10-15")>0
          and status="completed"
          and product_name in ("C++","Java","Python")
) AS t2
on t1.id=t2.client_id
where t2.number > 1
order by t2.id;

 

SQL85 实习广场投递简历分析(二)

-- 参考1
-- 按月统计数量并排序
select job,date_format(date,'%Y-%m') as mon,sum(num) as cnt
from resume_info
where date like '2025%'
group by job,mon
order by mon desc,cnt desc;

# DATE_FORMAT(date或DATETIME,format)函数用于以不同的格式显示日期/时间数据
# date 参数是合法的日期。format 规定日期/时间的输出格式
    # %Y 四位数字表示的年份
    # %y 两位数字表示的年份
    # %m 两位数字表示的月份( 01, 02, . . ., 12)
    # %d 两位数字表示月中的天数( 00, 01, . . ., 31)
    
    
    
    
-- 参考2
select job, DATE_FORMAT(date, '%Y-%m') as mon, sum(num) as cnt
from resume_info
where date < '2026-01-01'and date > '2024-12-31'
group by job, mon
order by mon desc , cnt desc;



-- 参考3
select job, DATE_FORMAT(date, '%Y-%m') as mon, sum(num) as cnt
from resume_info
where YEAR(date) = 2025
group by job, mon
order by mon desc , cnt desc;

 

SQL87 最差是第几名(一) 

-- 参考1:用转置函数 case when 解决
select grade ,
case grade when 'A' then (select sum(number) from class_grade where grade <= 'A')
            when 'B' then (select sum(number)from class_grade where grade <= 'B')
            when 'C' then (select sum(number)from class_grade where grade <= 'C')
            when 'D' then (select sum(number)from class_grade where grade <= 'D')
            else (select sum(number) from class_grade)
    end
from class_grade
order by grade



-- 参考2:用窗口函数sum()over()解决
select grade, sum(number)over(order by grade asc)t_rank
from class_grade
order by grade

-- case函数和窗口函数详解
-- https://blog.nowcoder.net/n/3872ffc328524ada842ead8843171aa7

 

SQL89 获得积分最多的人(一)

 

 

-- 参考1:使用窗口函数sum()over()
-- 总积分最高的 而不是 增加积分最高的
select u.name, g.grade
from (select user_id, sum(grade_num) over(partition by user_id) as grade
      from grade_info
      order by grade desc
      limit 1) as g
join user as u on g.user_id = u.id;



-- 参考2
select b.name, sum(a.grade_num) as grade_sum 
from grade_info as a 
left join user as b on a.user_id = b.id
group by a.user_id 
order by grade_sum desc
limit 1;

 

推荐阅读