概述#
如果你的履歷上有資料庫相關經驗,面試中可能會被問到這類問題。本章涵蓋關鍵概念及常見的查詢撰寫技巧。
SQL 有多種語法變體,本書範例以 Microsoft SQL Server 為基準進行測試。面試中遇到小幅語法差異是正常的。
SQL 語法與變體(SQL Syntax and Variations)#
Explicit Join 和 Implicit Join 兩者等效,本書統一使用 Explicit Join:
-- Explicit Join
SELECT CourseName, TeacherName
FROM Courses INNER JOIN Teachers
ON Courses.TeacherID = Teachers.TeacherID
-- Implicit Join(等效)
SELECT CourseName, TeacherName
FROM Courses, Teachers
WHERE Courses.TeacherID = Teachers.TeacherID正規化 vs. 反正規化(Denormalized vs. Normalized Databases)#
- Normalized(正規化):設計以最小化冗餘資料為目標。例如
Courses表透過TeacherID外鍵連結Teachers表,每位老師的資訊只儲存一次。缺點是許多查詢需要昂貴的 JOIN 操作。 - Denormalized(反正規化):透過儲存冗餘資料來加速讀取。例如直接在
Courses表中儲存TeacherName,避免 JOIN。反正規化常用於建立高擴展性的系統。
在大型、高流量系統中,JOIN 操作代價很高,通常需要採用反正規化或 NoSQL 來提升效能。
SQL 語句(SQL Statements)#
資料庫結構範例#
Courses: CourseID*, CourseName, TeacherID
Teachers: TeacherID*, TeacherName
Students: StudentID*, StudentName
StudentCourses: CourseID*, StudentID*Query 1:學生選課數量#
取得所有學生及其選修課程數量:
-- 錯誤版本(有三個問題)
SELECT Students.StudentName, count(*)
FROM Students INNER JOIN StudentCourses
ON Students.StudentID = StudentCourses.StudentID
GROUP BY Students.StudentID問題分析:
- 未選課的學生會被排除(應使用
LEFT JOIN) count(*)會把未選課學生算成 1(應改為count(StudentCourses.CourseID))GROUP BY StudentID後,StudentName在SELECT中必須使用 aggregate function
解法一:用子查詢包裝
SELECT StudentName, Students.StudentID, Cnt
FROM (
SELECT Students.StudentID, count(StudentCourses.CourseID) as [Cnt]
FROM Students LEFT JOIN StudentCourses
ON Students.StudentID = StudentCourses.StudentID
GROUP BY Students.StudentID
) T INNER JOIN Students on T.studentID = Students.StudentID解法二:將 StudentName 加入 GROUP BY
SELECT StudentName, Students.StudentID, count(StudentCourses.CourseID) as [Cnt]
FROM Students LEFT JOIN StudentCourses
ON Students.StudentID = StudentCourses.StudentID
GROUP BY Students.StudentID, Students.StudentName解法三:對 StudentName 使用 aggregate function
SELECT max(StudentName) as [StudentName], Students.StudentID,
count(StudentCourses.CourseID) as [Count]
FROM Students LEFT JOIN StudentCourses
ON Students.StudentID = StudentCourses.StudentID
GROUP BY Students.StudentIDQuery 2:每位老師教導的學生數#
取得所有老師及其學生數量,若同一位老師在兩門課教同一位學生,需重複計算。結果按學生數降序排列:
-- 步驟一:取得每位老師對應的 course-student 數量
SELECT TeacherID, count(StudentCourses.CourseID) AS [Number]
FROM Courses INNER JOIN StudentCourses
ON Courses.CourseID = StudentCourses.CourseID
GROUP BY Courses.TeacherID
-- 完整查詢(包含沒有開課的老師,以 0 表示)
SELECT TeacherName, isnull(StudentSize.Number, 0)
FROM Teachers LEFT JOIN
(SELECT TeacherID, count(StudentCourses.CourseID) AS [Number]
FROM Courses INNER JOIN StudentCourses
ON Courses.CourseID = StudentCourses.CourseID
GROUP BY Courses.TeacherID) StudentSize
ON Teachers.TeacherID = StudentSize.TeacherID
ORDER BY StudentSize.Number DESC使用
isnull()將 NULL 值轉換為 0,確保沒有學生的老師也能正確顯示。
小型資料庫設計(Small Database Design)#
設計資料庫時,方法類似於物件導向設計。
Step 1:釐清模糊之處(Handle Ambiguity)#
資料庫設計題通常有歧義,必須在動手前確認需求。
例如被要求設計公寓租賃仲介的系統時,需要確認:
- 仲介有幾個據點?
- 一個人可以租多間公寓嗎?
- 非常罕見的情況要處理到什麼程度?
Step 2:定義核心物件(Define the Core Objects)#
找出系統的核心物件,每個物件通常對應一張資料表。例如公寓租賃仲介的核心物件:
Property、Building、Apartment、Tenant、Manager
Step 3:分析關係(Analyze Relationships)#
確認各資料表之間的關係:一對多或多對多?
若 Buildings 對 Apartments 是一對多關係:
| Apartments | Buildings | ||
|---|---|---|---|
| ApartmentID | int | BuildingID | int |
| ApartmentAddress | varchar(100) | BuildingName | varchar(100) |
| BuildingID | int | BuildingAddress | varchar(500) |
若允許一個租客租多間公寓(多對多關係),需要一張中介表:
| TenantApartments | Apartments | Tenants | |||
|---|---|---|---|---|---|
| TenantID | int | ApartmentID | int | TenantID | int |
| ApartmentID | int | ApartmentAddress | varchar(500) | TenantName | varchar(100) |
| BuildingID | int | TenantAddress | varchar(500) |
Step 4:補充細節(Investigate Actions)#
走過系統中常見的操作,確認需要哪些額外的資料欄位。例如:租約期限、搬出紀錄、租金付款等,每一種操作都可能需要新的資料表或欄位。
大型資料庫設計(Large Database Design)#
在大型、可擴展的資料庫中,JOIN 操作通常很慢。因此必須對資料進行反正規化,將常用資料複製到多張資料表中,以空間換取查詢速度。
反正規化會引入資料一致性的挑戰——同一筆資料存在多處,更新時需要同步所有副本。這是大型系統設計中必須權衡的取捨。
面試題目列表#
以下題目 1-3 使用下列資料庫結構(每間公寓可有多位租客,每位租客可租多間公寓,每間公寓屬於一棟樓,每棟樓屬於一個社區):
| Apartments | Buildings | Requests | |||
|---|---|---|---|---|---|
| AptID | int | BuildingID | int | RequestID | int |
| UnitNumber | varchar(10) | ComplexID | int | Status | varchar(100) |
| BuildingID | int | BuildingName | varchar(100) | AptID | int |
| Address | varchar(500) | Description | varchar(500) |
| Complexes | AptTenants | Tenants | |||
|---|---|---|---|---|---|
| ComplexID | int | TenantID | int | TenantID | int |
| ComplexName | varchar(100) | AptID | int | TenantName | varchar(100) |
| 題號 | 題目 |
|---|---|
| 14.1 | Multiple Apartments:查詢租了不只一間公寓的所有租客 |
| 14.2 | Open Requests:查詢所有樓棟及其 Open 狀態的請求數量 |
| 14.3 | Close All Requests:關閉 #11 棟樓所有公寓的所有請求 |
| 14.4 | Joins:解釋各種 JOIN 類型及其適用情境 |
| 14.5 | Denormalization:什麼是反正規化?說明其優缺點 |
| 14.6 | Entity-Relationship Diagram:為公司、人員、職業人士畫出 ER 圖 |
| 14.7 | Design Grade Database:設計學生成績資料庫,並查詢前 10% 的學生 |