面向面試的SQL

怎么講,覺得SQL是一門 “一看就懂,一學(xué)就會(huì),一做就錯(cuò)”的語言. 這回是第…三…次SQL(上過兩次SQL的課,兩個(gè)月前看過《SQL》必知必會(huì),這回刷了leetcode和??途W(wǎng)上的SQL題)。 總的來說,的確不算很難得一門語言,不過從來不用SQL自然是邊學(xué)邊忘,看書也是一目十行,每次學(xué)的時(shí)候也沒怎么付出時(shí)間和精力,所以學(xué)的一直不好,但總歸是得應(yīng)付一下將要到來的面試的。

刷了差不多三四十道SQL了,還是有些題根本無從下手,通過翻書,看答案,最后總結(jié)出來為什么無從下手的原因是“課本例子太簡單,但是給的題目卻遠(yuǎn)遠(yuǎn)難于課本”。而且子查詢真是一個(gè)套一個(gè),這套一下那套一下,這join一下,那里笛卡爾積一下,再加上平時(shí)不寫,以及平時(shí)編程的習(xí)慣,導(dǎo)致寫了很多…….1. 無法通過編譯的SQL 2. 答案錯(cuò)誤的SQL。(每次先寫Select再寫FROM 子嵌套我真是傷腦筋…..)。

密集的刷了兩天之后,裝了個(gè)SQLiteStudio搗鼓了一通之后,坑都踩了一遍之后,簡單的SQL查詢已經(jīng)不會(huì)有太大的問題了。

無法通過編譯的SQL

  1. group by + where

  2. where + aggregation 函數(shù)

    -- 同時(shí)不等號(hào)也是錯(cuò)的
    WHERE s.salary!=max(s2.salary))
    
  3. 多個(gè)join 連用(只有l(wèi)eft join能連用)

    -- 錯(cuò)
    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

    -- 錯(cuò)
    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))
    
    --- 你就說這個(gè)query你自己看不看得懂吧...不要把簡單問題復(fù)雜化
    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會(huì)把最大salary的員工信息返回 ,但實(shí)際情況是 max(salary)只有一行,而select e.* 返回的只是員工的第一行,哈士奇狗頭….

    SELECT e.*
    FROM(
     -- 天真的覺得下一行的select會(huì)把最大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. 寫法混亂,多了不需要的東西

    兩個(gè)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 )
    

以為是錯(cuò)的其實(shí)是對(duì)的

  1. 可能寫算法寫魔怔了,總覺得SELECT max(*) 返回的是一個(gè)數(shù)組,即使只有一個(gè)數(shù)也應(yīng)該是數(shù)組,不能比較大小,得和返回值的第0個(gè)比較… 果然符合大一老師說的學(xué)語言時(shí)候容易出現(xiàn)的問題, 張冠李戴…..

    -- 對(duì)
    where s.salary < (select max(salary) from salaries)
    
  2. -- 下面這個(gè)寫法...還真是對(duì)的,先記下,從sqllite的調(diào)試結(jié)果看應(yīng)該是對(duì)整個(gè)表排了序
    SELECT s.*, d.dept_no,max(salary) FROM salaries s ,  dept_manager d
    
  3. -- 這個(gè)語句語法上不是錯(cuò)的,先執(zhí)行子查詢,返回salary,然后選emp_no,但是...其實(shí)他是錯(cuò)的,因?yàn)榭偸欠祷氐谝粋€(gè)emp_no
    SELECT emp_no,(SELECT salary FROM salaries)
    FROM employees;
    

    ?

一些例題

題抄百遍,其義自見……

怎么講,上面犯的很多錯(cuò)誤,其實(shí)都是對(duì)DBMS的了解很淺顯造成的,比如right join 和right join相互嵌套(上課其實(shí)是講過為什么不能這么嵌套的原理的,可惜忘到一干二凈)

第二也是對(duì)group by 和join的原理不清楚,導(dǎo)致寫出臭臭長長的難以理解還錯(cuò)的SQL。 Inner join 是等值連接,outer join允許為空值,但其實(shí)都可以用cartitian的方式改寫,然后判斷條件寫在where 里,inner join的方式必須強(qiáng)制寫 on條件,這樣不會(huì)忘記。

