强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

SQLite 完全指南 / 12 - JSON 支持

12 - JSON 支持:json 函数、索引与部分更新

12.1 JSON 函数总览

SQLite 3.9.0+ 提供完整的 JSON 支持,3.38.0+ 引入 ->->> 操作符。

12.1.1 函数分类

类别函数说明
创建json()验证并规范化 JSON
json_object()创建 JSON 对象
json_array()创建 JSON 数组
提取json_extract()提取指定路径的值
->提取 JSON 值(带引号)
->>提取 JSON 值(纯文本)
修改json_set()设置值(不存在则创建)
json_insert()仅插入不存在的路径
json_replace()仅替换已存在的路径
json_remove()删除路径
json_patch()合并两个 JSON 对象
展开json_each()展开 JSON 数组/对象(一行)
json_tree()递归展开嵌套 JSON
工具json_valid()验证 JSON 是否有效
json_type()返回值的类型
json_array_length()数组长度
json_group_array()聚合为 JSON 数组
json_group_object()聚合为 JSON 对象
json_quote()将值转为 JSON 字面量

12.2 创建 JSON

-- json_object():键值对创建对象
SELECT json_object('name', '张三', 'age', 25, 'active', json(1));
-- {"name":"张三","age":25,"active":true}

-- json_array():创建数组
SELECT json_array(1, 'hello', NULL, 3.14, json('true'));
-- [1,"hello",null,3.14,true]

-- json():验证并规范化
SELECT json('{"name":"张三","age":25}');
-- {"name":"张三","age":25}

-- 嵌套结构
SELECT json_object(
    'name', '张三',
    'address', json_object('city', '北京', 'zip', '100000'),
    'hobbies', json_array('阅读', '编程', '旅行')
);
-- {"name":"张三","address":{"city":"北京","zip":"100000"},"hobbies":["阅读","编程","旅行"]}

-- json_quote():将值转为 JSON 字面量
SELECT json_quote('hello');   -- "hello"
SELECT json_quote(42);        -- 42
SELECT json_quote(NULL);      -- null

12.3 提取 JSON 值

12.3.1 json_extract()

SET @data = '{"name":"张三","age":25,"address":{"city":"北京"},"hobbies":["阅读","编程"]}';

-- 基本提取
SELECT json_extract('{"name":"张三","age":25}', '$.name');  -- 张三
SELECT json_extract('{"name":"张三","age":25}', '$.age');   -- 25

-- 嵌套路径
SELECT json_extract('{"address":{"city":"北京"}}', '$.address.city');  -- 北京

-- 数组索引
SELECT json_extract('["a","b","c"]', '$[0]');  -- a
SELECT json_extract('["a","b","c"]', '$[2]');  -- c
SELECT json_extract('["a","b","c"]', '$[#-1]');  -- c(最后一个)

-- 多路径提取(返回数组)
SELECT json_extract('{"a":1,"b":2,"c":3}', '$.a', '$.c');  -- [1,3]

-- 通配符(数组)
SELECT json_extract('[1,2,3,4]', '$[*]');  -- [1,2,3,4]

