製作點菜系統的資料庫

作者:楊先民
精誠資訊/恆逸教育訓練中心資深講師
※網路引用請註明完整出處
現在出門吃飯,很多商店都要使用QR Code掃碼點餐,這個源自於我在2002年替微軟講的一場研討會:行動點餐系統而來,
本期就來分析設計這類型的資料庫吧。
設計OLTP資料庫
行動點餐,當時的想法是走無線網路,點餐人員拿著類似平板的裝置替客人點餐,點餐的資訊就會透過無線網路傳送到後方的廚房,既達到無紙化的目的,也不用走來走去,當餐點做好時,廚房發出的訊息讓點餐者可以去取餐拿給客戶。
行動點餐,使用者未必會註冊,我們把焦點放在只有一家店的行動點餐,未必有會員註冊機制這樣會比較容易一點。
在單店、免註冊的行動點餐場景中(例如:顧客掃描桌上 QR Code 直接點餐,或是線上點完外帶直接去拿),資料庫的設計會變得更精簡,重點會放在「如何識別這筆訂單是誰的」以及「流程的流暢度」。
在這種情況下,我們不再強制建立一個長期的 users(會員)資料表,而是將顧客的臨時聯絡資訊直接綁在訂單上,或者使用臨時的 Session來追蹤。
以下是針對「單店、免註冊」優化後的資料庫設計:
核心資料表設計 (Schema)
1. 商品與分類模組 (Categories &
Products)
因為只有一家店,所以完全不需要 stores(商家表)以及各個表中的
store_id 外鍵,結構變得簡單很多。
categories (商品分類表)
用於管理菜單上的大分類,例如:主食、小菜、飲料。
|
欄位名稱 (Column) |
資料型態 (Type) |
鍵值 (Key) |
允許空值 (Null) |
說明 (Description) |
|
category_id |
INT |
PK (長遞增) |
NO |
分類唯一識別碼 |
|
category_name |
VARCHAR(50) |
NO |
分類名稱 (如:熱炒類、冷飲) |
|
|
sort_order |
INT |
NO |
排序用 (數字越小排越前面,預設 0) |
products (商品表)
儲存每個餐點的基本資訊。
|
欄位名稱 (Column) |
資料型態 (Type) |
鍵值 (Key) |
允許空值 (Null) |
說明 (Description) |
|
product_id |
INT |
PK (長遞增) |
NO |
商品唯一識別碼 |
|
category_id |
INT |
FK |
NO |
對應的分類 ID (categories.category_id) |
|
product_name |
VARCHAR(100) |
NO |
餐點名稱 (如:招牌牛肉麵) |
|
|
price |
DECIMAL(10,2) |
NO |
售價 (使用 DECIMAL 避免浮點數誤差) |
|
|
description |
TEXT |
YES |
餐點描述或成份說明 |
|
|
image_url |
VARCHAR(255) |
YES |
餐點圖片的網址 |
|
|
is_available |
BOOLEAN |
NO |
是否供餐 (TRUE: 供應中, FALSE: 已售完) |
orders (訂單主檔)
紀錄整筆訂單的總體狀態、顧客資訊(免註冊)與用餐方式。
|
欄位名稱 (Column) |
資料型態 (Type) |
鍵值 (Key) |
允許空值 (Null) |
說明 (Description) |
|
order_id |
INT |
PK (長遞增) |
NO |
訂單內部唯一識別碼 |
|
order_number |
VARCHAR(50) |
Unique |
NO |
外顯的取餐/流水號 (如:#001 或 2026061801) |
|
customer_name |
VARCHAR(50) |
YES |
顧客稱呼 (外帶時核對用) |
|
|
customer_phone |
VARCHAR(20) |
YES |
顧客電話 (外帶通知用,內用可留空) |
|
|
dining_type |
ENUM |
NO |
用餐類型 ('dine_in': 內用, 'takeout': 外帶) |
|
|
table_number |
VARCHAR(20) |
YES |
內用桌號 (外帶時留空) |
|
|
total_amount |
DECIMAL(10,2) |
NO |
訂單總金額 |
|
|
order_status |
ENUM |
NO |
訂單狀態 ('pending', 'paid', 'preparing', 'completed',
'cancelled') |
|
|
payment_status |
ENUM |
NO |
付款狀態 ('unpaid': 未付款, 'paid': 已付款) |
|
|
note |
VARCHAR(255) |
YES |
顧客備註 (如:不要蔥、飲料去冰) |
|
|
guest_session_token |
VARCHAR(255) |
YES |
瀏覽器暫存的 Token,供免註冊顧客追蹤進度 |
|
|
created_at |
TIMESTAMP |
NO |
點餐時間 (預設當下時間) |
|
|
updated_at |
TIMESTAMP |
NO |
最後更新時間 |
order_items (訂單明細檔)
紀錄該筆訂單具體點了哪些餐點、數量與當時的價格。
|
欄位名稱 (Column) |
資料型態 (Type) |
鍵值 (Key) |
允許空值 (Null) |
說明 (Description) |
|
item_id |
INT |
PK (長遞增) |
NO |
明細唯一識別碼 |
|
order_id |
INT |
FK |
NO |
對應的訂單 ID (orders.order_id) |
|
product_id |
INT |
FK |
NO |
對應的商品 ID (products.product_id) |
|
quantity |
INT |
NO |
點餐數量 |
|
|
unit_price |
DECIMAL(10,2) |
NO |
購買當下的單價 (防止未來商品調價影響歷史報表) |
|
|
subtotal |
DECIMAL(10,2) |
NO |
該品項小計 (quantity * unit_price) |
-- 商品分類表 (例如:熱炒類、飲料、主食)
CREATE
TABLE categories (
category_id INT AUTO_INCREMENT PRIMARY KEY,
category_name VARCHAR(50) NOT NULL,
sort_order INT DEFAULT 0 -- 控制菜單顯示的順序
);
-- 商品表
CREATE
TABLE products (
product_id INT AUTO_INCREMENT PRIMARY KEY,
category_id INT NOT NULL,
product_name VARCHAR(100) NOT NULL,
price DECIMAL(10, 2) NOT NULL, -- 金額使用 decimal
避免浮點數誤差
description TEXT,
image_url VARCHAR(255),
is_available BOOLEAN DEFAULT TRUE, -- 廚房點完可直接設定「下架/售完」
FOREIGN KEY (category_id) REFERENCES
categories(category_id)
);
-- 訂單主檔
CREATE
TABLE orders (
order_id INT AUTO_INCREMENT PRIMARY KEY,
order_number VARCHAR(50) NOT NULL UNIQUE,
-- 外顯的取餐號碼或訂單號 (例如: #001 或
20260618001)
-- 免註冊:直接在訂單紀錄顧客資訊
customer_name VARCHAR(50), -- 顧客稱呼 (例如:陳先生)
customer_phone VARCHAR(20), -- 外帶通知或核對用的電話
(內用可為空)
dining_type ENUM('dine_in', 'takeout') NOT
NULL, -- 內用 或 外帶
table_number VARCHAR(20), -- 內用桌號 (外帶則為空)
total_amount DECIMAL(10, 2) NOT NULL, -- 訂單總金額
order_status ENUM('pending', 'paid',
'preparing', 'completed', 'cancelled') DEFAULT 'pending',
payment_status ENUM('unpaid', 'paid')
DEFAULT 'unpaid', -- 付款狀態 (現場付款或線上已付)
note VARCHAR(255), -- 顧客備註 (例如:不要洋蔥)
-- 為了免註冊前端追蹤用 (選填)
guest_session_token VARCHAR(255), -- 瀏覽器
Cookie/LocalStorage 的 Token,讓顧客沒關網頁前能看到進度
created_at TIMESTAMP DEFAULT
CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT
CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
-- 訂單明細檔
CREATE
TABLE order_items (
item_id INT AUTO_INCREMENT PRIMARY KEY,
order_id INT NOT NULL,
product_id INT NOT NULL,
quantity INT NOT NULL,
unit_price DECIMAL(10, 2) NOT NULL, -- 購買當下的價格 (重要!防未來的調漲影響歷史報表)
subtotal DECIMAL(10, 2) NOT NULL, -- 小計
FOREIGN KEY (order_id) REFERENCES
orders(order_id),
FOREIGN KEY (product_id) REFERENCES
products(product_id)
);
免註冊點餐系統的關鍵設計考量
1. 顧客送出點餐後,如何看到「出餐進度」?
因為沒有密碼可以登入,如果顧客不小心把網頁關掉,可能就找不到訂單了。業界通常有兩種解決方案:
- 作法 A(最推薦,極簡): 當顧客送出訂單時,後端生成一個隨機的 guest_session_token(或 UUID),並存在顧客手機的 LocalStorage 中。只要他不換手機或清除資料,當他再次打開點餐網頁,前端帶著這個 Token 去資料庫查 orders 表,就能顯示他剛剛點的餐點進度。
- 作法 B(查詢制): 網頁提供一個「輸入手機號碼/取餐序號」的查詢頁面,讓外帶顧客輸入手機號碼來確認「老闆做好了沒」。
- 當前端解析到 table=05 時,在建立訂單時,直接把 dining_type 設為 dine_in,table_number 設為 05。
- 此時甚至連手機號碼、姓名都不需要顧客填寫,直接點餐送出,廚房的接單系統就會顯示「05 桌,點了牛肉麵、冰綠茶」。
- 如果系統沒有串接線上金流(顧客現場付款):建議在訂單送出前,加入簡訊驗證碼(OTP),雖然多了一步,但能確保手機號碼真實存在。
- 如果系統有串接線上金流(LINE Pay / 信用卡):可以直接規定「外帶必須先完成線上付款」才開始製作,這樣就不用擔心註冊問題,因為錢已經收到了。
2. 桌邊掃碼 (Dine-in QR Code) 的處理
如果是內用掃碼,通常 QR Code 的 URL 會帶有桌號參數(例如:https://my-menu.com/?table=05)。
3. 外帶 (Takeout) 的防刷機制
免註冊最怕遇到有人惡意下單「100碗牛肉麵」卻不來拿(俗稱惡意棄單)。
這樣的單店免註冊設計,不僅降低了消費者的使用門檻,對你來說資料庫的維護難度也大幅下降。
各位也可以想想,還有哪些資料庫可以設計的。
0 意見:
張貼留言