檢視表與物化檢視
檢視表讓你把複雜的查詢封裝起來,像操作真實表一樣使用。本篇介紹普通檢視和物化檢視的用法與差異。
一、 檢視表 (VIEW)
檢視表是「儲存的查詢」,不存實際資料。
建立檢視表
sql
CREATE VIEW active_users AS
SELECT id, name, email, created_at
FROM users
WHERE is_active = true AND deleted_at IS NULL;
-- 使用
SELECT * FROM active_users WHERE created_at > '2024-01-01';優點
- 簡化複雜查詢:封裝 JOIN 和條件
- 權限控制:只暴露部分欄位
- 邏輯抽象:業務邏輯集中管理
範例:訂單統計
sql
CREATE VIEW order_summary AS
SELECT
o.id AS order_id,
c.name AS customer_name,
c.email AS customer_email,
o.total,
o.status,
o.created_at,
COUNT(oi.id) AS item_count
FROM orders o
JOIN customers c ON o.customer_id = c.id
LEFT JOIN order_items oi ON o.id = oi.order_id
GROUP BY o.id, c.name, c.email;
-- 直接查詢
SELECT * FROM order_summary WHERE status = 'pending';二、 可更新檢視 (Updatable View)
符合條件的檢視可以直接 INSERT/UPDATE/DELETE。
條件
- 只來自單一表
- 沒有聚合函數、DISTINCT、GROUP BY、UNION
- 沒有視窗函數
- 沒有 LIMIT/OFFSET
sql
-- 這個檢視可更新
CREATE VIEW us_customers AS
SELECT * FROM customers WHERE country = 'US';
-- 可以直接更新
UPDATE us_customers SET name = 'John' WHERE id = 1;
INSERT INTO us_customers (name, country) VALUES ('Jane', 'US');WITH CHECK OPTION
確保透過檢視插入的資料符合檢視條件:
sql
CREATE VIEW us_customers AS
SELECT * FROM customers WHERE country = 'US'
WITH CHECK OPTION;
-- 這會失敗
INSERT INTO us_customers (name, country) VALUES ('Pierre', 'FR');
-- ERROR: new row violates check option for view "us_customers"三、 物化檢視 (MATERIALIZED VIEW)
物化檢視實際儲存查詢結果,犧牲即時性換取效能。
建立物化檢視
sql
CREATE MATERIALIZED VIEW monthly_sales AS
SELECT
DATE_TRUNC('month', created_at) AS month,
SUM(amount) AS total_sales,
COUNT(*) AS order_count,
AVG(amount) AS avg_order
FROM orders
GROUP BY DATE_TRUNC('month', created_at)
ORDER BY month;
-- 查詢(非常快,直接讀取預計算結果)
SELECT * FROM monthly_sales WHERE month >= '2024-01-01';刷新資料
物化檢視不會自動更新,需要手動刷新:
sql
-- 完全刷新(阻塞讀取)
REFRESH MATERIALIZED VIEW monthly_sales;
-- 並行刷新(不阻塞,需要唯一索引)
CREATE UNIQUE INDEX idx_monthly_sales ON monthly_sales(month);
REFRESH MATERIALIZED VIEW CONCURRENTLY monthly_sales;刷新策略
| 策略 | 方式 | 適用 |
|---|---|---|
| 手動 | 需要時才刷新 | 不常變動的資料 |
| 定時 | pg_cron 定時任務 | 報表、統計 |
| 事件 | 觸發器觸發刷新 | 即時性要求中等 |
sql
-- 使用 pg_cron 定時刷新
SELECT cron.schedule('refresh_sales', '0 * * * *',
'REFRESH MATERIALIZED VIEW CONCURRENTLY monthly_sales');四、 VIEW vs MATERIALIZED VIEW
| 特性 | VIEW | MATERIALIZED VIEW |
|---|---|---|
| 儲存資料 | 否 | 是 |
| 查詢速度 | 即時執行 | 直接讀取 |
| 資料即時性 | 即時 | 需刷新 |
| 佔用空間 | 無 | 有 |
| 可建索引 | 否 | 是 |
| 可更新 | 視情況 | 否 |
選擇建議
| 情況 | 推薦 |
|---|---|
| 簡化查詢、權限控制 | VIEW |
| 複雜計算、報表 | MATERIALIZED VIEW |
| 即時性要求高 | VIEW |
| 查詢效能優先 | MATERIALIZED VIEW |
五、 遞迴檢視
sql
CREATE RECURSIVE VIEW employee_hierarchy (id, name, manager_id, level) AS
SELECT id, name, manager_id, 1
FROM employees
WHERE manager_id IS NULL
UNION ALL
SELECT e.id, e.name, e.manager_id, eh.level + 1
FROM employees e
JOIN employee_hierarchy eh ON e.manager_id = eh.id;
SELECT * FROM employee_hierarchy ORDER BY level;六、 實用範例
權限控制
sql
-- 隱藏敏感欄位
CREATE VIEW public_users AS
SELECT id, name, country, created_at
FROM users;
-- email、password 不暴露
GRANT SELECT ON public_users TO readonly_role;版本化資料
sql
-- 只顯示最新版本
CREATE VIEW current_documents AS
SELECT DISTINCT ON (document_id) *
FROM document_versions
ORDER BY document_id, version DESC;多表聚合
sql
CREATE MATERIALIZED VIEW product_stats AS
SELECT
p.id AS product_id,
p.name,
COALESCE(SUM(oi.quantity), 0) AS total_sold,
COALESCE(AVG(r.rating), 0) AS avg_rating,
COUNT(DISTINCT r.id) AS review_count
FROM products p
LEFT JOIN order_items oi ON p.id = oi.product_id
LEFT JOIN reviews r ON p.id = r.product_id
GROUP BY p.id, p.name;
CREATE UNIQUE INDEX idx_product_stats ON product_stats(product_id);七、 效能優化
為物化檢視建立索引
sql
CREATE MATERIALIZED VIEW user_stats AS
SELECT user_id, COUNT(*) as order_count, SUM(amount) as total_spent
FROM orders GROUP BY user_id;
-- 加上索引
CREATE INDEX idx_user_stats_user ON user_stats(user_id);
CREATE INDEX idx_user_stats_spent ON user_stats(total_spent DESC);部分刷新技巧
PostgreSQL 不支援增量刷新,但可以用技巧模擬:
sql
-- 建立分時段的物化檢視
CREATE MATERIALIZED VIEW daily_sales AS
SELECT DATE(created_at) as date, SUM(amount) as total
FROM orders
WHERE created_at >= CURRENT_DATE - INTERVAL '30 days'
GROUP BY DATE(created_at);
-- 只保留近期資料,刷新更快八、 管理檢視
查看檢視
sql
-- 列出所有檢視
\dv
-- 查看檢視定義
\d+ view_name
-- SQL 查詢
SELECT viewname, definition FROM pg_views WHERE schemaname = 'public';修改檢視
sql
-- 重新建立
CREATE OR REPLACE VIEW active_users AS
SELECT id, name, email, phone, created_at -- 新增 phone
FROM users
WHERE is_active = true;刪除檢視
sql
DROP VIEW IF EXISTS active_users;
-- 刪除物化檢視
DROP MATERIALIZED VIEW IF EXISTS monthly_sales;九、 依賴管理
檢視依賴底層表,修改表結構可能影響檢視:
sql
-- 查看檢視依賴
SELECT
view_name,
table_name
FROM information_schema.view_column_usage
WHERE view_name = 'order_summary';
-- CASCADE 刪除相關檢視
DROP TABLE orders CASCADE;
-- 會一起刪除依賴 orders 的檢視總結
| 概念 | 說明 |
|---|---|
| VIEW | 儲存的查詢,不存資料 |
| MATERIALIZED VIEW | 儲存查詢結果,需刷新 |
| Updatable View | 可透過檢視修改資料 |
| WITH CHECK OPTION | 確保插入資料符合檢視條件 |
| REFRESH CONCURRENTLY | 不阻塞的刷新 |
使用建議:
- 簡單抽象用 VIEW
- 複雜報表用 MATERIALIZED VIEW
- 記得為物化檢視建立索引
進階挑戰
- 建立一個物化檢視,預先計算每月銷售報表,並設定每日自動刷新
- 使用
REFRESH MATERIALIZED VIEW CONCURRENTLY測試在高流量下的刷新效能 - 比較相同查詢在 VIEW 和 MATERIALIZED VIEW 下的執行計畫差異