Skip to content

PostgreSQL JSON/JSONB 数据类型与函数

想把订单的明细存成 JSON?

想查询 JSON 里的某个字段?

MySQL 的 JSON 支持很弱,PostgreSQL 呢?

今天,我们来聊聊 PostgreSQL 的 JSON/JSONB。

JSON vs JSONB

区别

特性JSONJSONB
存储格式原始文本二进制
保留空白
重复键保留保留(最后一个)
查询性能较慢较快
支持索引
大小较小稍大
sql
-- JSON:保留原始格式
SELECT '{"key": "value", "arr": [1, 2, 3]}'::JSON;

-- JSONB:存储为二进制,忽略空白
SELECT '{"key": "value", "arr": [1, 2, 3]}'::JSONB;

建议:大多数场景使用 JSONB,性能更好。

JSON/JSONB 操作符

访问操作符

sql
-- 表结构
CREATE TABLE orders (
    id SERIAL PRIMARY KEY,
    order_data JSONB
);

INSERT INTO orders (order_data) VALUES (
    '{
        "customer": "Alice",
        "items": [
            {"product": "Book", "quantity": 2, "price": 29.99},
            {"product": "Pen", "quantity": 5, "price": 2.50}
        ],
        "shipping": {
            "address": "123 Main St",
            "city": "Beijing",
            "zip": "100000"
        }
    }'::JSONB
);

-- -> 操作符:返回 JSON 类型
SELECT order_data->>'customer' FROM orders;  -- 返回 TEXT
SELECT order_data->'customer' FROM orders;     -- 返回 JSON

-- ->> 操作符:返回 TEXT 类型
SELECT order_data->>'customer' FROM orders;  -- "Alice"

-- -> 嵌套访问
SELECT order_data->'shipping'->>'city' FROM orders;  -- "Beijing"
SELECT order_data->'items'->0->>'product' FROM orders;  -- "Book"

-- ->> 直接返回文本
SELECT order_data->'shipping'->>'address' FROM orders;  -- "123 Main St"

路径操作符

sql
-- #> 操作符:获取嵌套路径(返回 JSON)
SELECT order_data#>ARRAY['shipping', 'city'] FROM orders;

-- #>> 操作符:获取嵌套路径(返回 TEXT)
SELECT order_data#>>ARRAY['shipping', 'city'] FROM orders;

包含操作符

sql
-- @>:左包含右
SELECT order_data @> '{"customer": "Alice"}' FROM orders;  -- true

-- 检查数组包含
SELECT order_data @> '{"items": [{"product": "Book"}]}' FROM orders;  -- true

-- <@:右包含左
SELECT '{"customer": "Alice"}' <@ order_data FROM orders;  -- true

-- ?:键存在
SELECT order_data ? 'customer' FROM orders;  -- true
SELECT order_data ? 'phone' FROM orders;       -- false

-- ?|:任一键存在
SELECT order_data ?| ARRAY['customer', 'phone'] FROM orders;

-- ?&:所有键存在
SELECT order_data ?& ARRAY['customer', 'items'] FROM orders;

JSON 函数

创建函数

sql
-- to_json / to_jsonb:任意值转 JSON
SELECT to_json(row(1, 'Alice'));
SELECT to_jsonb(row(1, 'Alice'));

-- jsonb_build_object:构建对象
SELECT jsonb_build_object(
    'name', 'Alice',
    'age', 30,
    'email', 'alice@example.com'
);
-- 结果:{"name": "Alice", "age": 30, "email": "alice@example.com"}

-- jsonb_build_array:构建数组
SELECT jsonb_build_array(1, 2, 3, 4, 5);

-- jsonb_build_array/object 变参版本
SELECT jsonb_build_object('name', name, 'salary', salary)
FROM employees WHERE id = 1;

查询函数

sql
-- jsonb_object_keys:获取对象的所有键
SELECT jsonb_object_keys(order_data->'shipping') FROM orders;
-- 结果:address, city, zip

-- jsonb_array_elements:将数组展开
SELECT value
FROM orders,
     jsonb_array_elements(order_data->'items');

-- jsonb_array_elements_text:展开为文本
SELECT value::TEXT
FROM orders,
     jsonb_array_elements_text(order_data->'items');

聚合函数

sql
-- jsonb_agg:聚合为数组
SELECT jsonb_agg(name) FROM employees WHERE department = 'IT';
-- 结果:["Alice", "Bob", "Carol"]

