PostgreSQL JSON 数据查询教程

PostgreSQL 从 9.2 版本开始支持 JSON 数据类型,并在后续版本中引入了性能更优的 JSONB 类型。这使得 PostgreSQL 在保持关系型数据库强大事务能力的同时,也能灵活处理半结构化数据。本文将在搬瓦工 VPS 上详细讲解 PostgreSQL JSON/JSONB 的存储、查询、索引和实际应用,帮助你在一个数据库中同时处理结构化和非结构化数据。

一、JSON 与 JSONB 的区别

  • JSON:以文本形式存储,保留原始格式(包括空格和键顺序)。每次查询都需要重新解析,查询性能较差。
  • JSONB:以二进制格式存储,不保留空格和键顺序,但支持索引、查询速度更快。写入时有额外的解析开销。

推荐:绝大多数场景使用 JSONB,除非需要保留 JSON 原始格式。

二、创建包含 JSONB 的表

-- 创建产品表,属性用 JSONB 存储
CREATE TABLE products (
    id SERIAL PRIMARY KEY,
    name VARCHAR(200) NOT NULL,
    category VARCHAR(50),
    price DECIMAL(10, 2),
    attrs JSONB DEFAULT '{}',
    created_at TIMESTAMP DEFAULT NOW()
);

-- 插入示例数据
INSERT INTO products (name, category, price, attrs) VALUES
('ThinkPad X1 Carbon', '笔记本', 9999.00, '{
    "brand": "Lenovo",
    "cpu": "i7-1365U",
    "ram": "16GB",
    "storage": "512GB SSD",
    "screen": "14英寸",
    "weight": 1.12,
    "ports": ["USB-C", "USB-A", "HDMI", "耳机口"],
    "features": {"fingerprint": true, "backlit_keyboard": true}
}'),
('MacBook Air M3', '笔记本', 8999.00, '{
    "brand": "Apple",
    "cpu": "M3",
    "ram": "16GB",
    "storage": "256GB SSD",
    "screen": "13.6英寸",
    "weight": 1.24,
    "ports": ["MagSafe", "USB-C", "耳机口"],
    "features": {"touch_id": true, "backlit_keyboard": true}
}'),
('iPhone 15 Pro', '手机', 7999.00, '{
    "brand": "Apple",
    "cpu": "A17 Pro",
    "ram": "8GB",
    "storage": "256GB",
    "screen": "6.1英寸",
    "weight": 0.187,
    "colors": ["原色钛金属", "蓝色钛金属", "白色钛金属"],
    "features": {"face_id": true, "wireless_charging": true}
}');

三、基础查询操作符

3.1 提取 JSON 值

-- -> 操作符:提取 JSON 对象字段,返回 JSON 类型
SELECT name, attrs->'brand' AS brand FROM products;

-- ->> 操作符:提取 JSON 对象字段,返回文本类型
SELECT name, attrs->>'brand' AS brand FROM products;

-- #> 操作符:按路径提取,返回 JSON
SELECT name, attrs#>'{features,fingerprint}' FROM products;

-- #>> 操作符:按路径提取,返回文本
SELECT name, attrs#>>'{features,fingerprint}' FROM products;

3.2 条件查询

-- 查询特定品牌的产品
SELECT name, price FROM products WHERE attrs->>'brand' = 'Apple';

-- 查询 RAM 为 16GB 的产品
SELECT name, price FROM products WHERE attrs->>'ram' = '16GB';

-- 数值比较(需要类型转换)
SELECT name, price FROM products WHERE (attrs->>'weight')::numeric < 1.2;

-- @> 包含操作符:检查 JSONB 是否包含指定内容
SELECT name FROM products WHERE attrs @> '{"brand": "Apple"}';

-- ? 存在操作符:检查键是否存在
SELECT name FROM products WHERE attrs ? 'colors';

-- ?| 任一键存在
SELECT name FROM products WHERE attrs ?| array['colors', 'ports'];

-- ?& 所有键都存在
SELECT name FROM products WHERE attrs ?& array['cpu', 'ram', 'storage'];

3.3 数组操作

-- 查询数组元素
SELECT name, attrs->'ports'->0 AS first_port FROM products;

-- 展开 JSON 数组
SELECT name, jsonb_array_elements_text(attrs->'ports') AS port
FROM products WHERE attrs ? 'ports';

-- 查询数组中包含特定元素的记录
SELECT name FROM products WHERE attrs->'ports' ? 'HDMI';

四、JSONB 修改操作

-- 添加或更新键值
UPDATE products SET attrs = attrs || '{"warranty": "3年"}' WHERE id = 1;

-- 删除键
UPDATE products SET attrs = attrs - 'warranty' WHERE id = 1;

-- 按路径删除嵌套键
UPDATE products SET attrs = attrs #- '{features,fingerprint}' WHERE id = 1;

-- 按路径设置值(PostgreSQL 14+)
UPDATE products SET attrs = jsonb_set(attrs, '{ram}', '"32GB"') WHERE id = 1;

