SQL table 看起來像試算表,但這個外觀很容易誤導。關聯模型不是靠畫面上的列順序定義資料,而是靠 schema、key、domain 與 constraint 定義什麼狀態合法。
Schema 與 instance
Schema 描述資料允許的結構;instance 是某一個時間點實際存在的 rows。
CREATE TABLE enrollment (
student_id bigint NOT NULL,
course_id text NOT NULL,
grade integer,
PRIMARY KEY (student_id, course_id),
CHECK (grade BETWEEN 0 AND 100)
);
欄位型別、複合主鍵和檢查條件屬於 schema;今天插入的資料則是 instance。
白話來說: Schema 是規則書,instance 是今天的比賽狀態。一筆資料即使欄位數量正確,只要違反 key 或 constraint,仍然不是合法資料。
Relation 沒有顯示順序
數學上的 relation 是 tuple 的集合,而集合沒有順序。因此沒有 ORDER BY 的查詢,不保證哪一列先出現。新增索引、換 execution plan 或啟用平行查詢,都可能改變顯示順序,卻沒有改變查詢語意。
SQL 和純關聯代數還有一個差異:SQL 預設採用 bag semantics,會保留重複值。
SELECT course_id FROM enrollment; -- 允許重複
SELECT DISTINCT course_id FROM enrollment; -- 去重需要額外工作
DISTINCT 可能需要排序或 hash。只有當重複本身是錯的才使用它,不要拿它掩蓋沒弄懂的 join。
SELECT 的邏輯處理順序
SQL 寫下來的順序,不等於推理結果時的順序:
FROM / JOIN → WHERE → GROUP BY → HAVING
→ SELECT → DISTINCT → ORDER BY → LIMIT
這能解釋三個常見問題:
WHERE通常不能使用SELECTalias,因為 alias 當時還不存在。- 聚合條件要放
HAVING,因為執行WHERE時還沒有 group。 ORDER BY可以使用SELECTalias,因為排序發生得比較晚。
Optimizer 可以在實體計畫裡調整 join 順序或下推 filter,但前提是結果仍符合這套邏輯順序。
NULL 是未知,不是空字串
NULL 表示資訊缺失或不適用。任何一般比較只要碰到它,結果通常是第三種邏輯值 UNKNOWN。
SELECT * FROM enrollment WHERE grade = NULL; -- 不會得到 TRUE
SELECT * FROM enrollment WHERE grade IS NULL; -- 正確
WHERE 只保留 predicate 為 TRUE 的 rows;FALSE 和 UNKNOWN 都會被移除。
白話來說:
NULL不是一個可以和另一個NULL比較的神秘值,而是「沒有答案」。兩個都不知道,SQL 就不能說它們相等。
用四筆資料驗證
INSERT INTO enrollment VALUES
(1, 'CS411', 95),
(2, 'CS411', NULL),
(3, 'CS425', 88),
(4, 'CS425', 88);
SELECT course_id, COUNT(*) AS students, COUNT(grade) AS graded
FROM enrollment
GROUP BY course_id
ORDER BY course_id;
COUNT(*) 計算 rows;COUNT(grade) 忽略 NULL。這不是語法細節,而是兩個不同問題。
複習卡
- Schema 是結構與規則;instance 是目前資料。
- Key 與 constraint 定義合法狀態。
- 沒有
ORDER BY就沒有順序保證。 - SQL 預設保留重複資料。
- 用邏輯順序推理
SELECT,不要照書寫順序猜。 NULL會產生UNKNOWN;使用IS NULL判斷。