Table of Contents

在前兩篇文章裡,用 模擬的辦公室咖啡廳營運資料 來練習 SQL 語法之後,現在我們應該已經對查詢資料庫的思維,有比較直覺的認識了。那今天我們就再練習一組題目,讓腦中的那些 SQL 語法慢慢沉到直覺裡吧。

這次我們用叫車服務app當作例子,把乘客、司機,還有其他與每趟車程有關的資訊都寫進資料庫後,就可以組合一套想像中的簡易查詢練習。

練習的環境

把以下資料貼進平日習慣的環境中練習,或是到我預先建立好的 SQL Fiddle 線上環境: https://sqlfiddle.com/sqlite/online-compiler?id=6819b071-c57e-4365-a8f3-332f0eba8f47

新建資料表

首先先建立這次所需要的空白資料表,比如司機、車輛、乘客、行程、還有付款紀錄、評價、以及優惠碼…等

/* 司機 */
CREATE TABLE driver (
did     TEXT PRIMARY KEY,
dname   TEXT,
rating  REAL,         -- 平均星等
city    TEXT
);


/* 車輛 (一位司機一台車) */
CREATE TABLE vehicle (
vid      TEXT PRIMARY KEY,
did      TEXT UNIQUE,
make     TEXT,
model    TEXT,
year     INTEGER,
capacity INTEGER,
FOREIGN KEY (did) REFERENCES driver(did)
);


/* 乘客 */
CREATE TABLE rider (
rid        TEXT PRIMARY KEY,
rname      TEXT,
signup_dt  TEXT,      -- yyyy-mm-dd
city       TEXT
);


/* 行程 */
CREATE TABLE trip (
tid          TEXT PRIMARY KEY,
rid          TEXT,
did          TEXT,
pickup_time  TEXT,    -- yyyy-mm-dd hh:mm
dropoff_time TEXT,    -- yyyy-mm-dd hh:mm
distance_km  REAL,
fare         REAL,
promo_code   TEXT,
FOREIGN KEY (rid) REFERENCES rider(rid),
FOREIGN KEY (did) REFERENCES driver(did)
);


/* 付款 */
CREATE TABLE payment (
pid      TEXT PRIMARY KEY,
tid      TEXT,
paid_at  TEXT,        -- yyyy-mm-dd hh:mm
method   TEXT,        -- Card / Cash
status   TEXT,        -- Paid / Failed
amount   REAL,
FOREIGN KEY (tid) REFERENCES trip(tid)
);


/* 評價 (乘客對司機打分) */
CREATE TABLE feedback (
fid      TEXT PRIMARY KEY,
tid      TEXT UNIQUE,
stars    INTEGER,     -- 1~5
comment  TEXT,
FOREIGN KEY (tid) REFERENCES trip(tid)
);


/* 優惠碼 */
CREATE TABLE promo (
code        TEXT PRIMARY KEY,
description TEXT,
discount_pct INTEGER,
start_dt    TEXT,
end_dt      TEXT
);

填充模擬的資料

有了空白資料表後,將接下來練習要用的模擬資料給加進去

/* driver */
INSERT INTO driver VALUES
('d001','Alice' ,4.90,'Taipei'),
('d002','Brian' ,4.70,'Taipei'),
('d003','Cathy' ,4.30,'Taichung'),
('d004','David' ,4.80,'Kaohsiung'),
('d005','Ethan' ,4.10,'Taipei'),
('d006','Flora' ,4.95,'Taichung');


/* vehicle */
INSERT INTO vehicle VALUES
('v001','d001','Toyota' ,'Altis' ,2021,4),
('v002','d002','Honda'  ,'Civic' ,2019,4),
('v003','d003','Nissan' ,'Leaf'  ,2020,4),
('v004','d004','Tesla'  ,'Model 3',2022,4),
('v005','d005','Hyundai','Tucson',2016,5),
('v006','d006','Mazda'  ,'CX-5'  ,2023,4);


/* rider */
INSERT INTO rider VALUES
('r001','Gina' , '2024-01-12','Taipei'),
('r002','Henry','2023-11-03','Taipei'),
('r003','Irene','2025-02-01','Taichung'),
('r004','Jack' ,'2022-08-25','Kaohsiung'),
('r005','Kelly','2024-03-05','Taipei'),
('r006','Leo'  ,'2023-06-14','Taichung'),
('r007','Mia'  ,'2024-01-09','Taipei'),
('r008','Nick' ,'2023-05-01','Kaohsiung');


/* promo */
INSERT INTO promo VALUES
('SAVE10','10% off any ride',10,'2025-01-15','2025-02-15'),
('MORNING5','5% off before 10AM',5,'2025-01-01','2025-12-31');


