zoukankan      html  css  js  c++  java
  • 牛客-SQL-刷题(上)

    牛客 SQL题目

    SQLite数据库 3.7.9

    细节:SQL对大小写不敏感,但在大型ERP系统开发时显得重要,操作系统会将所有字符转换为大写
               所以在平时习惯用大写

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

        分析:第2页数据,即第6-10条

    SELECT * FROM employees LIMIT 5,5;
    • LIMIT n,m:从第n条记录开始,返回m条记录;第1条记录是0

    2. 查找字符串'10,A,B' 中逗号','出现的次数cnt

        分析:统计子串出现次数问题

    SELECT (LENGTH('10,A,B')-LENGTH(REPLACE('10,A,B',',','')))/LENGTH(',') cnt;
    • ( LENGTH(str)-LENGTH(REPLACE(str,substr,’ ’)) )/ LENGTH (substr)
    • REPLACE( str, substr, replacestr) 在str中,用replacestr替换subst

    3. 将id=5以及emp_no=10001的行数据替换成id=5以及emp_no=10005,其他数据保持不变,使用replace实现.

        分析:更新titles_test的行数据 (题目中已显示所有插入数据信息)

    REPLACE INTO titles_test VALUES (5, 10005, 'Senior Engineer', '1986-06-26', '9999-01-01');
    • REPLACE INTO table VALUES(…) 重新插入一条完整的新纪录,SQL会将id相同的记录自动替换
    UPDATE titles_test SET emp_no=REPLACE(emp_no,10001,10005) 
    WHERE id=5;
    • UPDATE 表名 SET colum=[new value] WHERE …

    4. 获取Employees中的first_name,查询按照first_name最后两个字母,按照升序进行排列

        分析:考察SUBSTR()

    SELECT first_name FROM employees ORDER BY SUBSTR(first_name,-2);
    • SUBSTR(str, start, length):在str中从start位置开始截取长度为length的子串
    • start参数:必选,第一个位置是1;倒数第一个位置是-1;length参数:可选,缺省默认到末尾字符

    5. 将titles_test表名修改为titles_2017

       分析:修改表名

    ALTER TABLE titles_test RENAME TO titles_2017;
    • 加上一个column:ALTER TABLE 表名 ADD 列名 列种类
    • 删去一个column:ALTER TABLE 表名 DROP 列名
    • 改变column名称:ALTER TABLE 表名 CHANGE 旧列名 新列名 新列种类
    • 改变column种类:ALTER TABLE 表名 MODIFY 列名 新列种类
    • 该表table名称:ALTER TABLE 旧表名 RENAME TO 新表名

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

        分析:找到emp_no重复的记录 --> GROUP BY emp_no HAVING COUNT(emp_no)>1

                   注意点1:因为这里要保留最小的id,即需要用到MIN(id),所以一定要有GROUP BY语句

                   注意点2:在找要删除的id时,不能使用MAX(id),因为这样默认只有2条重复的语句

                   注意点3:子查询表的结果时也要加重命名

       正向思维:

                  1. 找到emp_no重复、且id最小的记录

                    SELECT emp_no, MIN(id) id FROM titles_test GROUP BY emp_no HAVING COUNT(emp_no)>1

                  2. 联合之前的表,找到需要删除的id

                    SELECT a.id FROM titles_test a, (SELECT emp_no, MIN(id) id FROM titles_test GROUP BY emp_no HAVING COUNT(emp_no)>1) b

                    WHERE a.emp_no=b.emp_no AND a.id>b.id

                 3. 根据id删除行

    DELETE FROM titles_test
    WHERE id IN 
    (SELECT * FROM
    (SELECT a.id FROM titles_test a, (SELECT emp_no, MIN(id) id FROM titles_test GROUP BY emp_no HAVING COUNT(emp_no)>1) b
    WHERE a.emp_no=b.emp_no AND a.id>b.id) c);

        逆向思维:

                  删除重复的、不是最小id的记录

                  优化:不需要HAVING子句

    DELETE FROM titles_test
    WHERE id NOT IN 
    (SELECT * FROM
    (SELECT MIN(id) id FROM titles_test GROUP BY emp_no) a); 

    7. 按照dept_no进行汇总,属于同一个部门的emp_no按照逗号进行连接,结果给出dept_no以及连接出的结果employees

        分析:知识盲区:聚合函数GROUP_CONCAT(X,Y),必须与GROUP BY联合使用

    • GROUP_CONCAT(X,Y): X必选,是需要连接的字段;Y可选,是连接的符号,缺省是默认为逗号
    SELECT dept_no, GROUP_CONCAT(emp_no) employees FROM dept_emp
    GROUP BY dept_no;

    8. 从titles表获取按照title进行分组,每组个数大于等于2,给出title以及对应的数目t

       分析:基本操作:GROUP BY 联合HAVING COUNT(*)

                  优化:可以写成HAVING t>=2

    SELECT title, COUNT(*) t FROM titles
    GROUP BY title
    HAVING COUNT(title)>=2;

    9. 将所有to_date为9999-01-01的全部更新为NULL,且 from_date更新为2001-01-01

        分析:考察UPDATE语句:用逗号连接,不是用AND

    UPDATE titles_test SET to_date = NULL, from_date = '2001-01-01'
    WHERE to_date = '9999-01-01';

    10. 对于表actor批量插入如下数据

    actor_idfirst_namelast_namelast_update
    1 PENELOPE GUINESS 2006-02-15 12:34:33
    2 NICK WAHLBERG 2006-02-15 12:34:33

       分析:要求在一条语句中执行完成

                  知识盲区:在VALUES语句后用','隔开不同的插入记录;第9题的更新多条记录也是如此

    INSERT INTO actor VALUES(1,'PENELOPE','GUINESS','2006-02-15 12:34:33'),(2,'NICK','WAHLBERG','2006-02-15 12:34:33');

    11. 对于表actor批量插入如下数据,如果数据已经存在,请忽略,不使用replace操作

          分析:知识盲区:INSERT OR IGNORE INTO 表名 VALUES(...)

    INSERT OR IGNORE INTO actor VALUES(3,'ED','CHASE','2006-02-15 12:34:33');

    12. 将employees表中的所有员工的last_name和first_name通过(')连接起来

         分析:要按SQLite数据库中的连接符 || 来连接字符

                   MySQL中可以:SELECT CONCAT(last_name,"'",first_name) name FROM employees

    SELECT last_name|| "'"|| first_name  name FROM employees;

      --代码颜色没有分好...

    13. 针对库中的所有表生成select count(*)对应的SQL语句

          对应输出应为:

    cnts
    select count(*) from employees;
    select count(*) from departments;
    select count(*) from dept_emp;
    select count(*) from dept_manager;
    select count(*) from salaries;
    select count(*) from titles;
    select count(*) from emp_bonus;

         分析:知识盲区:SQLite数据库的内置表sqlite_master

                   参考:https://blog.csdn.net/jingcheng345413/article/details/70155254          

                   表结构为:

    CREATE TABLE sqlite_master ( 
    type TEXT, 
    name TEXT, 
    tbl_name TEXT, 
    rootpage INTEGER, 
    sql TEXT 
    ); 
    

              应用场景1:查询表信息:返回的sql字段为创建此表的sql语句

    SELECT * FROM sqlite_master
    WHERE type='table' and name='表名';
    

              应用场景2:查询索引信息:返回的tbl_table为该索引所在的表,sql字段为创建此索引的sql语句

    SELECT * FROM sqlite_master
    WHERE type='index' and name='索引名';
    

    本题解法:

    SELECT 'select count(*) from ' || name || ';' cnts FROM sqlite_master 
    WHERE type='table';

    14. 针对actor表创建视图actor_name_view,只包含first_name以及last_name两列,并对这两列重新命名,first_name为first_name_v,last_name修改为last_name_v

          分析:创建视图的基础操作 CREATE VIEW 视图名 AS ...

    CREATE VIEW actor_name_view
    AS SELECT first_name first_name_v, last_name last_name_v FROM actor;

    15. 找出所有员工当前(to_date='9999-01-01')具体的薪水salary情况,对于相同的薪水只显示一次,并按照逆序显示

          分析:用DISTINCT语句去重

    SELECT DISTINCT(salary) salary FROM salaries
    WHERE to_date='9999-01-01'
    ORDER BY salary DESC;

    16. 查找员工编号emp_no为10001其自入职以来的薪水salary涨幅值growth

         分析:涨幅即为一个周期内的最大绝对值,个人觉得即使降薪,也不需要按照日期排序

                    另外,SELECT 数1- 数2 growth,后面不需要FROM salaries,否则会返回n条记录

    SELECT MAX(salary)-MIN(salary) growth FROM salaries
    WHERE emp_no=10001;

    17. 将所有获取奖金的员工(bonus表)当前的薪水(salaries表)增加10%。

          分析:用WHERE emp_no IN ... 语句,不能直接上来就等于

                     是要找两张表中emp_no一样的记录,取个交集,用表A INNER JOIN 表B ON...

                     且to_date='9999-01-01'表示当前的

    UPDATE salaries SET salary=salary*1.1
    WHERE emp_no IN
    (SELECT a.emp_no FROM salaries a 
    INNER JOIN emp_bonus b
    ON a.emp_no=b.emp_no and a.to_date='9999-01-01');

    18. 查找employees表所有emp_no为奇数,且last_name不为Mary的员工信息,并按照hire_date逆序排列

          分析:用WHERE子句查询的基本操作,奇数用%判断 num%2=1,或者位运算 num&1=1 

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

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

    film表
    字段 说明
    film_id 电影id
    title 电影名称
    description 电影描述信息
    CREATE TABLE IF NOT EXISTS film (film_id smallint(5)  NOT NULL DEFAULT '0', title varchar(255) NOT NULL, description text,PRIMARY KEY (film_id)); 
    category表
    字段 说明
    category_id 电影分类id
    name 电影分类名称
    last_update 电影分类最后更新时间
    CREATE TABLE category  (category_id  tinyint(3)  NOT NULL , name  varchar(25) NOT NULL, `last_update` timestamp, PRIMARY KEY ( category_id ));
    film_category表
    字段 说明
    film_id 电影id
    category_id 电影分类id
    last_update 电影id和分类id对应关系的最后更新时间

    CREATE TABLE film_category  (film_id  smallint(5)  NOT NULL,category_id  tinyint(3)  NOT NULL, `last_update` timestamp);

    分析:
               根据category中的name='Action',找film表中的title和description
               要通过category表的category_id --> film_category表的category_id
               通过film_category表的film_id --> film表的film_id

    SELECT title,description FROM film
    WHERE film_id IN
    (SELECT a.film_id FROM film_category a
    INNER JOIN  category b
    ON a.category_id=b.category_id and b.name='Action');

    20. 针对如下表actor结构创建索引:
    CREATE TABLE IF NOT EXISTS actor (
    actor_id smallint(5) NOT NULL PRIMARY KEY,
    first_name varchar(45) NOT NULL,
    last_name varchar(45) NOT NULL,
    last_update timestamp NOT NULL DEFAULT (datetime('now','localtime')))
    对first_name创建唯一索引uniq_idx_firstname,对last_name创建普通索引idx_lastname

       分析:知识小盲区:创建唯一索引:CREATE UNIQUE INDEX 索引名 ON 表名(列名)

                  注意一句sql结束后有分号';'

    CREATE UNIQUE INDEX uniq_idx_firstname ON actor(first_name);
    CREATE INDEX idx_lastname ON actor(last_name);

    21. 对于如下表actor,其对应的数据为:

    actor_idfirst_namelast_namelast_update
    1 PENELOPE GUINESS 2006-02-15 12:34:33
    2 NICK WAHLBERG 2006-02-15 12:34:33


     创建一个actor_name表,将actor表中的所有first_name以及last_name导入改表。 actor_name表结构如下:

    列表类型是否为NULL含义
    first_name varchar(45) not null 名字
    last_name varchar(45) not null 姓氏

       分析:从actor表中选出first_name和last_name这两列,创建为actor_name表

    CREATE TABLE actor_name AS 
    SELECT first_name, last_name FROM actor;

    22. 查找薪水涨幅超过15次的员工号emp_no以及其对应的涨幅次数t

           分析:理解题意,相同的emp_no,有一条记录,就算一次薪水涨幅变动

                     注意是COUNT(emp_no),而不是COUNT(salary)

    SELECT emp_no, COUNT(emp_no) t FROM salaries 
    GROUP BY emp_no 
    HAVING t > 15;

    23. 从titles表获取按照title进行分组,每组个数大于等于2,给出title以及对应的数目t
          注意对于重复的emp_no进行忽略

          分析:注意与22题的区别:22题是按emp_no分组后,统计每组的个数

                     这题是按照title分组后,再按一定要求统计个数,即统计同一个title中不重复的emp_no的记录个数,所以用COUNT(DISTINCT emp_no) 

    SELECT title, COUNT(DISTINCT emp_no) t FROM titles
    GROUP BY title
    HAVING t>=2;

    24. 构造一个触发器audit_log,在向employees_test表中插入一条数据的时候,触发插入相关的数据到audit中

    CREATE TABLE employees_test(ID INT PRIMARY KEY NOT NULL, NAME TEXT NOT NULL, AGE INT NOT NULL, ADDRESS CHAR(50), SALARY REAL);
    CREATE TABLE audit(EMP_no INT NOT NULL, NAME TEXT NOT NULL);

          分析:用触发器同步两张表的数据更新

                     知识盲区:

    • 构造触发器:CREATE TRIGGER 语句,用BEFORE或AFTER指定在执行后面的SQL语句之前或之后触发TRIGGER
    •                                                                  BEFORE/AFTER [INSERT/UPDATE/ADD] ON 表名1
    • BEGIN和END语句之间:写触发器执行的内容,主要用分号‘;’
    • 触发器中NEW关键字:触发事件之后对应的表名1的相关列的值;OLD关键字:触发事件之前对应的表名1的相关列的值

         注意:这里是NEW.ID,ID是employees_test表中的列名,插入到audit中作为EMP_no的值

    CREATE TRIGGER audit_log AFTER INSERT ON employees_test
    BEGIN
        INSERT INTO audit VALUES(NEW.ID, NEW.NAME);
    END;

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

          分析:就是hire_date最大的对应的值,用个子查询语句实现

    SELECT * FROM employees
    WHERE hire_date=(
        SELECT max(hire_date) FROM employees);

    26. 针对salaries表emp_no字段创建索引idx_emp_no,查询emp_no为10005, 使用强制索引

          分析:知识盲区:

    • SQLite的强制索引查询:INDEXED BY 索引名;
    • MySQL的强制索引查询使用:FORCE INDEX(索引名);
    SELECT * FROM salaries
    INDEXED BY idx_emp_no
    WHERE emp_no=10005;

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

          分析:emp_no在employees表中, 但不在dept_emp表中

    用NOT IN方法:

    SELECT * FROM employees
    WHERE emp_no NOT IN (SELECT emp_no FROM dept_emp)

    知识盲区:EXIST语句

    • EXIST/ NOT EXIST返回:根据子查询语句的空或非空结果集返回一个BOOL值
    • 外查询表的每一行都带入内查询表作为检验

    此题,在 employees表中挑选出令(SELECT emp_no FROM dept_emp WHERE emp_no = employees.emp_no)不成立的记录
    在employees表中逐条判断,有返回结果则为TRUE,否则为FALSE
    比如employees表中的第一条emp_no=10001, 那么如果dept_emp表中,没有一条记录的emp_no=10001,那么就返回True,外表查询输出该条记录;
                                                                                     如果dept_emp表中,有一条记录的emp_no=10001,就返回False,外表查询不输出

    SELECT * FROM employees
    WHERE NOT EXISTS(SELECT emp_no FROM dept_emp WHERE emp_no=employees.emp_no)

    应用场景:NOT EXIST语句防止重复插入

    参考:http://blog.sina.com.cn/s/blog_5564eb640100i42t.html

    • 从suppliers表中选取多条数据插入clients表(主键为client_id)中
    INSERT INTO clients(client_id, client_name, client_type)
    SELECT supplier_id, supplier_name, 'advertising' FROM suppliers
    WHERE NOT EXISTS (SELECT * FROM clients
                      WHERE clients.client_id = suppliers.supplier_id);
    • 插入单条数据到clients表(主键为client_id)中
    INSERT INTO clients(client_id, client_name, client_type)
    SELECT 10345, 'IBM', 'advertising' FROM dual
    WHERE NOT EXISTS (SELECT * FROM clients
                      WHERE clients.client_id = 10345);
    

      注意:这里使用虚拟表名dual,可在SELECT之后直接写需要插入的数据

      

    28. 获取所有非manager的员工emp_no   

          分析:那就是emp_no在employees表中,不在dept_manager表中

          注意:SQLite不支持MINUS,可以使用EXCEPT进行集合的差集运算

    SELECT emp_no FROM employees
    EXCEPT
    SELECT emp_no FROM dept_manager;

    SELECT结果集的列个数,对应的列种类必须相同

    • 并集:[SQL语句1] UNION [SQL语句2] 去重
    •            [SQL语句1] UNION ALL [SQL语句2] 不去重
    • 交集:[SQL语句1] INTERSECT [SQL语句2]
    • 差集:[SQL语句1] EXCEPT [SQL语句2]

          用NOT IN方法:

    SELECT emp_no FROM employees
    WHERE emp_no NOT IN (SELECT emp_no FROM dept_manager)

          用LEFT JOIN方法:注意有dept_manager.emp_no is NULL

    SELECT a.emp_no FROM employees a
    LEFT JOIN dept_manager b
    ON a.emp_no=b.emp_no
    WHERE b.emp_no is NULL

    29. 获取当前(to_date='9999-01-01')薪水第二多的员工的emp_no以及其对应的薪水salary

          分析:可以用LIMIT 1,1选择第二多的记录,但要注意去重的情况,比如第一多的有重复两个,那么LIMIT 1,1选的还是薪水第一多的记录

    原始SQL(没有考虑重复的情况)

    SELECT emp_no, salary FROM salaries
    WHERE to_date='9999-01-01'
    ORDER BY salary DESC LIMIT 1,1

    用DISTINCT去重:子查询中也增加了当前 to_date的限制

    SELECT emp_no, salary FROM salaries
    WHERE to_date='9999-01-01' AND salary=(SELECT DISTINCT(salary) FROM salaries
                                           WHERE to_date='9999-01-01'
                                           ORDER BY salary DESC LIMIT 1,1);

    用GROUP BY去重:

    SELECT emp_no,salary FROM salaries
    WHERE to_date='9999-01-01'
    GROUP BY salary
    ORDER BY salary DESC LIMIT 1,1;

    DISTINCT去重需要扫描全表,效率较低

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

          分析:同29题,用DISTINCT去重+LIMIT使用

    SELECT * FROM employees 
    WHERE hire_date=(SELECT DISTINCT(hire_date) FROM employees
                     ORDER BY hire_date DESC LIMIT 2,1);

    同样可以用GROUP BY去重:

    SELECT * FROM employees 
    WHERE hire_date
    GROUP BY hire_date
    ORDER BY hire_date DESC LIMIT 2,1;
    欲望才是原动力
  • 相关阅读:
    UGO小组冲刺第一天
    day04_07-三个函数的区别
    day06_08 字符串
    day06_07 字典操作02
    day06_06 字典操作01
    day06_05 字典
    day06_04 购物车讲解02
    day06_03 购物车讲解01
    day06_02 元组
    day06_01 上节回顾
  • 原文地址:https://www.cnblogs.com/Nooni/p/12663890.html
Copyright © 2011-2022 走看看