六角學院 「2024 後端工程師 - 資料庫體驗營」的最終任務。
有一間廠商即將建置「健身教練線上直播課平台」
其優點是:
-
小班制健身直播課制:教練會在平台開課,學生前往教練開設的課程頁面報名,課程時間到了後,就可點擊課程直播室進行上課
-
堂數售價:每位健身教練的收費都一致,一堂 50 分鐘小班制健身直播課都是 200 元
堂數組合包方案:
-
7 堂組合包方案:
- 價格:1,400 元
-
14 堂方案:
- 價格:2,520 元
-
21 堂方案:
- 價格:4,800 元
為了提高任務的體驗,我們在資料表和欄位的設計上有些調整,所以這份任務的資料表內容並不適合直接用於實務喔!
-
1.使用者的資料表通常會有密碼欄位
這個任務並沒有伺服器來做密碼驗證,因此此資料表設計將密碼欄位先移除,直接由 email 來取得使用者 id。
-
2.權限設計除了角色 (Role) 以外,還會包含可用的細部權限功能
為了讓同學可以快速體驗角色區分,這個任務的角色權限設計只有簡單的角色 Role 欄位。
實務上除了角色 (Role) 以外,還會包含可用的細部權限功能以及可操作的行爲。
-
3.統計資料通常會透過排程功能統計後,記錄到另一張表
在部分基於統計與計算行為的題目中,你會發現都是即時運算。
而在實務上,這些不變的統計資料,他們通常會被獨立在另外一張資料表,並由一個排程功能來統計後,記錄至資料表。
-
4.不會直接刪除資料庫的資料,而使用軟刪除
基於 DELETE 語法練習,題目中有直接刪除資料庫資料的操作。
實務上通常我們不會直接刪除資料庫的資料,而是使用欄位或資料表來實作刪除標記,避免意外刪除導致無法挽回的結果,這種刪除被稱為軟刪除,而直接將資料完全刪除的行為,則稱為硬刪除。
-
5.資料表與欄位需搭配伺服器的商業邏輯,有更複雜的設計
在關於課程相關(包括購買,預約等行為),是特別被大幅度的簡化的資料表。
在實務上他需要更多的資料表與欄位,並且搭配伺服器的商業邏輯,來確保使用者每一步驟的狀態變化都被留下紀錄。
-
6. 資料庫紀錄的時間會以 UTC 為準
這次任務在時間的欄位上,大部分的時間都已經設定好自動產生。
在實務上,資料庫紀錄的時間大多也是使用 UTC 時間。
請先將 此專案模板 fork 到自己的 GitHub 帳號下。
可以使用以下兩種方式之一,完成 SQL 指令後,請將答案貼至專案中的 /migrations/task/sqls/20241021064214-task-up.sql
,並 push 到自己的 GitHub Repo 繳交任務。
⚠️ 專案模板已經完成 建立資料表,同學不需要再次建立。
可以參照「操作流程」說明來練習 SQL 指令。
可以將模板 /migrations/task/sqls/20241021064214-task-up.sql
中的題目註解貼到 DBeaver,方便對照題目練習。
以下皆為使用 DBeaver 連結 Docker,並在 DBeaver 進行練習的方式。
資料如下:
-
用戶名稱為
李燕容
,Email 為[email protected]
,Role 為USER
-
用戶名稱為
王小明
,Email 為[email protected]
,Role 為USER
-
用戶名稱為
肌肉棒子
,Email 為[email protected]
,Role 為USER
-
用戶名稱為
好野人
,Email 為[email protected]
,Role 為USER
-
用戶名稱為
Q太郎
,Email 為[email protected]
,Role 為USER
-
用戶名稱為
透明人
,Email 為[email protected]
,Role 為USER
insert into "USER" (name, email, role)
values
('李燕容', '[email protected]', 'USER'),
('王小明', '[email protected]', 'USER'),
('肌肉棒子', '[email protected]', 'USER'),
('好野人', '[email protected]', 'USER'),
('Q太郎', '[email protected]', 'USER'),
('透明人', '[email protected]', 'USER');
update "USER"
set "role" = 'COACH'
where email in ('[email protected]', '[email protected]', '[email protected]')
and "role" = 'USER';
delete from "USER"
where email = '[email protected]';
提示:使用
COUNT
函式
select count(*) as 用戶數量 from "USER";
提示:使用
LIMIT
語法
select * from "USER"
limit 3;
資料需求如下:
-
名稱為
7 堂組合包方案
,價格為1,400
元,堂數為7
-
名稱為
14 堂組合包方案
,價格為2,520
元,堂數為14
-
名稱為
21 堂組合包方案
,價格為4,800
元,堂數為21
insert into "CREDIT_PACKAGE" ("name" , price, credit_amount)
values
('7 堂組合包方案', 1400, 7),
('14 堂組合包方案', 2520, 14),
('21 堂組合包方案', 4800, 21);
(請使用 name 欄位做子查詢)
資料如下:
-
王小明
購買14 堂組合包方案
-
王小明
購買21 堂組合包方案
-
好野人
購買14 堂組合包方案
insert into "CREDIT_PURCHASE" (user_id, credit_package_id, purchased_credits, price_paid)
values
(
(select id from "USER" where email = '[email protected]'),
(select id from "CREDIT_PACKAGE" where name = '14 堂組合包方案'),
(select credit_amount from "CREDIT_PACKAGE" where name = '14 堂組合包方案'),
(select price from "CREDIT_PACKAGE" where name = '14 堂組合包方案')
),
(
(select id from "USER" where email = '[email protected]'),
(select id from "CREDIT_PACKAGE" where name = '21 堂組合包方案'),
(select credit_amount from "CREDIT_PACKAGE" where name = '21 堂組合包方案'),
(select price from "CREDIT_PACKAGE" where name = '21 堂組合包方案')
),
(
(select id from "USER" where email = '[email protected]'),
(select id from "CREDIT_PACKAGE" where name = '14 堂組合包方案'),
(select credit_amount from "CREDIT_PACKAGE" where name = '14 堂組合包方案'),
(select price from "CREDIT_PACKAGE" where name = '14 堂組合包方案')
);
提示:使用
李燕容
的 email ,取得李燕容
的id
資料需求如下:
-
將用戶
李燕容
新增為教練,並且年資設定為2
年 -
將用戶
肌肉棒子
新增為教練,並且年資設定為2
年 -
將用戶
Q太郎
新增為教練,並且年資設定為2
年
insert into "COACH" (user_id, experience_years)
values
((select id from "USER" where email = '[email protected]'), 2),
((select id from "USER" where email = '[email protected]'), 2),
((select id from "USER" where email = '[email protected]'), 2);
資料需求如下:
-
所有教練都有
重訓
專長 -
教練
肌肉棒子
需要有瑜伽
專長 -
教練
Q太郎
需要有有氧運動
與復健訓練
專長
insert into "COACH_LINK_SKILL" (coach_id , skill_id)
values
(
(select id from "COACH" where user_id = (select id from "USER" where email = '[email protected]')),
(select id from "SKILL" where name = '重訓')
),
(
(select id from "COACH" where user_id = (select id from "USER" where email = '[email protected]')),
(select id from "SKILL" where name = '重訓')
),
(
(select id from "COACH" where user_id = (select id from "USER" where email = '[email protected]')),
(select id from "SKILL" where name = '重訓')
),
(
(select id from "COACH" where user_id = (select id from "USER" where email = '[email protected]')),
(select id from "SKILL" where name = '瑜伽')
),
(
(select id from "COACH" where user_id = (select id from "USER" where email = '[email protected]')),
(select id from "SKILL" where name = '有氧運動')
),
(
(select id from "COACH" where user_id = (select id from "USER" where email = '[email protected]')),
(select id from "SKILL" where name = '復健訓練')
);
資料需求如下:
-
教練
肌肉棒子
的經驗年數為3
年 -
教練
Q太郎
的經驗年數為5
年
update "COACH"
set experience_years = 3
where user_id = (select id from "USER" where email = '[email protected]');
update "COACH"
set experience_years = 5
where user_id = (select id from "USER" where email = '[email protected]');
insert into "SKILL" (name) values ('空中瑜伽');
delete from "SKILL" where name = '空中瑜伽';
資料需求如下:
-
教練設定為用戶
李燕容
-
在課程專長
skill_id
上設定為「重訓
」 -
在課程名稱上,設定為「
重訓基礎課
」 -
授課開始時間
start_at
設定為2024-11-25 14:00:00
-
授課結束時間
end_at
設定為2024-11-25 16:00:00
-
最大授課人數
max_participants
設定為10
-
授課連結設定
meeting_url
為https://test-meeting.test.io
insert into "COURSE" (user_id, skill_id, "name", start_at, end_at, max_participants, meeting_url)
values
(
(select id from "USER" where email = '[email protected]'),
(select id from "SKILL" where name = '重訓'),
'重訓基礎課',
'2024-11-25 14:00:00',
'2024-11-25 16:00:00',
10,
'https://test-meeting.test.io'
);
-
第一筆:
王小明
預約李燕容
的課程-
預約人設為
王小明
-
預約時間
booking_at
設為2024-11-24 16:00:00
-
狀態
status
設定為即將授課
-
-
第二筆:
好野人
預約李燕容
的課程-
預約人設為
好野人
-
預約時間
booking_at
設為2024-11-24 16:00:00
-
狀態
status
設定為即將授課
-
insert into "COURSE_BOOKING" (user_id, course_id, booking_at, status)
values
(
(select id from "USER" where email = '[email protected]'),
(select id from "COURSE" where user_id = (select id from "USER" where email = '[email protected]')),
'2024-11-24 16:00:00',
'即將授課'
),
(
(select id from "USER" where email = '[email protected]'),
(select id from "COURSE" where user_id = (select id from "USER" where email = '[email protected]')),
'2024-11-24 16:00:00',
'即將授課'
);
-
取消預約時間
cancelled_at
設為2024-11-24 17:00:00
-
狀態
status
設定為課程已取消
update "COURSE_BOOKING"
set cancelled_at = '2024-11-24 17:00:00', status = '課程已取消'
where user_id = (select id from "USER" where email = '[email protected]')
and course_id = (select id from "COURSE" where user_id = (select id from "USER" where email = '[email protected]'));
-
預約人設為
王小明
-
預約時間
booking_at
設為2024-11-24 17:10:25
-
狀態
status
設定為即將授課
insert into "COURSE_BOOKING" (user_id, course_id, booking_at, status)
values
(
(select id from "USER" where email = '[email protected]'),
(select id from "COURSE" where user_id = (select id from "USER" where email = '[email protected]')),
'2024-11-24 17:10:25',
'即將授課'
);
select * from "COURSE_BOOKING"
where user_id = (select id from "USER" where email = '[email protected]');
請注意,不要更新到已經取消的紀錄
-
請在該筆預約記錄他的加入直播室時間
join_at
設為2024-11-25 14:01:59
-
狀態
status
設定為上課中
update "COURSE_BOOKING"
set join_at = '2024-11-25 14:01:59', status = '上課中'
where user_id = (select id from "USER" where email = '[email protected]')
and course_id = (select id from "COURSE" where user_id = (select id from "USER" where email = '[email protected]'))
and status = '即將授課';
需使用到
SUM
函式與GROUP BY
,顯示須包含以下欄位:user_id
,total
select
user_id,
sum(purchased_credits) as total
from "CREDIT_PURCHASE"
where user_id = (select id from "USER" where email = '[email protected]')
group by user_id;
需使用到
COUNT
函式與GROUP BY
,顯示須包含以下欄位:user_id
,total
select
user_id,
count(*) as total
from "COURSE_BOOKING"
where user_id = (select id from "USER" where email = '[email protected]') and join_at is not null
group by user_id;
顯示須包含以下欄位:
user_id
,remaining_credit
-
提示:
SELECT ("CREDIT_PURCHASE".total_credit - "COURSE_BOOKING".used_credit) AS remaining_credit, ... FROM ( 用戶王小明的購買堂數 ) AS "CREDIT_PURCHASE" INNER JOIN ( 用戶王小明的已使用堂數 ) AS "COURSE_BOOKING" ON "COURSE_BOOKING".user_id = "CREDIT_PURCHASE".user_id;
select
"CREDIT_PURCHASE".user_id as user_id,
("CREDIT_PURCHASE".total_credit - "COURSE_BOOKING".used_credit) as remaining_credit
from
(
select
user_id,
sum(purchased_credits) as total_credit
from "CREDIT_PURCHASE"
where user_id = (select id from "USER" where email = '[email protected]')
group by user_id
) as "CREDIT_PURCHASE"
inner join
(
select
user_id,
count(*) as used_credit
from "COURSE_BOOKING"
where user_id = (select id from "USER" where email = '[email protected]') and join_at is not null
group by user_id
) as "COURSE_BOOKING"
on "COURSE_BOOKING".user_id = "CREDIT_PURCHASE".user_id;
需使用
INNER JOIN
與ORDER BY
語法,顯示須包含以下欄位:教練名稱
,經驗年數
,專長名稱
select
(select u."name" from "USER" u where u.id = c.user_id) as 教練名稱,
c.experience_years as 經驗年數,
s."name" as 專長名稱
from "COACH_LINK_SKILL"
inner join "SKILL" s on skill_id = s.id
inner join "COACH" c ON coach_id = c.id
where s."name" = '重訓'
order by c.experience_years desc;
需使用
GROUP BY
,INNER JOIN
與ORDER BY
與LIMIT
語法,顯示須包含以下欄位:專長名稱
,coach_total
select
s."name" as 專長名稱,
count(*) as coach_total
from "COACH_LINK_SKILL" cls
inner join "SKILL" s ON cls.skill_id = s.id
group by s."name"
order by coach_total desc
limit 1;
顯示須包含以下欄位:
組合包方案名稱
,銷售數量
select
cp."name" as 組合包方案名稱,
count(*) as 銷售數量
from "CREDIT_PURCHASE"
inner join "CREDIT_PACKAGE" cp ON credit_package_id = cp.id
where (select extract (month from purchase_at)) = 11
group by cp."name";
使用
purchase_at
欄位統計,顯示須包含以下欄位:總營收
select
sum(price_paid) as 總營收
from "CREDIT_PURCHASE"
where (select extract (month from purchase_at)) = 11;
需使用
DISTINCT
,並用created_at
和status
欄位統計,顯示須包含以下欄位:預約會員人數
select
count(distinct user_id) as 預約會員人數
from "COURSE_BOOKING" cb
where (select extract (month from created_at)) = 11 and status != '課程已取消';