美文网首页
面向面试的SQL

面向面试的SQL

作者: 锅锅Iris | 来源:发表于2019-01-11 19:43 被阅读0次

    怎么讲,觉得SQL是一门 “一看就懂,一学就会,一做就错”的语言. 这回是第…三…次SQL(上过两次SQL的课,两个月前看过《SQL》必知必会,这回刷了leetcode和牛客网上的SQL题)。 总的来说,的确不算很难得一门语言,不过从来不用SQL自然是边学边忘,看书也是一目十行,每次学的时候也没怎么付出时间和精力,所以学的一直不好,但总归是得应付一下将要到来的面试的。

    刷了差不多三四十道SQL了,还是有些题根本无从下手,通过翻书,看答案,最后总结出来为什么无从下手的原因是“课本例子太简单,但是给的题目却远远难于课本”。而且子查询真是一个套一个,这套一下那套一下,这join一下,那里笛卡尔积一下,再加上平时不写,以及平时编程的习惯,导致写了很多…….1. 无法通过编译的SQL 2. 答案错误的SQL。(每次先写Select再写FROM 子嵌套我真是伤脑筋…..)。

    密集的刷了两天之后,装了个SQLiteStudio捣鼓了一通之后,坑都踩了一遍之后,简单的SQL查询已经不会有太大的问题了。

    无法通过编译的SQL

    1. group by + where

    2. where + aggregation 函数

      -- 同时不等号也是错的
      WHERE s.salary!=max(s2.salary))
      
    3. 多个join 连用(只有left join能连用)

      -- 错
      SELECT DISTINCT e.last_name as last_name, e.first_name as first_name, dname.dept_name = dept_name;
      FROM (departments dname inner join dept_emp d on dname.dept_no = d.dept_no)
      right join employees e on e.emp_no = d.emp_no
      
    4. 乱用之前提到的 where+ aggregation

      -- 错
      SELECT emp_no, max(salary)
      FROM (SELECT e.emp_no as emp_no, s.salary as salary FROM salaries s inner join employees e
            , salaries s2
            ON e.emp_no = s.emp_no
            WHERE s.salary!=max(s2.salary))
      
      --- 你就说这个query你自己看不看得懂吧...不要把简单问题复杂化
      SELECT emp_no, max(salary)
      FROM (SELECT e.emp_no as emp_no, s.salary as salary FROM salaries s inner join employees e
            , salaries s2
            ON e.emp_no = s.emp_no)
            
      
    5. 天真的觉得下一行的select会把最大salary的员工信息返回 ,但实际情况是 max(salary)只有一行,而select e.* 返回的只是员工的第一行,哈士奇狗头….

      SELECT e.*
      FROM(
       -- 天真的觉得下一行的select会把最大salary的员工信息返回 
      SELECT e.*,max(s.salary)
      FROM employees e INNER JOIN salaries s on e.emp_no=s.emp_no AND s.to_date='9999-01-01'
      WHERE s.salary NOT IN (SELECT max(salary)
                               FROM salaries))
                            
      
    6. 写法混乱,多了不需要的东西

      两个d是怎么回事? 第二行的d根本没用到啊,而且做了cartitian product后,e.emp_no多了好几行

      SELECT e.emp_no
      FROM employees e, dept_manager d
      WHERE e.emp_no NOT IN (SELECT e2.emp_no FROM dept_manager d inner join employees e2 on e2.emp_no=d.emp_no )
      

      正确写法为

      SELECT emp_no
      FROM employees
      WHERE emp_no NOT IN (SELECT e2.emp_no FROM dept_manager d inner join employees e2 on e2.emp_no=d.emp_no )
      

    以为是错的其实是对的

    1. 可能写算法写魔怔了,总觉得SELECT max(*) 返回的是一个数组,即使只有一个数也应该是数组,不能比较大小,得和返回值的第0个比较… 果然符合大一老师说的学语言时候容易出现的问题, 张冠李戴…..

      -- 对
      where s.salary < (select max(salary) from salaries)
      
    2. -- 下面这个写法...还真是对的,先记下,从sqllite的调试结果看应该是对整个表排了序
      SELECT s.*, d.dept_no,max(salary) FROM salaries s ,  dept_manager d
      
    3. -- 这个语句语法上不是错的,先执行子查询,返回salary,然后选emp_no,但是...其实他是错的,因为总是返回第一个emp_no
      SELECT emp_no,(SELECT salary FROM salaries)
      FROM employees;
      

    一些例题

    题抄百遍,其义自见……

    怎么讲,上面犯的很多错误,其实都是对DBMS的了解很浅显造成的,比如right join 和right join相互嵌套(上课其实是讲过为什么不能这么嵌套的原理的,可惜忘到一干二净)

    第二也是对group by 和join的原理不清楚,导致写出臭臭长长的难以理解还错的SQL。 Inner join 是等值连接,outer join允许为空值,但其实都可以用cartitian的方式改写,然后判断条件写在where 里,inner join的方式必须强制写 on条件,这样不会忘记。

    补充关于子查询的一些定义

    1. 在WHERE子句中使用子查询能够编写出功能很强并且很灵活的 SQL语句。对于能嵌套的子查询的数目没有限制,不过在实际使用时由于 性能的限制,不能嵌套太多的子查询。

    2. 在WHERE子句中使用子查询(如这里所示),应该保证SELECT语句具有与WHERE子句中相同数目的列。通常,子查询将返回单个列并且与单个列匹配,但如果需要也可以使用多个列。

    3. 虽然子查询一般与IN操作符结合使用,但也可以用于测试等于(=)、 不等于(<>)等。

    4. -- orders 是一个计算字段,由圆括号的子查询简历的,对每个客户执行一次,执行5次,因为有五个客户。 这个子查询,涉及外部查询的子查询,叫做相关子查询。任何时候列名有多义性,就必须用这种。
      SELECT cust_name, cust_state, (SELECT COUNT(*)
                                    FROM orders
                                    WHERE customers.cust_id = orders.cust_id) AS orders
      FROM customers
      Order BY cust_name;
      
      -- 两个SQL的区别,下面这个没有限定表名,那么orders 的cust_id一直在和自身比较,所以总是返回订单总数
      SELECT cust_name, cust_state, (SELECT COUNT(*)
                                    FROM orders
                                    WHERE cust_id = cust_id) AS orders
      FROM customers
      Order BY cust_name
      
    5. 选出部门中工资最多的

      -- 牛客网上一直过不了,不知道为啥,但我觉得牛客网答案错了
      SELECT DISTINCT d1.dept_no,d1.emp_no,s1.salary
      FROM dept_emp d1, salaries s1
      WHERE d1.emp_no = s1.emp_no and s1.salary >= (SELECT max(s.salary)
                       FROM salaries s ,dept_emp d
                       WHERE s.emp_no =d.emp_no and d.dept_no = d1.dept_no and s.to_date = '9999-01-01') AND s1.to_date = '9999-01-01
      
    6. 选出工资比部门经理多的

      -- 错误解法,先将人和经理对应起来,然后再对应工资,选出最大。
      -- 分析为什么为错呢? 1. 这是一个四重的连接,包括s1和s2的自联结
      -- 经过一步步调试,原来是忘了加上 s_todate='9999-01-01'的日期限定条件,所以做SQL一定要细心啊....
      SELECT *
        FROM (
                 SELECT DISTINCT d.emp_no AS emp_no,
                        ma.emp_no AS manager_no,
                        s.salary AS emp_salary,
                        s2.salary AS manager_salary
                   FROM dept_emp d
                        INNER JOIN
                        dept_manager ma ON d.dept_no = ma.dept_no,
                        salaries s ,
                        salaries s2
                  WHERE s2.emp_no = ma.emp_no AND s.emp_no = d.emp_no and d.dept_no = ma.dept_no 
             )
       WHERE emp_salary>manager_salary;
      

    补充联结

    # 自联结
    SELECT p1.prod_id, p1.prod_name
    FROM products AS p1, products AS p2
    WHERE p1.vend_id = p2.vend_id AND p2.prod_id = 'DTNTR';
    
    # 和这个子查询是一样的,单选用自联结快于子查询
    # question2: 里面的= 和 in 会有什么差别呢?
    SELECT prod_id, prod_name
    FROM products
    # 注释: 一个产品只有一个vend_id,所以这里用= 比较而没有用 IN 是可以的
    WHERE vend_id = (SELECT vend_id
                    FROM products
                    WHERE prod_id='DTNTR')
    

    解答question 2

    等号是用来查找与单个值匹配的所有数据;IN 是 用来查找与多个值匹配的所有数据;而 LIKE用来查找与一个模式匹配的所有数据。等号 确切知道所要查找的内容,且为单一值时,可以使用等号运算符来进行数据比较。等号运算符中可以使用字符串、日期或数字。IN当确切知道所要查找的内容,且为多个值时,可以使用 IN 子句来进行数据比较。IN 子句中也可以使用数字、字符串或日期。

    -- 选出不是经理
    SELECT DISTINCT e.emp_no
    FROM employees e, dept_manager d
    WHERE e.emp_no NOT IN (SELECT e2.emp_no FROM dept_manager d inner join employees e2 on e2.emp_no=d.emp_no )
    ORDER BY e.emp_no ASC;
    -- <>, 错误写法
    SELECT DISTINCT e.emp_no
    FROM employees e
    WHERE e.emp_no <> (SELECT e2.emp_no FROM dept_manager d inner join employees e2 on e2.emp_no=d.emp_no )
    ORDER BY e.emp_no ASC;
    

    下面一些query的结果可以解释

    SELECT emp_no FROM employees
    
    emp_no
    SELECT e2.emp_no FROM employees e2 inner join dept_manager d on e2.emp_no=d.emp_no
    
    e2.emp_no
    SELECT  emp_no, emp_no = (SELECT e2.emp_no FROM employees e2 inner join dept_manager d on e2.emp_no=d.emp_no) AS a, (SELECT e2.emp_no FROM employees e2 inner join dept_manager d on e2.emp_no=d.emp_no) as e2_emp_no
    FROM employees ;
    
    也就是说,当一个数和一个SELECT出来的子集比较时,永远只和第一个数比较,所以用IN的子集操作比较妥当,不要混用,乱用

    最大第二大前K大的问题

    基本思路有 排序+limit, count() +where (大于xxx的个数/小于xx的个数), max+(去掉最大的子集)

    -- 寻找入职第三晚的员工, 此为不去重的写法
    SELECT * FROM employees ORDER BY hire_date DESC LIMIT 2,1;
    
    -- 寻找最晚入职的三个员工, 此为去重的写法,应该根据题意确定是否要用distinct
    SELECT * 
    FROM employees
    WHERE hire_date > (SELECT DISTINCT hire_date 
                       FROM employees 
                       ORDER BY hire_date ASC
                       LIMIT 2,1)
    
    SELECT * 
    FROM employees e1
    WHERE 2=(SELECT COUNT(*) 
             FROM employees e2 
             WHERE e1.hire_date < e2.hire_date);
    
    -- 寻找部门工资数目上前三高的,所以加了DISTINCT, 
    -- 不能用GROUP BY + LIMIT
    SELECT d.name AS department, e.name AS employee, e.salary AS salary
    FROM employee e INNER JOIN Department d on d.id = e.departmentID
    WHERE (SELECT COUNT(DISTINCT e2.salary) FROM employee e2 WHERE e2.salary>e.salary AND e2.departmentId = e.departmentId) <=2
    ORDER BY department ASC, salary DESC
    
    1. 补充COUNT()函数,使用COUNT(*)对表中行的数目进行计数,不管表列中包含的是空 值(NULL)还是非空值。使用 COUNT(column) 对特定列中具有值的行进行计数, 忽略NULL值。

    2. 补充 HAVING,HAVING和WHERE的差别这里有另一种理解方法,WHERE在数据分组前进行过滤,HAVING在数据分组后进行过滤。这是一个重要的区别,WHERE排除的行不包括在分组中。这可能会改变计算值,从而影响HAVING子句中基于这些值过滤掉的分组。

    3. 补充 Set Comparison >some, >all , 并不是都支持(虽然学的课本是支持的,但最近在sqlstudio中用这个不支持)

    4. 补充if函数

       if(t.status! = "completed", t.status, NULL)
      
    5. 补充 if…else

      SELECT OrderID, Quantity,
      CASE
          WHEN Quantity > 30 THEN "The quantity is greater than 30"
          WHEN Quantity = 30 THEN "The quantity is 30"
          ELSE "The quantity is under 30"
      END AS QuantityText
      FROM OrderDetails;
      

    总结

    1. 第三次SQL的学习时间大概为两天,整个学习效果只能说 比之前好吧,总结的经验是,一定要一层一层剥,从最小的SQL开始写,然后一步步叠加,不要乱写,也不要错写,也不要冗余,尤其注重条件是否漏
    2. 目前遇到的比较难的题目是,求分组里面前K大的,注意重复值要不要处理。
    3. 分清楚 = 和 IN, 两个的概念是不一样的
    4. 有个很奇怪的问题还在困扰我,sql的子查询里面的别名怎么没有 编程里的作用域呢 😂,真是强行一个概念套一个概念啊,张冠李戴,有时间还是去看看吧。
    5. 如果真有人看到了这,唯一的提醒是,牛客网给了一些SQL的例题,但是那里面有些判题的OJ是错的,而且题目要求的也很模糊,LeetCode上的全部自己验证过了,都是对的。 以及自己电脑上可以装一个SQLiteStudio.

    有时间还需要提升的地方

    1. DBMS是如何处理 MAX()之类的计算,是整个表排序之后选第一个么? 还是说那种类似于编程的选择排序?
    2. DBMS怎么处理GROUP BY的,也是整个表排序么? 还是只把group by值相同的放一起,然后内部无序? 如果要求每组里面最大的可不可以用DESC关键字之后选第一行呢?
    3. 字符串的各种处理函数如正则还没有学
    4. 常用的函数还没有学
    5. DBMS的JDBC之类的还没有接触,以及关系型数据库的设计要点,各种范式
    6. 还需要学一下function怎么写
    7. DBMS architecture 和query optimization

    相关文章

      网友评论

          本文标题:面向面试的SQL

          本文链接:https://www.haomeiwen.com/subject/xxlfdqtx.html