-- 路径语法
-- $.key        — 对象成员
-- $[0]         — 数组元素
-- $[#]         — 数组长度
-- $[#-1]       — 最后一个元素
-- $.a.b.c      — 嵌套路径
-- $[*]         — 所有元素
-- $.key[*]     — 对象成员的所有子元素

12.3.2 -> 和 -» 操作符

-- -> 返回 JSON 值(保留类型和引号)
SELECT '{"name":"张三","age":25}' -> '$.name';   -- "张三"
SELECT '{"name":"张三","age":25}' -> '$.age';    -- 25

-- ->> 返回 SQL 值(去除引号)
SELECT '{"name":"张三","age":25}' ->> '$.name';  -- 张三
SELECT '{"name":"张三","age":25}' ->> '$.age';   -- 25

-- 实际使用对比
-- -> 适合嵌套 JSON 或继续使用 json 函数
-- ->> 适合直接比较和显示
SELECT * FROM products
WHERE info ->> '$.category' = '电子产品'
AND (info ->> '$.price')::REAL > 100;

12.4 修改 JSON

-- json_set():设置值(不存在则创建,存在则更新)
SELECT json_set('{"a":1}', '$.b', 2);         -- {"a":1,"b":2}
SELECT json_set('{"a":1}', '$.a', 99);         -- {"a":99}
SELECT json_set('{}', '$.name', '张三', '$.age', 25);  -- {"name":"张三","age":25}

-- json_insert():仅在路径不存在时插入
SELECT json_insert('{"a":1}', '$.b', 2);       -- {"a":1,"b":2}
SELECT json_insert('{"a":1}', '$.a', 99);       -- {"a":1}(已存在,不修改)

-- json_replace():仅在路径存在时替换
SELECT json_replace('{"a":1}', '$.b', 2);      -- {"a":1}(不存在,不创建)
SELECT json_replace('{"a":1}', '$.a', 99);      -- {"a":99}

-- json_remove():删除路径
SELECT json_remove('{"a":1,"b":2,"c":3}', '$.b');  -- {"a":1,"c":3}
SELECT json_remove('["a","b","c"]', '$[1]');        -- ["a","c"]

-- json_patch():合并 JSON 对象
SELECT json_patch('{"a":1,"b":2}', '{"b":3,"c":4}');  -- {"a":1,"b":3,"c":4}
-- 第二个对象覆盖第一个的同名键

12.5 JSON 表函数

12.5.1 json_each()

-- 展开 JSON 数组
SELECT * FROM json_each('["苹果","香蕉","橙子"]');
-- key  value  type  atom  id  parent  fullkey  path
-- 0    苹果   text  苹果  1   0       $[0]     $
-- 1    香蕉   text  香蕉  3   0       $[1]     $
-- 2    橙子   text  橙子  5   0       $[2]     $

-- 展开 JSON 对象
SELECT * FROM json_each('{"name":"张三","age":25}');
-- key    value  type     atom  id  parent  fullkey  path
-- name   张三   text     张三  1   0       $.name   $
-- age    25     integer  25    3   0       $.age    $

-- 实际使用:数组转行
SELECT
    key AS idx,
    value AS item
FROM json_each('["苹果","香蕉","橙子"]');

-- 展开嵌套数组
CREATE TABLE orders (id INTEGER PRIMARY KEY, items TEXT);
INSERT INTO orders VALUES (1, '[{"product":"苹果","qty":2},{"product":"香蕉","qty":3}]');

SELECT
    o.id,
    je.value ->> '$.product' AS product,
    je.value ->> '$.qty' AS quantity
FROM orders o, json_each(o.items) je;

12.5.2 json_tree()

-- 递归展开嵌套 JSON
SELECT * FROM json_tree('{"a":{"b":1},"c":[2,3]}');

-- 只查看特定路径的子树
SELECT * FROM json_tree('{"a":{"b":{"c":1}}}', '$.a');

12.6 JSON 聚合函数

-- json_group_array():将行聚合为 JSON 数组
CREATE TABLE tags (article_id INTEGER, tag TEXT);
INSERT INTO tags VALUES (1, 'SQLite'), (1, '数据库'), (1, '教程');
INSERT INTO tags VALUES (2, 'Python'), (2, '编程');

SELECT article_id, json_group_array(tag) AS tags
FROM tags
GROUP BY article_id;
-- 1  ["SQLite","数据库","教程"]
-- 2  ["Python","编程"]

-- json_group_object():将行聚合为 JSON 对象
CREATE TABLE attrs (item_id TEXT, key TEXT, value TEXT);
INSERT INTO attrs VALUES ('item1', 'color', 'red');
INSERT INTO attrs VALUES ('item1', 'size', 'large');
INSERT INTO attrs VALUES ('item2', 'color', 'blue');

SELECT item_id, json_group_object(key, value) AS attrs
FROM attrs
GROUP BY item_id;
-- item1  {"color":"red","size":"large"}
-- item2  {"color":"blue"}

-- 组合使用
SELECT json_object(
    'id', id,
    'name', name,
    'tags', (SELECT json_group_array(tag) FROM tags WHERE article_id = a.id)
) AS article_json
FROM articles a;

12.7 JSON 索引

12.7.1 表达式索引

-- 创建存储 JSON 的表
CREATE TABLE products (
    id INTEGER PRIMARY KEY,
    data TEXT NOT NULL  -- JSON 字符串
);

INSERT INTO products VALUES
    (1, '{"name":"iPhone","category":"电子产品","price":5999}'),
    (2, '{"name":"MacBook","category":"电子产品","price":12999}'),
    (3, '{"name":"T恤","category":"服装","price":99}');

-- 为 JSON 字段创建索引
CREATE INDEX idx_products_category ON products(json_extract(data, '$.category'));
CREATE INDEX idx_products_price ON products(json_extract(data, '$.price'));

-- 使用索引查询
EXPLAIN QUERY PLAN
SELECT * FROM products
WHERE json_extract(data, '$.category') = '电子产品';
-- SEARCH TABLE products USING INDEX idx_products_category

-- 复合表达式索引
CREATE INDEX idx_products_cat_price ON products(
    json_extract(data, '$.category'),
    json_extract(data, '$.price')
);

12.7.2 JSON 部分索引

-- 只为电子产品创建价格索引
CREATE INDEX idx_electronics_price ON products(
    json_extract(data, '$.price')
) WHERE json_extract(data, '$.category') = '电子产品';

12.7.3 -» 操作符索引

-- 使用 ->> 操作符创建索引
CREATE INDEX idx_products_name ON products(data ->> '$.name');

-- 查询
SELECT * FROM products WHERE data ->> '$.name' = 'iPhone';

12.8 JSON 实战模式

12.8.1 EAV 模式(实体-属性-值)

-- 使用 JSON 替代传统的 EAV 表
CREATE TABLE entities (
    id INTEGER PRIMARY KEY,
    type TEXT NOT NULL,
    attributes TEXT NOT NULL  -- JSON
);

INSERT INTO entities VALUES
    (1, 'user', '{"name":"张三","age":25,"city":"北京"}'),
    (2, 'user', '{"name":"李四","age":30,"city":"上海","phone":"13800138000"}'),
    (3, 'product', '{"name":"iPhone","price":5999}');

-- 查询:用户的城市
SELECT id, attributes ->> '$.city' AS city
FROM entities WHERE type = 'user';

-- 筛选:年龄大于 25 的用户
SELECT * FROM entities
WHERE type = 'user' AND CAST(attributes ->> '$.age' AS INTEGER) > 25;

12.8.2 动态表单数据

CREATE TABLE form_submissions (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    form_name TEXT NOT NULL,
    submitted_at TEXT DEFAULT (datetime('now')),
    data TEXT NOT NULL  -- JSON 表单数据
);

INSERT INTO form_submissions (form_name, data) VALUES
    ('registration', '{"name":"张三","email":"[email protected]","plan":"pro"}'),
    ('registration', '{"name":"李四","email":"[email protected]","plan":"free"}');

-- 查询特定计划的注册
SELECT * FROM form_submissions
WHERE form_name = 'registration'
AND data ->> '$.plan' = 'pro';

12.8.3 配置存储

CREATE TABLE app_settings (
    key TEXT PRIMARY KEY,
    value TEXT NOT NULL
);

INSERT INTO app_settings VALUES
    ('theme', '{"mode":"dark","primary":"#1976D2","font_size":14}'),
    ('notifications', '{"email":true,"push":false,"sms":true}');

-- 更新单个配置项
UPDATE app_settings
SET value = json_set(value, '$.font_size', 16)
WHERE key = 'theme';

-- 查询配置
SELECT value ->> '$.mode' FROM app_settings WHERE key = 'theme';

12.9 性能注意事项

12.9.1 JSON 函数的开销

操作开销说明
json_extract()中等每次调用需解析 JSON
->>中等与 json_extract 类似
json_each()较高需要解析并展开整个 JSON
json_set()较高需要解析、修改、重新序列化
表达式索引查询快索引避免了重复解析

12.9.2 何时使用 JSON vs 关系列

场景推荐方式
需要查询/过滤的字段关系列
需要 JOIN 的字段关系列
灵活/动态属性JSON
配置数据JSON
日志/审计附加信息JSON
需要外键约束的字段关系列
数组类型数据JSON

⚠️ 注意事项

  1. JSON 值以 TEXT 形式存储——数值提取后需要 CAST 转换
  2. 路径是大小写敏感的——$.Name$.name 是不同的路径
  3. json_extract 返回 NULL 时路径不存在——不是错误
  4. 大 JSON 的频繁解析会影响性能——考虑使用表达式索引
  5. -> 返回 JSON 格式——比较时需要注意引号差异
  6. JSON 数组索引从 0 开始——$[0] 是第一个元素

💡 技巧

  1. ->> 用于 WHERE 和显示——返回纯文本值
  2. -> 用于嵌套提取——返回有效的 JSON 子文档
  3. json_each() + JOIN 可以将 JSON 数组转为关系数据
  4. 表达式索引是 JSON 性能的关键——为经常查询的 JSON 字段创建索引
  5. json_group_array() 可以将关系数据聚合为 JSON

📌 业务场景

场景一:商品属性系统

CREATE TABLE products (
    id INTEGER PRIMARY KEY,
    name TEXT NOT NULL,
    price INTEGER NOT NULL,
    attrs TEXT DEFAULT '{}'  -- JSON 动态属性
);

-- 创建属性索引
CREATE INDEX idx_products_brand ON products(json_extract(attrs, '$.brand'));
CREATE INDEX idx_products_color ON products(json_extract(attrs, '$.color'));

-- 插入不同类型的属性
INSERT INTO products (name, price, attrs) VALUES
    ('iPhone 15', 599900, '{"brand":"Apple","color":"黑色","storage":"256GB","5g":true}'),
    ('MacBook Pro', 1299900, '{"brand":"Apple","cpu":"M3","ram":"16GB","screen":"14寸"}');

-- 按品牌查询
SELECT * FROM products WHERE json_extract(attrs, '$.brand') = 'Apple';

-- 聚合属性
SELECT json_group_object(key, value)
FROM products, json_each(products.attrs)
WHERE id = 1;

场景二:Webhook 事件存储

CREATE TABLE webhook_events (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    event_type TEXT NOT NULL,
    payload TEXT NOT NULL,
    processed INTEGER DEFAULT 0,
    created_at TEXT DEFAULT (datetime('now'))
);

-- 存储事件
INSERT INTO webhook_events (event_type, payload) VALUES
    ('order.created', '{"order_id":12345,"amount":9999,"user_id":42}'),
    ('user.updated', '{"user_id":42,"changes":{"email":"[email protected]"}}');

-- 处理特定事件
SELECT * FROM webhook_events
WHERE event_type = 'order.created'
AND payload ->> '$.amount' > 5000
AND processed = 0;

🔗 扩展阅读


📖 下一章13 - 备份恢复 —— .backup、VACUUM、在线备份 API