補(bǔ)充關(guān)于子查詢的一些定義

  1. 在WHERE子句中使用子查詢能夠編寫出功能很強(qiáng)并且很靈活的 SQL語句。對(duì)于能嵌套的子查詢的數(shù)目沒有限制,不過在實(shí)際使用時(shí)由于 性能的限制,不能嵌套太多的子查詢。

  2. 在WHERE子句中使用子查詢(如這里所示),應(yīng)該保證SELECT語句具有與WHERE子句中相同數(shù)目的列。通常,子查詢將返回單個(gè)列并且與單個(gè)列匹配,但如果需要也可以使用多個(gè)列。

  3. 雖然子查詢一般與IN操作符結(jié)合使用,但也可以用于測(cè)試等于(=)、 不等于(<>)等。

  4. -- orders 是一個(gè)計(jì)算字段,由圓括號(hào)的子查詢簡歷的,對(duì)每個(gè)客戶執(zhí)行一次,執(zhí)行5次,因?yàn)橛形鍌€(gè)客戶。 這個(gè)子查詢,涉及外部查詢的子查詢,叫做相關(guān)子查詢。任何時(shí)候列名有多義性,就必須用這種。
    SELECT cust_name, cust_state, (SELECT COUNT(*)
                                  FROM orders
                                  WHERE customers.cust_id = orders.cust_id) AS orders
    FROM customers
    Order BY cust_name;
    
    -- 兩個(gè)SQL的區(qū)別,下面這個(gè)沒有限定表名,那么orders 的cust_id一直在和自身比較,所以總是返回訂單總數(shù)
    SELECT cust_name, cust_state, (SELECT COUNT(*)
                                  FROM orders
                                  WHERE cust_id = cust_id) AS orders
    FROM customers
    Order BY cust_name
    
  5. 選出部門中工資最多的

    -- 牛客網(wǎng)上一直過不了,不知道為啥,但我覺得??途W(wǎng)答案錯(cuò)了
    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. 選出工資比部門經(jīng)理多的

    -- 錯(cuò)誤解法,先將人和經(jīng)理對(duì)應(yīng)起來,然后再對(duì)應(yīng)工資,選出最大。
    -- 分析為什么為錯(cuò)呢? 1. 這是一個(gè)四重的連接,包括s1和s2的自聯(lián)結(jié)
    -- 經(jīng)過一步步調(diào)試,原來是忘了加上 s_todate='9999-01-01'的日期限定條件,所以做SQL一定要細(xì)心啊....
    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;
    

    ?

補(bǔ)充聯(lián)結(jié)

# 自聯(lián)結(jié)
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';

# 和這個(gè)子查詢是一樣的,單選用自聯(lián)結(jié)快于子查詢
# question2: 里面的= 和 in 會(huì)有什么差別呢?
SELECT prod_id, prod_name
FROM products
# 注釋: 一個(gè)產(chǎn)品只有一個(gè)vend_id,所以這里用= 比較而沒有用 IN 是可以的
WHERE vend_id = (SELECT vend_id
                FROM products
                WHERE prod_id='DTNTR')

解答question 2

等號(hào)是用來查找與單個(gè)值匹配的所有數(shù)據(jù);IN 是 用來查找與多個(gè)值匹配的所有數(shù)據(jù);而 LIKE用來查找與一個(gè)模式匹配的所有數(shù)據(jù)。等號(hào) 確切知道所要查找的內(nèi)容,且為單一值時(shí),可以使用等號(hào)運(yùn)算符來進(jìn)行數(shù)據(jù)比較。等號(hào)運(yùn)算符中可以使用字符串、日期或數(shù)字。IN當(dāng)確切知道所要查找的內(nèi)容,且為多個(gè)值時(shí),可以使用 IN 子句來進(jìn)行數(shù)據(jù)比較。IN 子句中也可以使用數(shù)字、字符串或日期。

