PostgreSQL 视图 #

视图概述 #

视图是一个虚拟表,其内容由查询定义。视图不存储数据,每次查询视图时执行底层查询。

text
┌─────────────────────────────────────────────────────────────┐
│                    视图类型                                  │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  普通视图(VIEW)                                           │
│  ├── 不存储数据                                            │
│  ├── 每次查询执行底层 SQL                                  │
│  ├── 实时反映基表变化                                      │
│  └── 适合简化复杂查询                                      │
│                                                             │
│  物化视图(MATERIALIZED VIEW)                              │
│  ├── 存储查询结果                                          │
│  ├── 需要手动刷新                                          │
│  ├── 查询速度快                                            │
│  └── 适合数据仓库、报表                                    │
│                                                             │
└─────────────────────────────────────────────────────────────┘

创建视图 #

基本视图 #

sql
-- 创建示例表
CREATE TABLE employees (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    department_id INTEGER,
    salary DECIMAL(10, 2),
    hire_date DATE,
    is_active BOOLEAN DEFAULT TRUE
);

CREATE TABLE departments (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100) NOT NULL
);

INSERT INTO departments (name) VALUES ('Engineering'), ('Sales'), ('Marketing');
INSERT INTO employees (name, department_id, salary, hire_date) VALUES
    ('Alice', 1, 90000.00, '2020-01-15'),
    ('Bob', 1, 80000.00, '2021-03-20'),
    ('Charlie', 2, 85000.00, '2019-06-10'),
    ('Diana', 2, 70000.00, '2022-02-28'),
    ('Eve', 3, 65000.00, '2021-08-05');

-- 创建简单视图
CREATE VIEW employee_list AS
SELECT 
    e.id,
    e.name,
    d.name AS department,
    e.salary
FROM employees e
LEFT JOIN departments d ON e.department_id = d.id;

-- 查询视图
SELECT * FROM employee_list;

-- 输出:
--  id |  name   | department  | salary
-- ----+---------+-------------+---------
--   1 | Alice   | Engineering | 90000.00
--   2 | Bob     | Engineering | 80000.00
--   3 | Charlie | Sales       | 85000.00
--   4 | Diana   | Sales       | 70000.00
--   5 | Eve     | Marketing   | 65000.00

带参数的视图(使用函数) #

sql
-- 创建返回特定部门员工的函数
CREATE OR REPLACE FUNCTION get_employees_by_dept(dept_name VARCHAR)
RETURNS TABLE (
    id INTEGER,
    name VARCHAR,
    salary DECIMAL
) AS $$
BEGIN
    RETURN QUERY
    SELECT e.id, e.name, e.salary
    FROM employees e
    JOIN departments d ON e.department_id = d.id
    WHERE d.name = dept_name;
END;
$$ LANGUAGE plpgsql;

-- 使用函数
SELECT * FROM get_employees_by_dept('Engineering');

视图嵌套 #

sql
-- 创建基础视图
CREATE VIEW employee_base AS
SELECT 
    id,
    name,
    department_id,
    salary,
    hire_date
FROM employees
WHERE is_active = TRUE;

-- 创建嵌套视图
CREATE VIEW employee_summary AS
SELECT 
    e.id,
    e.name,
    d.name AS department,
    e.salary,
    EXTRACT(YEAR FROM AGE(CURRENT_DATE, e.hire_date))::INTEGER AS years_of_service
FROM employee_base e
LEFT JOIN departments d ON e.department_id = d.id;

修改视图 #

CREATE OR REPLACE VIEW #

sql
-- 替换视图定义
CREATE OR REPLACE VIEW employee_list AS
SELECT 
    e.id,
    e.name,
    d.name AS department,
    e.salary,
    e.hire_date
FROM employees e
LEFT JOIN departments d ON e.department_id = d.id;

ALTER VIEW #

sql
-- 修改视图名称
ALTER VIEW employee_list RENAME TO employee_overview;

-- 修改视图所有者
ALTER VIEW employee_list OWNER TO new_owner;

-- 设置视图选项
ALTER VIEW employee_list SET (security_barrier = true);

