py程式設計(三)課程網站
第 3 週 · 單元 1-3

資料庫入門:打開吃貨評分站的資料庫,用 SQL 算出排行榜

網站的資料不放在程式裡,而是放在資料庫。今天親手查詢、新增、統計,下週 AI 寫的 Flask 程式,你才看得懂它在對資料庫做什麼。

W3SQLSQLite

本週結束時你會

今天的時間安排

0:00–0:15安裝 DB Browser for SQLite、打開 foodie.db
0:15–0:30觀念:資料表、欄位、主鍵、外鍵
0:30–1:05實作 1【跟著做】查詢:SELECT、WHERE、ORDER BY、COUNT
1:05–1:15休息
1:15–1:50實作 2【跟著做】JOIN 與 GROUP BY:算出排行榜
1:50–2:15實作 3【自己做】新增資料、讓資料庫擋下錯誤;請 AI 寫 SQL 並對答案
2:15–2:30把自己的 PROJECT.md 資料表寫成建表 SQL,本週檢核

準備:打開資料庫

  1. 到 sqlitebrowser.org 下載 DB Browser for SQLite(Windows 選 Standard installer,Mac 選 Intel 或 Apple Silicon 版)。
  2. 從課程資源下載 foodie.db,放進 程式設計三/w03/。
  3. DB Browser →「開啟資料庫」→ 選 foodie.db。上方分頁:「資料庫結構」看有哪些表,「瀏覽資料」看內容,「執行 SQL」寫查詢。
foodie.db
├─ shops     店家 12 間   id · name · category · address · created_at
└─ reviews   評論 49 則   id · shop_id → shops.id · nickname · rating · comment · created_at

主鍵(id)是每一筆資料的身分證號碼,不會重複。外鍵(shop_id)記錄「這則評論屬於哪一間店」,指向 shops 表的 id。一間店可以有很多則評論,這叫做一對多。

不能安裝軟體的備案

在終端機輸入 python -m sqlite3 foodie.db(Python 3.12 以上內建),就能直接輸入 SQL。結果會以 Python 格式顯示,例如 ('縮排鍋燒意麵',)。

實作 1:查詢【跟著做】

在「執行 SQL」分頁輸入,按 Ctrl+Enter 執行。每一題都先猜結果,再執行。

-- 1. 看所有店家
SELECT * FROM shops;

-- 2. 只看麵類,只要店名
SELECT name FROM shops WHERE category = '麵';

-- 3. 派森牛肉湯(id = 1)最新的 2 則評論
SELECT nickname, rating, comment, created_at
FROM reviews
WHERE shop_id = 1
ORDER BY created_at DESC
LIMIT 2;

-- 4. 五顆星的評論有幾則
SELECT COUNT(*) FROM reviews WHERE rating = 5;

對答案

題正確結果
112 間店
2縮排鍋燒意麵、變數擔仔麵、函式炒鱔魚
3小林 4 分「價格合理」、豆豆 5 分「排隊也值得」
418

第 3 題常見錯誤是忘了 DESC,結果拿到最舊的兩則。讓學生自己發現「最新」要由新排到舊。

實作 2:JOIN 與 GROUP BY,算出排行榜【跟著做】

平均分數在 reviews 表,店名在 shops 表。要把兩張表接起來:

-- 5. 各類別有幾間店
SELECT category, COUNT(*) AS 店家數
FROM shops
GROUP BY category
ORDER BY 店家數 DESC;

-- 6. 排行榜:每間店的平均分數與評論數
SELECT s.name, s.category,
       ROUND(AVG(r.rating), 1) AS 平均分數,
       COUNT(r.id)             AS 評論數
FROM shops s
LEFT JOIN reviews r ON r.shop_id = s.id
GROUP BY s.id
ORDER BY AVG(r.rating) IS NULL, AVG(r.rating) DESC, 評論數 DESC;

對答案:排行榜前 6 名與最後一名

名次店家平均評論數說明
1派森牛肉湯4.76兩間都是 4.67 分,評論數多的排前面
2函式炒鱔魚4.73
3布林豆花4.65
4字典虱目魚粥4.56同為 4.5 分
5迴圈碗粿4.54
6例外蝦捲4.47評論最多,但平均較低
12模組肉燥飯(空白)0沒有評論,排最後

如果用 JOIN(不加 LEFT)

沒有評論的「模組肉燥飯」會直接消失,排行榜只剩 11 間店。

LEFT JOIN

保留左邊(shops)的每一間店,即使右邊(reviews)沒有對應的資料。

請學生把 LEFT JOIN 改成 JOIN 執行一次,親眼看到少了一間店。這是下週排行榜頁面最容易出錯的地方。

實作 3:新增資料,讓資料庫擋下錯誤【自己做】

-- 先開啟外鍵檢查(SQLite 預設是關閉的!)
PRAGMA foreign_keys = ON;

-- 7. 替模組肉燥飯(id = 12)新增一則評論
INSERT INTO reviews (shop_id, nickname, rating, comment)
VALUES (12, '你的暱稱', 5, '肉燥很香');

-- 8. 試試看:評分 6 分
INSERT INTO reviews (shop_id, nickname, rating) VALUES (1, '測試', 6);

-- 9. 試試看:不存在的店家 id = 99
INSERT INTO reviews (shop_id, nickname, rating) VALUES (99, '測試', 5);
題應該看到是誰擋下的
7成功;再執行一次排行榜,模組肉燥飯有分數了—
8CHECK constraint failed建表時的 CHECK (rating BETWEEN 1 AND 5)
9FOREIGN KEY constraint failed外鍵 REFERENCES shops(id)

練習完記得復原:DB Browser 上方按「撤銷變更」,資料庫會回到原本的 49 則評論,否則下週對答案的數字會不一樣。

請 AI 寫 SQL,再對答案

提示詞 Antigravity
我有一個 SQLite 資料庫,結構如下:
shops(id, name, category, address, created_at)
reviews(id, shop_id → shops.id, nickname, rating 1–5, comment, created_at)

請各寫一段 SQL,並逐行解釋:
1. 評論數最多的前 3 間店(店名、評論數;同數量時依店名排序)
2. 找出完全沒有評論的店家
3. 「小吃」類所有評論的平均分數,四捨五入到小數 2 位
題正確結果
1例外蝦捲 7、字典虱目魚粥 6、派森牛肉湯 6
2模組肉燥飯(若你在實作 3 新增了評論且沒有復原,結果會是空的)
34.53

AI 的答案和上表不同時,不要直接相信任何一方:把 AI 的 SQL 貼進 DB Browser 執行,找出差在哪裡。

延伸:把你的 PROJECT.md 變成建表 SQL

提示詞 Antigravity
請先讀 PROJECT.md 的「3. 資料表」。
請寫出 SQLite 的建表 SQL:標出主鍵、必填欄位(NOT NULL)、外鍵(REFERENCES),並用 CHECK 限制不合理的值。
再寫 3 筆 INSERT 範例資料,以及一段我可以用來檢查關聯是否正確的 SELECT ... JOIN。

在 DB Browser 建立新資料庫並執行。三筆資料都能 JOIN 出來,資料表設計就通過了。

本週檢核

第 4 週預告:示範專案開工。今天的排行榜 SQL,下週會原封不動出現在 Flask 程式裡。