-- 選出不是經(jīng)理
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;
-- <>, 錯(cuò)誤寫法
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的結(jié)果可以解釋

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 ;
也就是說,當(dāng)一個(gè)數(shù)和一個(gè)SELECT出來的子集比較時(shí),永遠(yuǎn)只和第一個(gè)數(shù)比較,所以用IN的子集操作比較妥當(dāng),不要混用,亂用

最大第二大前K大的問題

基本思路有 排序+limit, count() +where (大于xxx的個(gè)數(shù)/小于xx的個(gè)數(shù)), max+(去掉最大的子集)

-- 尋找入職第三晚的員工, 此為不去重的寫法
SELECT * FROM employees ORDER BY hire_date DESC LIMIT 2,1;
-- 尋找最晚入職的三個(gè)員工, 此為去重的寫法,應(yīng)該根據(jù)題意確定是否要用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);
-- 尋找部門工資數(shù)目上前三高的,所以加了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. 補(bǔ)充COUNT()函數(shù),使用COUNT(*)對(duì)表中行的數(shù)目進(jìn)行計(jì)數(shù),不管表列中包含的是空 值(NULL)還是非空值。使用 COUNT(column) 對(duì)特定列中具有值的行進(jìn)行計(jì)數(shù), 忽略NULL值。

  2. 補(bǔ)充 HAVING,HAVING和WHERE的差別這里有另一種理解方法,WHERE在數(shù)據(jù)分組前進(jìn)行過濾,HAVING在數(shù)據(jù)分組后進(jìn)行過濾。這是一個(gè)重要的區(qū)別,WHERE排除的行不包括在分組中。這可能會(huì)改變計(jì)算值,從而影響HAVING子句中基于這些值過濾掉的分組。

  3. 補(bǔ)充 Set Comparison >some, >all , 并不是都支持(雖然學(xué)的課本是支持的,但最近在sqlstudio中用這個(gè)不支持)

  4. 補(bǔ)充if函數(shù)

     if(t.status! = "completed", t.status, NULL)
    
  5. 補(bǔ)充 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;
    

    ?

總結(jié)

  1. 第三次SQL的學(xué)習(xí)時(shí)間大概為兩天,整個(gè)學(xué)習(xí)效果只能說 比之前好吧,總結(jié)的經(jīng)驗(yàn)是,一定要一層一層剝,從最小的SQL開始寫,然后一步步疊加,不要亂寫,也不要錯(cuò)寫,也不要冗余,尤其注重條件是否漏
  2. 目前遇到的比較難的題目是,求分組里面前K大的,注意重復(fù)值要不要處理。
  3. 分清楚 = 和 IN, 兩個(gè)的概念是不一樣的
  4. 有個(gè)很奇怪的問題還在困擾我,sql的子查詢里面的別名怎么沒有 編程里的作用域呢 ??,真是強(qiáng)行一個(gè)概念套一個(gè)概念啊,張冠李戴,有時(shí)間還是去看看吧。
  5. 如果真有人看到了這,唯一的提醒是,牛客網(wǎng)給了一些SQL的例題,但是那里面有些判題的OJ是錯(cuò)的,而且題目要求的也很模糊,LeetCode上的全部自己驗(yàn)證過了,都是對(duì)的。 以及自己電腦上可以裝一個(gè)SQLiteStudio.

有時(shí)間還需要提升的地方

  1. DBMS是如何處理 MAX()之類的計(jì)算,是整個(gè)表排序之后選第一個(gè)么? 還是說那種類似于編程的選擇排序?
  2. DBMS怎么處理GROUP BY的,也是整個(gè)表排序么? 還是只把group by值相同的放一起,然后內(nèi)部無序? 如果要求每組里面最大的可不可以用DESC關(guān)鍵字之后選第一行呢?
  3. 字符串的各種處理函數(shù)如正則還沒有學(xué)
  4. 常用的函數(shù)還沒有學(xué)
  5. DBMS的JDBC之類的還沒有接觸,以及關(guān)系型數(shù)據(jù)庫的設(shè)計(jì)要點(diǎn),各種范式
  6. 還需要學(xué)一下function怎么寫
  7. DBMS architecture 和query optimization
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容