删除视图 #

sql
-- 删除视图
DROP VIEW employee_list;

-- 如果存在则删除
DROP VIEW IF EXISTS employee_list;

-- 级联删除依赖对象
DROP VIEW employee_list CASCADE;

可更新视图 #

简单可更新视图 #

sql
-- 简单视图可以更新
CREATE VIEW active_employees AS
SELECT id, name, salary, department_id
FROM employees
WHERE is_active = TRUE;

-- 插入数据
INSERT INTO active_employees (name, salary, department_id)
VALUES ('Frank', 75000.00, 1);

-- 更新数据
UPDATE active_employees SET salary = 95000.00 WHERE name = 'Alice';

-- 删除数据
DELETE FROM active_employees WHERE name = 'Frank';

视图更新条件 #

text
┌─────────────────────────────────────────────────────────────┐
│                    可更新视图条件                            │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  视图必须满足以下条件才能更新:                              │
│                                                             │
│  ✅ FROM 子句只有一个表                                    │
│  ✅ 不包含 DISTINCT                                        │
│  ✅ 不包含 GROUP BY                                        │
│  ✅ 不包含 HAVING                                          │
│  ✅ 不包含 LIMIT/OFFSET                                    │
│  ✅ 不包含 UNION/INTERSECT/EXCEPT                          │
│  ✅ 不包含聚合函数                                          │
│  ✅ 不包含窗口函数                                          │
│  ✅ 不包含子查询(FROM 中的除外)                          │
│                                                             │
└─────────────────────────────────────────────────────────────┘

使用触发器实现复杂视图更新 #

sql
-- 创建复杂视图
CREATE VIEW employee_department AS
SELECT 
    e.id,
    e.name AS employee_name,
    e.salary,
    d.name AS department_name
FROM employees e
LEFT JOIN departments d ON e.department_id = d.id;

-- 创建 INSTEAD OF 触发器函数
CREATE OR REPLACE FUNCTION update_employee_department()
RETURNS TRIGGER AS $$
BEGIN
    IF TG_OP = 'INSERT' THEN
        INSERT INTO employees (name, salary, department_id)
        VALUES (
            NEW.employee_name,
            NEW.salary,
            (SELECT id FROM departments WHERE name = NEW.department_name)
        );
        RETURN NEW;
    ELSIF TG_OP = 'UPDATE' THEN
        UPDATE employees SET
            name = NEW.employee_name,
            salary = NEW.salary,
            department_id = (SELECT id FROM departments WHERE name = NEW.department_name)
        WHERE id = OLD.id;
        RETURN NEW;
    ELSIF TG_OP = 'DELETE' THEN
        DELETE FROM employees WHERE id = OLD.id;
        RETURN OLD;
    END IF;
    RETURN NULL;
END;
$$ LANGUAGE plpgsql;

-- 创建触发器
CREATE TRIGGER trg_employee_department
    INSTEAD OF INSERT OR UPDATE OR DELETE ON employee_department
    FOR EACH ROW
    EXECUTE FUNCTION update_employee_department();

-- 现在可以更新视图
INSERT INTO employee_department (employee_name, salary, department_name)
VALUES ('George', 70000.00, 'Engineering');

UPDATE employee_department 
SET salary = 80000.00 
WHERE employee_name = 'George';

物化视图 #

创建物化视图 #

sql
-- 创建物化视图
CREATE MATERIALIZED VIEW employee_stats AS
SELECT 
    d.name AS department,
    COUNT(*) AS employee_count,
    ROUND(AVG(e.salary), 2) AS avg_salary,
    MAX(e.salary) AS max_salary,
    MIN(e.salary) AS min_salary
FROM employees e
JOIN departments d ON e.department_id = d.id
GROUP BY d.id, d.name;

-- 查询物化视图
SELECT * FROM employee_stats;

-- 输出:
--  department  | employee_count | avg_salary | max_salary | min_salary
-- -------------+----------------+------------+------------+------------
--  Engineering |              2 |   85000.00 |   90000.00 |   80000.00
--  Sales       |              2 |   77500.00 |   85000.00 |   70000.00
--  Marketing   |              1 |   65000.00 |   65000.00 |   65000.00