-- jsonb_object_agg:聚合为对象
SELECT jsonb_object_agg(department, count)
FROM (SELECT department, COUNT(*) as count FROM employees GROUP BY department) d;

-- 示例:聚合员工信息
SELECT 
    department,
    jsonb_object_agg(name, salary) as employees
FROM employees
GROUP BY department;

修改函数

sql
-- jsonb_set:设置值
SELECT jsonb_set(order_data, '{customer}', '"Bob"'::JSONB)
FROM orders;

-- jsonb_insert:插入值
SELECT jsonb_insert(
    order_data, 
    '{tags}', 
    '["electronics"]'::JSONB,
    true  -- true: 插入到末尾,false: 插入到开头
) FROM orders;

-- jsonb_concat:合并
SELECT order_data || '{"priority": "high"}'::JSONB FROM orders;

-- jsonb_delete_key:删除键
SELECT order_data - 'shipping' FROM orders;

-- jsonb_delete_path:删除路径
SELECT jsonb_delete_path(order_data, '{shipping, address}') FROM orders;

类型转换

sql
-- jsonb_typeof:获取值的类型
SELECT jsonb_typeof(order_data->'customer') FROM orders;  -- string
SELECT jsonb_typeof(order_data->'items') FROM orders;      -- array
SELECT jsonb_typeof(order_data->'shipping') FROM orders;    -- object

-- ->> 配合 CAST 使用
SELECT (order_data->>'items')::JSON FROM orders;

GIN 索引

为 JSONB 创建索引

sql
-- 为整个 JSONB 创建 GIN 索引
CREATE INDEX idx_orders_data ON orders USING GIN (order_data);

-- 为 JSONB 中的特定路径创建索引
CREATE INDEX idx_orders_customer ON orders ((order_data->>'customer'));
CREATE INDEX idx_orders_items_product ON orders ((order_data->'items'->0->>'product'));

-- 创建表达式索引
CREATE INDEX idx_orders_shipping_city 
ON orders (((order_data->'shipping'->>'city')::TEXT));

使用索引查询

sql
-- 查询包含特定键值
SELECT * FROM orders 
WHERE order_data @> '{"customer": "Alice"}';

-- 查询数组包含
SELECT * FROM orders 
WHERE order_data @> '{"items": [{"product": "Book"}]}';

-- 键存在检查
SELECT * FROM orders 
WHERE order_data ? 'shipping';

-- 效率对比
EXPLAIN SELECT * FROM orders WHERE order_data @> '{"customer": "Alice"}';

实际应用

场景一:动态属性

sql
-- 产品表,支持动态属性
CREATE TABLE products (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100),
    base_price NUMERIC(10,2),
    attributes JSONB DEFAULT '{}'
);

INSERT INTO products (name, base_price, attributes) VALUES
    ('Laptop', 5000, '{"color": "silver", "memory": "16GB", "storage": "512GB"}'),
    ('Phone', 3000, '{"color": "black", "memory": "8GB", "storage": "256GB"}');

-- 查询有银色属性的产品
SELECT * FROM products 
WHERE attributes @> '{"color": "silver"}';

-- 查询所有内存属性
SELECT id, name, attributes->>'memory' as memory
FROM products;

场景二:订单明细

sql
-- 用 JSONB 存储订单明细
CREATE TABLE orders_v2 (
    id SERIAL PRIMARY KEY,
    customer_id BIGINT,
    order_date TIMESTAMPTZ DEFAULT NOW(),
    items JSONB,
    metadata JSONB DEFAULT '{}'
);

-- 插入订单
INSERT INTO orders_v2 (customer_id, items) VALUES
    (1, '[
        {"sku": "SKU001", "name": "Book", "qty": 2, "price": 29.99},
        {"sku": "SKU002", "name": "Pen", "qty": 5, "price": 2.50}
    ]');

-- 查询订单总金额
SELECT 
    id,
    SUM((item->>'price')::NUMERIC * (item->>'qty')::INTEGER) as total
FROM orders_v2,
     jsonb_array_elements(items) as item
GROUP BY id;

-- 查询购买了特定商品的所有订单
SELECT DISTINCT id
FROM orders_v2,
     jsonb_array_elements(items) as item
WHERE item->>'sku' = 'SKU001';

场景三:灵活的配置

sql
-- 应用配置表
CREATE TABLE app_config (
    id SERIAL PRIMARY KEY,
    config_key VARCHAR(50) UNIQUE,
    config_value JSONB
);

