2026年6月26日 星期五

製作點菜系統的資料庫

 


作者:楊先民  
精誠資訊/恆逸教育訓練中心資深講師


※網路引用請註明完整出處


現在出門吃飯,很多商店都要使用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_intable_number 設為 05
  • 此時甚至連手機號碼、姓名都不需要顧客填寫,直接點餐送出,廚房的接單系統就會顯示「05 桌,點了牛肉麵、冰綠茶」。
  • 如果系統沒有串接線上金流(顧客現場付款):建議在訂單送出前,加入簡訊驗證碼(OTP),雖然多了一步,但能確保手機號碼真實存在。
  • 如果系統串接線上金流(LINE Pay / 信用卡):可以直接規定「外帶必須先完成線上付款」才開始製作,這樣就不用擔心註冊問題,因為錢已經收到了。

 

2. 桌邊掃碼 (Dine-in QR Code) 的處理

如果是內用掃碼,通常 QR Code URL 會帶有桌號參數(例如:https://my-menu.com/?table=05)。


3. 外帶 (Takeout) 的防刷機制

免註冊最怕遇到有人惡意下單「100碗牛肉麵」卻不來拿(俗稱惡意棄單)。

這樣的單店免註冊設計,不僅降低了消費者的使用門檻,對你來說資料庫的維護難度也大幅下降。

 

各位也可以想想,還有哪些資料庫可以設計的。


0 意見:

張貼留言