# CREATE VIEW

> 通过视图提供可复用的查询，并了解视图何时支持更新。

---

LLMS 索引： [llms.txt](/llms.txt)

---

*视图*是一个有名称且保存下来的 `SELECT`。你可以像查询表一样查询它，但每次都会即时计算结果行——不会存储任何数据。视图非常适合隐藏复杂性（将大型连接隐藏在简短名称之后）、实施列级访问控制（只授予对视图的 `SELECT` 权限，而不授予底层表权限），以及在底层模式发生演变时为应用提供稳定接口。PostgreSQL 还支持*物化视图*，它会存储行；这是下一课的内容。

## 本章要点 {#learn}

- 使用 `CREATE OR REPLACE VIEW` 创建和替换视图
- 可更新视图及其必须遵循的规则
- 使用 `WITH CHECK OPTION` 确保更新仍处于视图范围内
- 授予视图级访问权限而不暴露底层表
- 列出和删除视图

## 示例 {#example}

```sql {num="48.1" caption="创建视图"}
-- A simple view: customer + their order count
CREATE OR REPLACE VIEW customer_orders AS
SELECT c.id, c.name, count(o.id) AS order_count, COALESCE(sum(o.total), 0) AS total_spent
FROM customers_example c
LEFT JOIN orders_example o ON o.customer_id = c.id
GROUP BY c.id, c.name;

-- Use it like a table
SELECT * FROM customer_orders WHERE total_spent > 0 ORDER BY total_spent DESC;

-- Simple updatable view (no joins, no aggregates, no DISTINCT)
CREATE OR REPLACE VIEW alice_orders AS
SELECT * FROM orders_example WHERE customer_id = 1;

-- You can INSERT through an updatable view
INSERT INTO alice_orders (id, customer_id, total) VALUES (10, 1, 99.00);

-- WITH CHECK OPTION: refuse INSERTs/UPDATEs that wouldn't satisfy the view's WHERE
CREATE OR REPLACE VIEW alice_orders_strict AS
SELECT * FROM orders_example WHERE customer_id = 1
WITH CHECK OPTION;

-- This would fail because customer_id = 2 is not in the view's scope
-- INSERT INTO alice_orders_strict (id, customer_id, total) VALUES (11, 2, 1.00);

-- List views
SELECT viewname, definition
FROM pg_views
WHERE schemaname = 'public'
ORDER BY viewname;

-- Drop a view
DROP VIEW IF EXISTS alice_orders;
DROP VIEW IF EXISTS alice_orders_strict;
DROP VIEW IF EXISTS customer_orders;
```

视图是*虚拟的*：对视图执行的每个 `SELECT` 都会重新运行底层查询。视图没有缓存——本质上是规划器会内联的宏。这使视图不会产生过时数据，但也意味着视图本身不会带来性能收益；视图解决的是*抽象*问题，而不是速度问题。

*可更新视图*——那些能直接映射到单个底层表，且不包含聚合、连接、`DISTINCT` 或集合运算的视图——可以接受 `INSERT`、`UPDATE` 和 `DELETE`。这些操作会作用于底层表。`WITH CHECK OPTION` 是安全带，可防止 `UPDATE` 将行移出视图的谓词范围。

如果视图已存在，`CREATE OR REPLACE VIEW` 会更新其定义。列列表和类型必须保持兼容——不能通过这种方式删除列或更改其类型。使用 `ALTER VIEW ... RENAME COLUMN` 重命名列；其他不兼容的更改可能需要先删除再重新创建。

## 运行示例 {#run}

运行方式（需要 `customers_example` 和 `orders_example`）：

```text
$ psql -f source/create-view.sql postgres
CREATE VIEW
 id | name  | order_count | total_spent
----+-------+-------------+-------------
  1 | Alice |           2 |       80.00
(1 row)
 ...
```

## 常见陷阱 {#pitfalls}

- 视图不会自动带来性能提升——视图定义会被内联到每个查询中。对于反复执行的繁重工作，可以考虑物化视图。
- 可更新视图的规则很严格：任何聚合、`DISTINCT`、`GROUP BY`、集合运算或多个基表，通常都会使视图不可更新。你可以编写 `INSTEAD OF` 触发器来允许写入，但那属于高级用法。
- `CREATE OR REPLACE VIEW` 必须保留现有的列名、顺序和类型，即使没有依赖对象也是如此。它可以追加列；重命名请使用 `ALTER VIEW`。
- 视图可能隐藏代价高昂的连接，因此对视图执行的一行查询可能会运行数秒。使用 `EXPLAIN` 检查。

> [!TIP] 提示
> 视图是应用安全中常被低估的工具。只授予应用角色对某个视图的 `SELECT` 权限，该视图只返回它应该看到的列（例如排除 `password_hash`）。底层表仍受保护。

> [!NOTE] 动手练习
> 创建一个列出没有订单的客户的视图（`HAVING count(o.id) = 0`）。在 psql 中使用 `\d customer_orders` 检查视图。然后创建一个限定到单个客户、支持 `UPDATE` 的视图，并通过它执行更新——观察底层表的变化。

## 下载 {#downloads}

SQL 文件：[create-view.sql](/examples/source/create-view.sql) · [全部示例文件](/examples/examples.zip) · [准备工作](/#prerequisites)