INSERT INTO app_config (config_key, config_value) VALUES
    ('feature_flags', '{"new_ui": true, "beta_features": ["chat", "video"]}'),
    ('rate_limits', '{"api": 1000, "upload": 100}');

-- 查询功能开关
SELECT config_value->>'new_ui' as new_ui_enabled
FROM app_config
WHERE config_key = 'feature_flags';

-- 查询所有 beta 功能
SELECT jsonb_array_elements_text(config_value->'beta_features')
FROM app_config
WHERE config_key = 'feature_flags';

Java 应用

JPA JSONB 映射

java
// 使用 Hibernate Types
@Entity
@Table(name = "orders")
@TypeDef(name = "jsonb", typeClass = JsonbType.class)
public class Order {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(name = "order_data", columnDefinition = "jsonb")
    @Type(type = "jsonb")
    private Map<String, Object> orderData;
}

// 使用 Jackson
@Entity
@Table(name = "orders")
public class Order {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(name = "order_data", columnDefinition = "jsonb")
    @Convert(converter = JsonbConverter.class)
    private OrderData orderData;
}

@Converter
public class JsonbConverter implements AttributeConverter<Map<String, Object>, String> {
    private static final ObjectMapper mapper = new ObjectMapper();
    
    @Override
    public String convertToDatabaseColumn(Map<String, Object> attribute) {
        return attribute == null ? null : mapper.writeValueAsString(attribute);
    }
    
    @Override
    public Map<String, Object> convertToEntityAttribute(String dbData) {
        return dbData == null ? null : mapper.readValue(dbData, Map.class);
    }
}

MyBatis JSONB 查询

java
@Select("""
    SELECT 
        id,
        order_data->>'customer' as customer_name,
        order_data->'shipping'->>'city' as city
    FROM orders
    WHERE order_data @> '{"customer": #{customerName}}'
    """)
List<OrderDto> findByCustomer(@Param("customerName") String customerName);

@Insert("""
    INSERT INTO orders (order_data)
    VALUES (#{orderData}::JSONB)
    """)
@SelectKey(statement = "SELECT LASTVAL()", keyProperty = "id", 
           before = false, resultType = Long.class)
void insertOrder(Order order);

JSONPath(PostgreSQL 14+)

sql
-- PostgreSQL 14+ 支持 JSONPath
SELECT order_data.jsonb_path_query('$.customer') FROM orders;
SELECT order_data.jsonb_path_query_first('$.items[*].product') FROM orders;

-- JSONPath 查询语法
SELECT order_data.jsonb_path_query_array(
    '$.items[*] ? (@.price > 10)'
) FROM orders;

-- 带默认值的查询
SELECT order_data.jsonb_path_query_first(
    '$.notes',
    '"No notes"'  -- 默认值
) FROM orders;

面试高频问题

Q1: JSON 和 JSONB 有什么区别?

考察点:JSON 类型理解

参考答案

  • JSON 存储为文本,JSONB 存储为二进制
  • JSONB 忽略空白,保留最后一个重复键
  • JSONB 查询更快,支持索引
  • 大多数场景推荐使用 JSONB

Q2: 如何为 JSONB 字段创建索引?

考察点:JSON 索引

参考答案

  • GIN 索引:USING GIN (jsonb_column)
  • 表达式索引:((jsonb_column->>'key')::type)
  • 路径索引:((jsonb_column->'nested'->>'key'))

Q3: JSONB 支持哪些查询操作?

考察点:JSON 操作

参考答案

  • @>:包含
  • ?:键存在
  • ?|:任一键存在
  • ?&:所有键存在
  • -> / ->>:访问
  • jsonb_array_elements:展开数组
  • jsonb_object_keys:获取键列表

Q4: JSONB 适合什么场景?

考察点:实际应用

参考答案

  1. 动态属性(schema-less)
  2. 订单明细、日志
  3. 配置信息
  4. 半结构化数据
  5. 避免多表 JOIN

总结

PostgreSQL JSON/JSONB 功能强大:

特性说明
JSON文本存储
JSONB二进制存储,性能更好
GIN 索引加速 JSONB 查询
丰富的操作符@>、?、->、->>
丰富的函数jsonb_set、jsonb_array_elements

使用场景:

  • 动态属性
  • 半结构化数据
  • 配置存储
  • 避免多表 JOIN

PostgreSQL 的 JSON 支持,远比 MySQL 强大。

基于 VitePress 构建