/* trip */
INSERT INTO trip VALUES
('t101','r001','d001','2025-02-18 09:10','2025-02-18 09:40',12.5,300,'MORNING5'),
('t102','r002','d001','2025-02-18 10:20','2025-02-18 10:55',15.0,360,NULL),
('t103','r003','d002','2025-02-18 15:05','2025-02-18 15:35',14.2,340,NULL),
('t104','r001','d002','2025-02-19 08:05','2025-02-19 08:25', 8.8,220,'MORNING5'),
('t105','r004','d003','2025-02-19 09:50','2025-02-19 10:30',22.0,480,'SAVE10'),
('t106','r005','d002','2025-02-19 16:15','2025-02-19 16:45',11.7,280,NULL),
('t107','r006','d004','2025-02-20 18:20','2025-02-20 18:55',18.3,420,NULL),
('t108','r007','d004','2025-02-20 09:40','2025-02-20 10:05', 9.1,230,'MORNING5'),
('t109','r008','d005','2025-02-20 19:05','2025-02-20 19:45',20.4,500,NULL),
('t110','r002','d005','2025-02-21 14:25','2025-02-21 14:55',12.0,300,'SAVE10'),
('t111','r003','d006','2025-02-21 11:10','2025-02-21 11:40',13.3,310,NULL),
('t112','r001','d006','2025-02-22 08:30','2025-02-22 08:55',10.5,250,'MORNING5'),
('t113','r005','d001','2025-02-22 21:15','2025-02-22 21:45',12.8,320,NULL),
('t114','r006','d003','2025-02-22 07:50','2025-02-22 08:20',14.0,330,'MORNING5'),
('t115','r007','d002','2025-02-23 09:05','2025-02-23 09:35',13.6,330,'MORNING5');


/* payment */
INSERT INTO payment VALUES
('p901','t101','2025-02-18 09:41','Card','Paid',285),
('p902','t102','2025-02-18 10:56','Card','Paid',360),
('p903','t103','2025-02-18 15:36','Cash','Paid',340),
('p904','t104','2025-02-19 08:26','Card','Paid',209),
('p905','t105','2025-02-19 10:31','Card','Paid',432),
('p906','t106','2025-02-19 16:46','Cash','Paid',280),
('p907','t107','2025-02-20 18:56','Card','Paid',420),
('p908','t108','2025-02-20 10:06','Card','Paid',219),
('p909','t109','2025-02-20 19:46','Cash','Failed',0),
('p910','t110','2025-02-21 14:56','Card','Paid',270),
('p911','t111','2025-02-21 11:41','Card','Paid',310),
('p912','t112','2025-02-22 08:56','Card','Paid',238),
('p913','t113','2025-02-22 21:46','Cash','Paid',320),
('p914','t114','2025-02-22 08:21','Card','Paid',314),
('p915','t115','2025-02-23 09:36','Card','Paid',314);


/* feedback */
INSERT INTO feedback VALUES
('f001','t101',5,'Smooth ride'),
('f002','t103',4,'Good driver'),
('f003','t105',3,'Car not very clean'),
('f004','t107',5,'Excellent'),
('f005','t110',4,'On time'),
('f006','t111',5,'Great'),
('f007','t112',4,'Nice chat'),
('f008','t114',5,'Early pickup');

練習題 (總共30題)

一樣是三種難度,如果已經做過我們 第一篇第二篇 的練習,應該可以發現這次寫起來已經漸漸順手起來啦。如果還是覺得寫起來卡卡的、不太確定也沒關係,還是可以試著慢慢寫,讓寫 SQL 查詢這件事情的思維能慢慢變成肌肉記憶。

基礎難度

  1. 列出 rider 表當中的前 5 筆資料
  2. 找出所有 rating < 4.5 的司機姓名與評分
  3. 找出 distance_km > 10 的行程編號與車資
  4. 統計 capacity >= 5 的車輛數量
  5. 使用 LIKE 找出司機姓名以 'A' 開頭的司機編號與姓名
  6. 計算 2025-02-19 這天當中,所有行程的平均車資
  7. 列出所有司機服務的城市 (不重複)
  8. 查詢使用優惠碼 (promo_code IS NOT NULL) 的行程編號,以及使用了什麼優惠碼
  9. 依照 distance_km 降序列出最遠的 3 趟行程
  10. 統計付款狀態為 'Paid' 的各付款方式 (method) 行程筆數

中等難度

  1. 計算各城市司機的平均評分
  2. 列出每位乘客的總車資 (來自 trip.fare)
  3. 找出行程數超過 2 趟的司機編號與行程數量
  4. 找出車資高於自己平均車資的行程編號、乘客編號與車資
  5. 列出車齡 > 5 年的車輛編號、品牌與年份
  6. 依優惠碼分組,計算各優惠碼為乘客省下的總折扣金額 (提示:fare - amount)
  7. 對每位司機列出最長距離的行程公里數
  8. 找出自 2025-02-20 起就沒有再搭車的乘客姓名
  9. 列出 2025-02-18~2025-02-20 每天的行程總數
  10. 使用 GROUP_CONCAT 把同一位司機的行程編號串成一欄顯示 (顯示司機編號與行程列表)

進階難度

  1. 找出搭過至少 3 位不同司機的乘客編號與姓名
  2. 使用視窗函數,對每位乘客將行程依車資降序排名,顯示乘客編號、行程編號、車資與排名
  3. 以 CTE 先計算各司機每日收入,再列出收入高於自己平均日收入的日期與金額
  4. 計算公司整體司機平均評分,列出低於此平均的司機編號、姓名與評分
  5. 依城市找出行程數最多的司機 (顯示城市、司機姓名、行程數)
  6. 計算每趟行程的行駛時間 (分鐘),並列出 tid, distance_km, 行駛時間
  7. 找出 payment.amount <> trip.fare 的行程編號與差額
  8. 找出同一對司機-乘客組合出現 2 次以上的行程數與最後一次行程日期
  9. 建立一個 VIEW 叫做 high_value_trip,內容是 fare 高於全公司平均車資的行程;再查詢此 view 的行程總數
  10. 使用集合運算找出尚未搭乘任何行程的乘客編號與姓名 (應為空集合或顯示結果)