1 示範數據庫與資料表:先看清「有甚麼資料」
重點
- 能指出數據庫、數據庫管理系統(DBMS)與資料表之間的關係。
- 能說明結構化查詢語言(SQL)的用途:用於查詢與管理數據庫內的資料。
- 在寫 SQL 之前,先理解「要查的是哪一張資料表、哪一些欄、甚麼條件」。
- 數據庫(database):用於有系統地儲存與管理大量資料,並支援查詢與更新。
- 數據庫管理系統(Database Management System, DBMS):用來建立、管理與存取數據庫的系統(例如:MySQL)。
- 結構化查詢語言(Structured Query Language, SQL):向數據庫「提問」或「下指令」的語言,例如查詢(SELECT)、新增(INSERT)、更新(UPDATE)、刪除(DELETE)。
- 資料表可理解為一張表格:每一欄(field)代表一種資料項目(例如:username),每一列代表一筆記錄。
- 寫 SQL 時,最常見的第一步是清楚知道:查詢目標是哪一張資料表,以及你需要的欄是甚麼。
本章的 users 資料表為教學用途,password 欄位以明文作示範,方便觀察查詢結果。
在真實系統中,密碼應以雜湊方式儲存(而非明文),並配合權限控制與安全設計。
示範資料表:test_db → users
在本頁所有示範與練習中,我們都以此資料表作為查詢目標。請先看清楚「欄」與「例子資料」,再開始寫 SQL。
資料表結構(Schema)
| 欄名(field) | 資料型態(示意) | 用途 | 例子 |
|---|---|---|---|
id |
INTEGER(主鍵;自動遞增) | 記錄編號,用於精確定位一筆記錄 | 1、2、3… |
username |
TEXT / VARCHAR | 用戶名稱(示範用) | admin、student、chan_it |
password |
TEXT / VARCHAR | 密碼(教學示範:明文) | password123、teacherpass |
role |
TEXT / VARCHAR | 角色/身分(示範用) | admin、user、teacher |
例子資料(節錄 8 行)
| id | username | password | role |
|---|---|---|---|
| 1 | admin | password123 | admin |
| 2 | student | password123 | user |
| 3 | teacher1 | teacherpass | teacher |
| 4 | teacher2 | teacherpass | teacher |
| 5 | teacher3 | teacherpass | teacher |
| 6 | teacher4 | teacherpass | teacher |
| 7 | teacher5 | teacherpass | teacher |
| 8 | chan_it | pw_chan | user |
模擬數據庫:即時檢視與重置
本頁的模擬器會以「模擬數據庫」方式執行 SQL。當你在 CRUD 練習中新增/更新/刪除記錄後,
可用下方工具即時查看目前 test_db 內的資料表與資料,並在需要時把示範數據庫還原至預設狀態。
SQL 是「向資料表提問」
寫 SQL 時,你其實是在回答三個核心問題: (1)查哪一張資料表、(2)取哪些欄、(3)有甚麼條件/排序/限制。 例如:
SELECT * FROM users;:取出 users 的所有欄與所有記錄。SELECT id, username FROM users ORDER BY id LIMIT 5;:只取 id 與 username,並限制只顯示前 5 筆。SELECT * FROM users WHERE role='teacher';:只取 role 為 teacher 的記錄。
2 MySQLi:連線與查詢流程
重點
- 能寫出 MySQLi 的基本流程:連線 → 查詢 → 取資料 → 輸出。
- 能分辨四個常用函式的用途、輸入與回傳:
mysqli_connect()、mysqli_query()、mysqli_fetch_assoc()、mysqli_num_rows()。 - 能在程式中處理「連線失敗/查詢失敗/結果為 0」等情況。
用途:建立與數據庫的連線。
輸入:常用 4 個參數:host、username、password、database name。
回傳:成功回傳「連線物件」,失敗回傳 false(實務應檢查)。
用途:在指定連線上執行一段 SQL。
輸入:(連線物件, SQL 字串)。
回傳:若是 SELECT 等查詢且成功,回傳「結果集」;若是 INSERT/UPDATE/DELETE 且成功,通常回傳 true;失敗回傳 false。
用途:從結果集中逐行取資料。
重點:每次呼叫只取一行,回傳「關聯式陣列」(鍵是欄名)。沒有下一行時回傳 null/false(視版本)。
用途:取得結果集的列數(即查詢結果有幾筆記錄)。
何時適用:通常用於 SELECT 的結果集;不適用於 INSERT/UPDATE/DELETE。
函式速查表:輸入與回傳
| 函式 | 需要輸入甚麼? | 回傳甚麼? | 常見用途 |
|---|---|---|---|
mysqli_connect(host, user, pass, db) |
主機、帳戶、密碼、數據庫名稱 | 連線物件/false |
建立連線,供後續查詢使用 |
mysqli_query(conn, sql) |
連線物件、SQL 字串 | 結果集/true/false |
執行 SELECT/INSERT/UPDATE/DELETE 等指令 |
mysqli_fetch_assoc(result) |
結果集 | 關聯式陣列/null |
逐行讀取結果,常與 while 迴圈配合 |
mysqli_num_rows(result) |
結果集 | 整數(列數) | 顯示「找到 X 筆資料」或判斷 0 筆結果 |
一行示範:常用 MySQLi 呼叫(每行一個示範)
以下每一行都是一個常用動作;實務上你會把它們串成「連線 → 查詢 → 取資料 → 輸出」。
$conn = mysqli_connect("localhost", "root", "", "test_db");$result = mysqli_query($conn, "SELECT * FROM users");$row = mysqli_fetch_assoc($result); // 取得下一行(關聯式陣列)$count = mysqli_num_rows($result); // 結果集列數代碼小練習:填寫 mysqli_connect() 的 4 個參數
練習目標:根據題目提供的數據庫資料,補齊連線參數,並在成功時輸出指定文字。
即時示範(模擬器):連線與查詢
示範 1:列出用戶表(前 5 筆)
示範 2:WHERE 過濾 + mysqli_num_rows
Check Point 1:MySQLi 連線與查詢流程(四選一;≥40 題)
題目以 MySQLi 函式的輸入/回傳/用途為主,並包含程式片段理解題。
Check Point 1:MySQLi 連線與查詢流程
狀態:載入中…
3 逐行解釋示範程式:查詢 users 並輸出表格
重點
- 能逐行解釋程式在做甚麼:變量、連線、查詢、迴圈、輸出。
- 能指出「結果集」需要用
mysqli_fetch_assoc()逐行讀取。 - 能說明為何輸出時要使用
htmlspecialchars()(避免把資料當成 HTML 解讀)。
示範(可試玩):逐行註解已寫在代碼內
說明已用 // 註解 方式直接寫入程式碼。你可以在模擬器內捲動查看註解,並按「執行」觀察輸出結果。
4 把用戶輸入帶入 SQL:安全寫法
重點
- 能分辨 GET 與 POST 的用途,並在合適情境選用。
- 能在伺服器端對輸入進行有效性檢驗(例如:空值、長度、允許清單)。
- 在把輸入帶入 SQL 前,先「限制」輸入範圍;不要直接相信用戶傳入的字串。
- GET:資料會出現在網址參數中(可書籤、可分享)。適合「搜尋/篩選/分頁」等不改動資料的操作。
- POST:資料不會直接顯示在網址上,適合「新增/更新/刪除」等會改動資料或較敏感的輸入。
- 安全提示:不論 GET 或 POST,伺服器端都必須重新做有效性檢驗;只靠客戶端限制並不可靠。
- 是否存在:
isset($_GET[...])/isset($_POST[...]) - 去除空白:
trim() - 空值檢查:空字串直接拒絕查詢
- 長度限制:避免極長字串造成負擔(例如 1–30 字符)
- 白名單:只允許指定值(例如 role 只能是 admin/user/teacher)
- 型態/範圍:id 必須為整數(可用
intval())
把用戶輸入帶入 SQL 時,核心原則是:
- 先做有效性檢驗(存在、空值、長度、白名單、型態)。
- 再決定合適的 SQL 寫法(下一節會比較 escape 與 Prepared Statement)。
- 最後才輸出結果,並對輸出做 HTML 轉義。
即時示範(模擬器):GET + 白名單驗證 + 查詢
Check Point 2:用戶輸入帶入 SQL(四選一;≥40 題)
題目以「填空/找出正確寫法」為主:補齊 $_GET[...]、補齊查詢條件、補齊有效性檢驗邏輯等。
Check Point 2:用戶輸入帶入 SQL
狀態:載入中…
5.1 SQL 注入防護:mysqli_real_escape_string()
重點
mysqli_real_escape_string()用於逃脫字串中的特殊字符,降低以字串拼接造成的 SQL 注入風險。- 它主要針對「把用戶輸入放進引號字串」的情況;對於 SQL 結構(例如:欄名、ORDER BY)不適用。
- 此方法仍屬「較脆弱」的防線;一般建議以 Prepared Statement 作主力。
- 你暫時未能使用 Prepared Statement,且必須以字串方式拼接 SQL。
- 用戶輸入會被放入 SQL 的引號字串內(例如:
WHERE username='...')。
- 容易遺漏:只要有一個地方忘記逃脫,就可能出現漏洞。
- 不適用於 SQL 結構:例如把用戶輸入當成欄名/表名/ORDER BY 片段,escape 亦無法令它安全。
- 穩健性不足:相比 Prepared Statement,逃脫字串更依賴「你有否正確處理所有情況」。
字串與跳脫字符
當你把用戶輸入放入 SQL 字串時,必須處理引號、反斜線等特殊字符,否則輸入可能被解讀為 SQL 結構的一部分。mysqli_real_escape_string() 會按照目前連線的規則,把輸入中的特殊字符轉成較安全的形式,從而降低注入風險。
注意:逃脫字串並非萬能;較穩健的做法是使用 Prepared Statement(參數化查詢),詳見下一節。
即時示範(模擬器):逃脫字串後再拼接 SQL
小練習(建議)
- 嘗試輸入:
' OR '1'='1,觀察 escape 後的查詢結果是否仍會「回傳大量資料」。 - 再到下一節用 Prepared Statement 以相同輸入測試,對照兩種做法的差異。
5.2 SQL 注入防護:Prepared Statement(參數化查詢)
重點
- Prepared Statement 以「佔位符 + 綁定參數」方式執行查詢,避免把用戶輸入直接拼接進 SQL。
- 參數化查詢把「SQL 結構」與「資料」分開處理,因此能有效降低 SQL 注入風險。
- 常見流程:
mysqli_prepare()→bind_param()→execute()→get_result()。
Prepared Statement 的核心思想是:
- 先把 SQL 結構固定下來(例如:
WHERE username = ?)。 - 再把用戶輸入當作「資料」綁定到佔位符,讓引擎以安全方式處理。
- 即使用戶輸入含有引號或 SQL 關鍵字,也只會被視為「字串內容」,不會改變 SQL 結構。
- 不需要你手動在每個位置做 escape,降低遺漏風險。
- 對多數情況都一致有效,特別是涉及多個條件或複雜查詢時。
- 配合有效性檢驗與權限控制,能建立更完整的防線。
即時示範(模擬器):Prepared Statement 精確查詢
6 CRUD 完整流程、錯誤處理與安全性
重點
- 能指出 CRUD 的四種操作與對應 SQL:Create/Read/Update/Delete。
- 能用同一張資料表設計完整流程:輸入 → 有效性檢驗 →(Prepared Statement)→ 執行 → 處理結果 → 輸出。
- 能處理常見例外:連線失敗、查詢失敗、結果為 0、輸入無效。
- 理解基本安全原則:伺服器端有效性檢驗、最小權限、避免回傳敏感錯誤訊息。
| 操作 | 意思 | 常見 SQL | 例子(users) |
|---|---|---|---|
| Create | 新增記錄 | INSERT |
INSERT INTO users (username, password, role) VALUES (...) |
| Read | 讀取記錄 | SELECT |
SELECT * FROM users WHERE id=? |
| Update | 更新記錄 | UPDATE |
UPDATE users SET role=? WHERE id=? |
| Delete | 刪除記錄 | DELETE |
DELETE FROM users WHERE id=? |
- 連線失敗:顯示一般化訊息(例如「系統繁忙」),並停止後續操作。
- 查詢失敗:同樣顯示一般化訊息;真實系統可把詳細錯誤寫入伺服器日誌(不直接回傳給用戶)。
- 結果為 0:清楚顯示「沒有符合資料」,避免用戶以為系統壞掉。
- 輸入無效:指出哪一項輸入不符合規格(空值、長度、格式、白名單)。
- 伺服器端有效性檢驗:客戶端限制(例如 required、maxlength)只能輔助,不能取代伺服器端檢查。
- 最小權限:用於連接數據庫的帳戶,只授予必需權限(例如只需 SELECT 就不要授予 DROP)。
- 避免敏感錯誤外洩:不要把
mysqli_error()等詳細資訊直接顯示給一般用戶。
完整流程範例(同一張 users 資料表)
1) Create:新增(INSERT)
<?php
$conn = mysqli_connect("localhost","root","","test_db");
if(!$conn){ echo "系統繁忙"; exit; }
$username = trim($_POST["username"] ?? "");
if($username === "" || strlen($username) > 30){ echo "輸入無效"; exit; }
$stmt = mysqli_prepare($conn, "INSERT INTO users (username, password, role) VALUES (?, ?, ?)");
$pw = "pw_temp";
$role = "user";
mysqli_stmt_bind_param($stmt, "sss", $username, $pw, $role);
mysqli_stmt_execute($stmt);
echo "新增完成";
?>
2) Read:讀取單筆(SELECT WHERE id=?)
<?php
$id = intval($_GET["id"] ?? 0);
if($id <= 0){ echo "輸入無效"; exit; }
$stmt = mysqli_prepare($conn, "SELECT id, username, role FROM users WHERE id = ?");
mysqli_stmt_bind_param($stmt, "i", $id);
mysqli_stmt_execute($stmt);
$result = mysqli_stmt_get_result($stmt);
if(mysqli_num_rows($result) === 0){
echo "沒有符合資料";
}else{
$row = mysqli_fetch_assoc($result);
echo htmlspecialchars($row["username"]);
}
?>
3) Update:更新(UPDATE)
<?php
$id = intval($_POST["id"] ?? 0);
$role = trim($_POST["role"] ?? "");
$allowedRole = ["admin","user","teacher"];
if($id <= 0 || !in_array($role, $allowedRole, true)){
echo "輸入無效";
exit;
}
$stmt = mysqli_prepare($conn, "UPDATE users SET role = ? WHERE id = ?");
mysqli_stmt_bind_param($stmt, "si", $role, $id);
mysqli_stmt_execute($stmt);
echo "更新完成";
?>
4) Delete:刪除(DELETE)
<?php
$id = intval($_POST["id"] ?? 0);
if($id <= 0){ echo "輸入無效"; exit; }
$stmt = mysqli_prepare($conn, "DELETE FROM users WHERE id = ?");
mysqli_stmt_bind_param($stmt, "i", $id);
mysqli_stmt_execute($stmt);
// 顯示剩餘筆數(較穩健做法:COUNT(*))
$r2 = mysqli_query($conn, "SELECT COUNT(*) AS c FROM users");
$row = mysqli_fetch_assoc($r2);
echo "剩餘 " . intval($row["c"]) . " 筆";
?>
Check Point 3:CRUD、錯誤處理與安全性(四選一;≥40 題)
題目涵蓋 CRUD、常見錯誤處理策略、最小權限與避免洩漏敏感錯誤訊息等。
Check Point 3:CRUD、錯誤處理與安全性
狀態:載入中…
Coding 代碼題(20 題;一行一題)
每題均提供:提示/核對答案/顯示參考答案。請按題目要求輸出指定文字或結構,以便「核對答案」自動評分。