-- 在数组中追加元素
UPDATE products SET attrs = jsonb_set(
    attrs,
    '{ports}',
    (attrs->'ports') || '"SD卡槽"'
) WHERE id = 1;

五、JSONB 索引优化

JSONB 支持 GIN 索引,可以大幅提升查询性能。

5.1 通用 GIN 索引

-- 创建 GIN 索引(支持 @>、?、?|、?& 操作符)
CREATE INDEX idx_products_attrs ON products USING GIN (attrs);

-- 支持 jsonb_path_ops 模式(更小更快,仅支持 @> 操作符)
CREATE INDEX idx_products_attrs_path ON products USING GIN (attrs jsonb_path_ops);

5.2 针对特定键的索引

-- 为特定字段创建 B-tree 索引
CREATE INDEX idx_products_brand ON products ((attrs->>'brand'));

-- 为数值字段创建索引
CREATE INDEX idx_products_weight ON products (((attrs->>'weight')::numeric));

5.3 索引效果验证

-- 使用 EXPLAIN ANALYZE 验证索引是否被使用
EXPLAIN ANALYZE SELECT * FROM products WHERE attrs @> '{"brand": "Apple"}';
EXPLAIN ANALYZE SELECT * FROM products WHERE attrs->>'brand' = 'Lenovo';

六、JSON 聚合与构造

-- 构建 JSON 对象
SELECT jsonb_build_object('name', name, 'price', price, 'brand', attrs->>'brand')
FROM products;

-- 聚合为 JSON 数组
SELECT category, jsonb_agg(jsonb_build_object('name', name, 'price', price)) AS products_list
FROM products GROUP BY category;

-- 将行转换为 JSON
SELECT to_jsonb(p.*) FROM products p WHERE id = 1;

-- JSON 键值对展开
SELECT key, value FROM products, jsonb_each(attrs) WHERE id = 1;

-- 仅展开文本值
SELECT key, value FROM products, jsonb_each_text(attrs) WHERE id = 1;

七、JSONPath 查询(PostgreSQL 12+)

-- 使用 JSONPath 查询
SELECT name FROM products WHERE attrs @? '$.features.face_id';

-- 带条件的 JSONPath
SELECT name, price FROM products
WHERE attrs @@ '$.weight < 1.2';

-- jsonb_path_query 提取数据
SELECT name, jsonb_path_query(attrs, '$.ports[*]') AS port
FROM products WHERE id = 1;

-- jsonb_path_exists 检查路径是否存在
SELECT name FROM products
WHERE jsonb_path_exists(attrs, '$.features.wireless_charging');

八、实际应用场景

8.1 用户配置存储

CREATE TABLE user_settings (
    user_id INTEGER PRIMARY KEY,
    settings JSONB DEFAULT '{
        "theme": "light",
        "language": "zh-CN",
        "notifications": {"email": true, "push": true},
        "privacy": {"profile_public": false}
    }'
);

-- 更新单个设置
UPDATE user_settings
SET settings = jsonb_set(settings, '{theme}', '"dark"')
WHERE user_id = 1;

8.2 日志事件存储

CREATE TABLE event_logs (
    id BIGSERIAL PRIMARY KEY,
    event_type VARCHAR(50),
    payload JSONB,
    created_at TIMESTAMP DEFAULT NOW()
);

CREATE INDEX idx_events_type_time ON event_logs (event_type, created_at);
CREATE INDEX idx_events_payload ON event_logs USING GIN (payload);

-- 查询特定用户的登录事件
SELECT * FROM event_logs
WHERE event_type = 'login' AND payload @> '{"user_id": 12345}'
ORDER BY created_at DESC LIMIT 10;

九、性能注意事项

  • 避免超大 JSONB 文档:单个 JSONB 字段不建议超过几 MB,否则会影响 TOAST 存储和查询性能。
  • 合理使用索引:GIN 索引会增加写入开销,只为高频查询的字段建索引。
  • 关系列与 JSONB 列互补:频繁用于 WHERE、JOIN、ORDER BY 的字段应提取为独立列,灵活可变的属性放入 JSONB。
  • 批量更新:频繁的部分更新(jsonb_set)会导致整行重写,批量场景考虑缓冲后一次写入。

总结

PostgreSQL 的 JSONB 功能让你在关系型数据库中优雅地处理半结构化数据,避免了同时维护 SQL 数据库和 NoSQL 数据库的复杂性。搭配 GIN 索引和 JSONPath,JSONB 查询性能完全可以满足生产需求。更多 PostgreSQL 教程可参考 流复制配置全文搜索。选购搬瓦工 VPS 请查看 全部方案,使用优惠码 NODESEEK2026 享受 6.77% 折扣,通过 bwh81.net 进入官网。

关于本站

搬瓦工VPS中文网(bwgvps.com)是非官方中文信息站,整理搬瓦工的方案、优惠和教程。我们不销售主机,不提供技术服务。

新手必读
搬瓦工优惠码

NODESEEK2026(优惠 6.77%)

购买时填入即可抵扣。