# UUID 与 JSONB

> 生成 UUID，并使用 jsonb 操作符查询结构化 JSON 文档。

---

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

---

PostgreSQL 中两种最受欢迎的非基础类型：`uuid` 用于不透明、全局唯一的标识符，`jsonb` 用于可建立索引并查询的二进制编码 JSON。UUID 是顺序整数 ID 的标准替代方案，适用于需要 ID 不可猜测、可在客户端生成或跨系统全局唯一的场景。对于半结构化数据，`jsonb` 让 PostgreSQL 足以与文档存储系统竞争。

## 本章要点 {#learn}

- 使用 `gen_random_uuid()` 生成 UUID
- 使用 `jsonb` 存储并查询 JSON
- `->`、`->>`、`#>`、`#>>` 操作符及其返回值
- `@>` 包含关系操作符（以及它如何使用 GIN 索引）
- `json` 与 `jsonb` 的区别

## 示例 {#example}

```sql {num="14.1" caption="UUID 与 JSONB"}
-- UUID generation (built-in from PostgreSQL 13)
SELECT gen_random_uuid() AS id;

-- jsonb literal
SELECT '{"name": "Alice", "age": 30, "tags": ["admin", "beta"]}'::jsonb AS j;

-- -> returns jsonb (preserves type); ->> returns text
SELECT '{"name": "Alice"}'::jsonb -> 'name'  AS as_jsonb,
       '{"name": "Alice"}'::jsonb ->> 'name' AS as_text;

-- Array indexing (0-based)
SELECT '["a", "b", "c"]'::jsonb -> 1 AS second;

-- Path operators: #> returns jsonb, #>> returns text
SELECT '{"user": {"address": {"city": "Paris"}}}'::jsonb #>> '{user, address, city}' AS city;

-- Containment: @> checks if the right side is contained in the left
SELECT '{"name": "Alice", "age": 30}'::jsonb @> '{"name": "Alice"}'::jsonb AS contains_alice;

-- Querying jsonb columns in a real table
DROP TABLE IF EXISTS docs_example;
CREATE TABLE docs_example (id integer PRIMARY KEY, data jsonb);
INSERT INTO docs_example VALUES
  (1, '{"name": "Alice", "tags": ["admin"]}'),
  (2, '{"name": "Bob",   "tags": ["beta"]}');

SELECT id, data ->> 'name' AS name
FROM docs_example
WHERE data @> '{"tags": ["admin"]}';
```

`gen_random_uuid()` 会生成随机的版本 4 UUID。从 PostgreSQL 13 开始它就是内置函数，不需要 `pgcrypto` 扩展。PostgreSQL 17 没有内置的 UUIDv7 生成器；如果需要按时间排序的 UUID，请使用应用程序/库或扩展。PostgreSQL 18 新增 `uuidv7()`。`bigint` 标识列也是一种选择。

对于 JSON，优先使用 `jsonb` 而不是 `json`。`json` 会原样存储输入文本（包括空白和键顺序）；`jsonb` 会解析、规范化并以二进制形式存储。通常对 `jsonb` 建立索引并进行高效查询，几乎所有应用都使用它。只有在需要逐字节保留输入（这种情况很少见）时，才使用普通 `json`。

`->` 操作符提取并返回 `jsonb`。`->>` 提取并返回 `text`（去掉 JSON 引号）。`#>` 和 `#>>` 使用路径（由键/下标组成的数组）。`@>`（“包含”）测试左值是否包含右值——它是 JSON 查询的基础。

## 运行示例 {#run}

运行方式：

```text
$ psql -f source/uuid-jsonb.sql postgres
                  id
--------------------------------------
 xxxxxxxx-xxxx-4xxx-xxxx-xxxxxxxxxxxx
(1 row)
 ...
 contains_alice
----------------
 t
(1 row)
 ...
```

## 常见陷阱 {#pitfalls}

- 将 `data -> 'name'` 与字符串比较：`data -> 'name' = '"Alice"'` 可以工作（注意带 JSON 引号的字符串），但通常应该使用 `data ->> 'name' = 'Alice'`。
- 忘记建立索引：对没有 GIN 索引的 `jsonb` 列进行过滤会执行全表扫描。`CREATE INDEX ON docs_example USING gin (data);` 让 `@>` 及类似查询能够使用索引。
- 就地修改 jsonb：`UPDATE t SET data = data || '{"k": "v"}'::jsonb` 会添加或替换顶层键；`jsonb_set` 可以修改嵌套路径。
- `jsonb` 内部的模式漂移：它很灵活，但你仍需知道其中有什么。记录预期结构，并在应用层或使用 CHECK 约束进行验证。

> [!TIP] 提示
> 只为你查询的 JSON 部分建立索引。与其给整个 `data` 列建立 GIN 索引，不如给单一路径建立索引：`CREATE INDEX ON docs_example ((data ->> 'name'));`。这是一个 B-树表达式索引——小巧、快速且精确。

> [!NOTE] 动手练习
> 使用 `->>` 以文本形式获取名称：`SELECT '{"name": "Alice"}'::jsonb ->> 'name';`。插入一条带有嵌套数据的记录，并使用 `#>>` 查询嵌套字段。然后在 `data` 上建立 GIN 索引，并使用 `@>` 进行包含查询。

## 下载 {#downloads}

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