刷新物化视图 #

sql
-- 刷新物化视图
REFRESH MATERIALIZED VIEW employee_stats;

-- 并发刷新(不阻塞查询)
REFRESH MATERIALIZED VIEW CONCURRENTLY employee_stats;

-- 注意:CONCURRENTLY 需要物化视图有唯一索引
CREATE UNIQUE INDEX idx_employee_stats_dept ON employee_stats(department);

物化视图属性 #

sql
-- 创建带存储参数的物化视图
CREATE MATERIALIZED VIEW employee_stats_with_data 
WITH (fillfactor = 90) AS
SELECT * FROM employee_stats
WITH DATA;

-- 创建不带数据的物化视图
CREATE MATERIALIZED VIEW employee_stats_empty AS
SELECT * FROM employee_stats
WITH NO DATA;

-- 需要手动刷新才能查询
REFRESH MATERIALIZED VIEW employee_stats_empty;

删除物化视图 #

sql
-- 删除物化视图
DROP MATERIALIZED VIEW employee_stats;

-- 如果存在则删除
DROP MATERIALIZED VIEW IF EXISTS employee_stats;

视图使用场景 #

简化复杂查询 #

sql
-- 复杂查询
SELECT 
    e.name,
    d.name AS department,
    e.salary,
    CASE 
        WHEN e.salary > dept_avg.avg_salary THEN 'Above Average'
        ELSE 'Below Average'
    END AS salary_status
FROM employees e
JOIN departments d ON e.department_id = d.id
JOIN (
    SELECT department_id, AVG(salary) AS avg_salary
    FROM employees
    GROUP BY department_id
) dept_avg ON e.department_id = dept_avg.department_id;

-- 创建视图简化
CREATE VIEW employee_salary_analysis AS
SELECT 
    e.name,
    d.name AS department,
    e.salary,
    ROUND(dept_avg.avg_salary, 2) AS dept_avg_salary,
    CASE 
        WHEN e.salary > dept_avg.avg_salary THEN 'Above Average'
        ELSE 'Below Average'
    END AS salary_status
FROM employees e
JOIN departments d ON e.department_id = d.id
JOIN (
    SELECT department_id, AVG(salary) AS avg_salary
    FROM employees
    GROUP BY department_id
) dept_avg ON e.department_id = dept_avg.department_id;

-- 使用视图
SELECT * FROM employee_salary_analysis WHERE salary_status = 'Above Average';

数据安全 #

sql
-- 创建只显示部分列的视图
CREATE VIEW employee_public AS
SELECT id, name, department_id FROM employees;

-- 授予视图访问权限
GRANT SELECT ON employee_public TO public_user;

-- 撤销基表访问权限
REVOKE ALL ON employees FROM public_user;

数据抽象 #

sql
-- 隐藏复杂的业务逻辑
CREATE VIEW customer_orders AS
SELECT 
    c.id AS customer_id,
    c.name AS customer_name,
    COUNT(o.id) AS order_count,
    COALESCE(SUM(o.total), 0) AS total_spent,
    MAX(o.created_at) AS last_order_date
FROM customers c
LEFT JOIN orders o ON c.id = o.customer_id
GROUP BY c.id, c.name;

查看视图信息 #

sql
-- 查看所有视图
SELECT 
    schemaname,
    viewname,
    viewowner
FROM pg_views
WHERE schemaname NOT IN ('pg_catalog', 'information_schema');

-- 查看视图定义
SELECT pg_get_viewdef('employee_list'::regclass, true);

-- 查看物化视图
SELECT 
    schemaname,
    matviewname,
    matviewowner
FROM pg_matviews;

-- 使用 psql 命令
\d+ employee_list
\dm+ employee_stats

学习路径 #

text
高级阶段
├── 索引
├── 视图(本文)
├── 存储过程
├── 触发器
└── 事务与锁

下一步 #

掌握了视图后,接下来学习 存储过程,了解如何编写 PL/pgSQL!

最后更新:2026-03-29