使用Python與PostgreSQL的JSON數(shù)據(jù)進(jìn)行交互
PostgreSQL 自 9.2 版本起原生支持 JSON 數(shù)據(jù)類型,并在后續(xù)版本中不斷增強(qiáng)其功能,現(xiàn)已提供 JSON 和 JSONB 兩種類型、豐富的操作符、索引支持及函數(shù)體系。與此同時(shí),Python 作為數(shù)據(jù)處理的主流語(yǔ)言,與 PostgreSQL 的結(jié)合日益緊密。
本文將系統(tǒng)講解如何在 Python 中高效、安全、可維護(hù)地與 PostgreSQL 的 JSON 數(shù)據(jù)交互,涵蓋:
- JSON 與 JSONB 的區(qū)別與選型
- 使用
psycopg2和asyncpg等主流驅(qū)動(dòng) - 自動(dòng)序列化/反序列化 Python 字典與 JSON
- 復(fù)雜查詢:路徑提取、條件過(guò)濾、更新操作
- 性能優(yōu)化與索引策略
- 實(shí)戰(zhàn)案例:配置存儲(chǔ)、日志分析、動(dòng)態(tài)表單等
一、PostgreSQL 中的 JSON 類型基礎(chǔ)
參考
- PostgreSQL JSON 函數(shù)文檔:https://www.postgresql.org/docs/current/functions-json.html
- psycopg2 JSON 支持:https://www.psycopg.org/docs/extras.html#json-adaptation
- asyncpg 類型映射:https://magicstack.github.io/asyncpg/current/usage.html#type-conversion
1.1 JSON vs JSONB:關(guān)鍵區(qū)別
| 特性 | JSON | JSONB |
|---|---|---|
| 存儲(chǔ)格式 | 文本(保留原始格式) | 二進(jìn)制(解析后存儲(chǔ)) |
| 是否去重 | 否(保留重復(fù)鍵) | 是(僅保留最后一個(gè)鍵值) |
| 是否保留順序 | 是 | 否(對(duì)象鍵無(wú)序) |
| 索引支持 | 不支持(需表達(dá)式索引) | 支持 GIN、GiST 索引 |
| 查詢性能 | 較慢(每次需解析) | 快(已解析為內(nèi)部結(jié)構(gòu)) |
| 存儲(chǔ)空間 | 較大 | 較?。o(wú)空白、無(wú)重復(fù)) |
推薦:除非必須保留原始 JSON 格式(如審計(jì)日志),否則一律使用
JSONB。
1.2 常用操作符與函數(shù)
1、路徑提取
->:返回 JSON 對(duì)象(仍為 JSONB)
SELECT data->'user'->'name' FROM logs;
->>:返回文本(TEXT)
SELECT data->>'status' FROM orders;
2、條件查詢
檢查鍵是否存在:
SELECT * FROM events WHERE data ? 'error_code';
檢查嵌套路徑:
SELECT * FROM configs WHERE data @> '{"feature": {"enabled": true}}';
3、更新操作
設(shè)置字段(PostgreSQL 9.5+):
UPDATE users SET profile = jsonb_set(profile, '{settings,theme}', '"dark"');
二、Python 驅(qū)動(dòng)選擇與配置
2.1 主流驅(qū)動(dòng)對(duì)比
| 驅(qū)動(dòng) | 異步支持 | JSON 自動(dòng)轉(zhuǎn)換 | 成熟度 | 適用場(chǎng)景 |
|---|---|---|---|---|
psycopg2 | 否 | 需手動(dòng)注冊(cè)適配器 | 高 | 同步應(yīng)用、Django、Flask |
psycopg2-binary | 否 | 同上 | 高 | 快速原型、無(wú)需編譯 |
asyncpg | 是 | 自動(dòng)轉(zhuǎn)換 dict ↔ JSONB | 高 | 異步框架(FastAPI, aiohttp) |
SQLAlchemy + psycopg2 | 否 | 通過(guò) JSON / JSONB 類型自動(dòng)處理 | 極高 | ORM 場(chǎng)景 |
2.2 PostgreSQL 的 JSONB 與 Python 的結(jié)合注意點(diǎn)
PostgreSQL 的 JSONB 與 Python 的結(jié)合,為處理半結(jié)構(gòu)化數(shù)據(jù)提供了強(qiáng)大而靈活的方案。通過(guò)合理選擇驅(qū)動(dòng)、配置自動(dòng)轉(zhuǎn)換、利用索引和操作符,可實(shí)現(xiàn):
- 開發(fā)效率高:無(wú)需預(yù)定義 schema
- 查詢能力強(qiáng):支持復(fù)雜嵌套查詢
- 性能可優(yōu)化:GIN 索引、路徑索引保障效率
但也要謹(jǐn)記:
- 不要濫用 JSON:結(jié)構(gòu)化數(shù)據(jù)仍應(yīng)使用關(guān)系模型
- 保持查詢簡(jiǎn)單:避免過(guò)度嵌套導(dǎo)致維護(hù)困難
- 監(jiān)控性能:定期分析執(zhí)行計(jì)劃
三、使用 psycopg2 處理 JSON
3.1 基礎(chǔ)連接與自動(dòng)轉(zhuǎn)換
默認(rèn)情況下,psycopg2 將 JSONB 列返回為字符串。需注冊(cè)適配器實(shí)現(xiàn)自動(dòng)轉(zhuǎn)換:
import json
import psycopg2
from psycopg2.extras import Json
# 注冊(cè)自動(dòng)轉(zhuǎn)換:DB → Python
def _json_decode(data, cur):
if data is None:
return None
return json.loads(data)
# 注冊(cè)適配器
psycopg2.extensions.register_type(
psycopg2.extensions.new_type(
(3802,), "JSONB", _json_decode # 3802 是 JSONB 的 OID
)
)
# 連接數(shù)據(jù)庫(kù)
conn = psycopg2.connect(
host="localhost",
database="mydb",
user="user",
password="pass"
)
3.2 插入 JSON 數(shù)據(jù)
data = {
"user_id": 123,
"action": "login",
"metadata": {
"ip": "192.168.1.1",
"device": "mobile"
}
}
cur = conn.cursor()
cur.execute(
"INSERT INTO events (event_data) VALUES (%s)",
(Json(data),) # 使用 Json 包裝器
)
conn.commit()
注意:必須使用
psycopg2.extras.Json,否則會(huì)被當(dāng)作字符串插入。
3.3 查詢并自動(dòng)反序列化
cur.execute("SELECT id, event_data FROM events WHERE id = %s", (1,))
row = cur.fetchone()
print(type(row[1])) # <class 'dict'>
print(row[1]['metadata']['ip']) # 192.168.1.1
得益于前面的適配器注冊(cè),event_data 自動(dòng)轉(zhuǎn)為 dict。
3.4 復(fù)雜查詢示例
1、提取嵌套字段
cur.execute("""
SELECT
id,
event_data->>'action' AS action,
event_data->'metadata'->>'ip' AS ip
FROM events
WHERE (event_data->'metadata'->>'device') = %s
""", ("mobile",))
for row in cur:
print(f"Action: {row[1]}, IP: {row[2]}")
2、條件過(guò)濾(使用 @>)
# 查找包含特定子結(jié)構(gòu)的記錄
filter_condition = {"metadata": {"device": "mobile"}}
cur.execute(
"SELECT * FROM events WHERE event_data @> %s",
(Json(filter_condition),)
)
四、使用 asyncpg 處理 JSON(異步場(chǎng)景)
asyncpg 對(duì) JSONB 支持更友好,默認(rèn)自動(dòng)轉(zhuǎn)換。
4.1 基礎(chǔ)用法
import asyncio
import asyncpg
async def main():
conn = await asyncpg.connect(
host='localhost',
database='mydb',
user='user',
password='pass'
)
# 插入:直接傳 dict
data = {"user_id": 123, "tags": ["a", "b"]}
await conn.execute(
"INSERT INTO items (payload) VALUES ($1)",
data # asyncpg 自動(dòng)序列化為 JSONB
)
# 查詢:自動(dòng)反序列化為 dict/list
row = await conn.fetchrow("SELECT payload FROM items LIMIT 1")
print(type(row['payload'])) # <class 'dict'>
print(row['payload']['tags']) # ['a', 'b']
await conn.close()
asyncio.run(main())
優(yōu)勢(shì):無(wú)需額外配置,開箱即用。
4.2 復(fù)雜查詢
# 使用路徑操作符
rows = await conn.fetch("""
SELECT
id,
payload->'user'->>'name' AS name
FROM profiles
WHERE payload @> $1
""", {"settings": {"visible": True}})
for r in rows:
print(r['name'])
五、使用 SQLAlchemy ORM 處理 JSON
SQLAlchemy 通過(guò) JSON 和 JSONB 類型提供 ORM 支持。
5.1 模型定義
from sqlalchemy import create_engine, Column, Integer
from sqlalchemy.dialects.postgresql import JSONB
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
Base = declarative_base()
class Event(Base):
__tablename__ = 'events'
id = Column(Integer, primary_key=True)
data = Column(JSONB) # 使用 JSONB
engine = create_engine('postgresql://user:pass@localhost/mydb')
Session = sessionmaker(bind=engine)
5.2 CRUD 操作
session = Session()
# 插入
event = Event(data={
"type": "click",
"element": {"id": "btn1", "class": "primary"}
})
session.add(event)
session.commit()
# 查詢(自動(dòng)轉(zhuǎn)為 dict)
e = session.query(Event).first()
print(e.data['element']['id']) # btn1
# 條件查詢(使用 .op() 調(diào)用操作符)
from sqlalchemy import text
results = session.query(Event).filter(
Event.data.op('@>')({'type': 'click'})
).all()
5.3 使用專用函數(shù)(SQLAlchemy 1.4+)
from sqlalchemy.dialects.postgresql import JSONB
# 提取字段
session.query(
Event.data['user']['name'].astext.label('username')
).all()
六、高級(jí)技巧與最佳實(shí)踐
6.1 動(dòng)態(tài)構(gòu)建 JSON 查詢條件
避免拼接 SQL,使用參數(shù)化:
def find_by_metadata(conn, **kwargs):
# 構(gòu)建嵌套 dict
condition = {"metadata": kwargs}
cur = conn.cursor()
cur.execute(
"SELECT * FROM events WHERE event_data @> %s",
(Json(condition),)
)
return cur.fetchall()
# 調(diào)用
results = find_by_metadata(conn, device="mobile", os="iOS")
6.2 更新 JSON 字段
# 使用 jsonb_set 更新嵌套字段
cur.execute("""
UPDATE users
SET profile = jsonb_set(profile, %s, %s, true)
WHERE id = %s
""", (
['settings', 'notifications'], # 路徑(數(shù)組)
Json({"email": True, "push": False}), # 新值
user_id
))
第四個(gè)參數(shù) true 表示:若路徑不存在則創(chuàng)建。
6.3 索引優(yōu)化
對(duì)高頻查詢字段建立 GIN 索引:
-- 全文索引(適用于任意鍵查詢) CREATE INDEX idx_event_data ON events USING GIN (event_data); -- 特定路徑索引(更高效) CREATE INDEX idx_event_action ON events ((event_data->>'action'));
在 Python 中可通過(guò) Alembic 或原生 SQL 創(chuàng)建。
七、性能考量
7.1 存儲(chǔ)效率
JSONB比JSON節(jié)省 10%~30% 空間- 避免在 JSON 中存儲(chǔ)大文本(如 base64 圖片),應(yīng)存 URL
7.2 查詢性能
- 避免在
WHERE中對(duì) JSON 字段使用函數(shù)(如length(data->>'name')),會(huì)導(dǎo)致索引失效 - 優(yōu)先使用
@>,?,->>等支持索引的操作符
7.3 批量操作
- 使用
execute_values(psycopg2)或copy_records_to_table(asyncpg)提升批量插入性能 - 示例(psycopg2):
from psycopg2.extras import execute_values
data_list = [{"id": i, "tags": ["x"]} for i in range(1000)]
execute_values(
cur,
"INSERT INTO items (payload) VALUES %s",
[(Json(d),) for d in data_list]
)
八、典型應(yīng)用場(chǎng)景
8.1 用戶配置存儲(chǔ)
CREATE TABLE user_settings (
user_id INT PRIMARY KEY,
preferences JSONB NOT NULL DEFAULT '{}'
);
- 優(yōu)勢(shì):無(wú)需 ALTER TABLE 即可新增配置項(xiàng)
- 查詢:
SELECT preferences->'theme' FROM user_settings
8.2 日志與事件追蹤
- 存儲(chǔ)非結(jié)構(gòu)化日志,支持按任意字段過(guò)濾
- 結(jié)合 BRIN 索引按時(shí)間分區(qū),GIN 索引按內(nèi)容查詢
8.3 動(dòng)態(tài)表單/問(wèn)卷
- 表單結(jié)構(gòu)存為 JSON,回答存為另一 JSON
- 避免 EAV(Entity-Attribute-Value)反模式
九、常見陷阱與解決方案
9.1 時(shí)區(qū)與日期處理
JSON 不支持日期類型,通常存為 ISO 字符串:
data = {"created_at": datetime.utcnow().isoformat()}
查詢時(shí)用 to_timestamp() 轉(zhuǎn)換:
SELECT to_timestamp(data->>'created_at', 'YYYY-MM-DD"T"HH24:MI:SS.US') FROM logs;
9.2 精度丟失(浮點(diǎn)數(shù))
PostgreSQL 的 JSONB 使用 IEEE 754 雙精度,與 Python 一致,一般無(wú)問(wèn)題。
但若需高精度(如金融),應(yīng)存為字符串或使用 NUMERIC 字段。
9.3 鍵名大小寫敏感
JSON 對(duì)象鍵區(qū)分大小寫:
-- 以下不等價(jià) data->'UserId' vs data->'userid'
建議統(tǒng)一使用小寫命名。
以上就是使用Python與PostgreSQL的JSON數(shù)據(jù)進(jìn)行交互的詳細(xì)內(nèi)容,更多關(guān)于Python與PostgreSQL JSON數(shù)據(jù)交互的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Django如何判斷訪問(wèn)來(lái)源是PC端還是手機(jī)端
這篇文章主要介紹了Django如何判斷訪問(wèn)來(lái)源是PC端還是手機(jī)端問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-05-05
Python openpyxl模塊學(xué)習(xí)之輕松玩轉(zhuǎn)Excel
Python提供了許多操作Excel的模塊,能夠讓我們從繁瑣的工作中騰出雙手。本文主要為大家介紹的是openpyxl模塊,它的功能相對(duì)與其他模塊更為齊全,感興趣的小伙伴快來(lái)學(xué)習(xí)一下吧2021-12-12
基于Python的ModbusTCP客戶端實(shí)現(xiàn)詳解
這篇文章主要介紹了基于Python的ModbusTCP客戶端實(shí)現(xiàn)詳解,Modbus Poll和Modbus Slave是兩款非常流行的Modbus設(shè)備仿真軟件,支持Modbus RTU/ASCII和Modbus TCP/IP協(xié)議 ,經(jīng)常用于測(cè)試和調(diào)試Modbus設(shè)備,觀察Modbus通信過(guò)程中的各種報(bào)文,需要的朋友可以參考下2019-07-07
基于Python實(shí)現(xiàn)在線加密解密網(wǎng)站系統(tǒng)
在這個(gè)數(shù)字化時(shí)代,數(shù)據(jù)的安全和隱私變得越來(lái)越重要,所以本文小編就來(lái)帶大家實(shí)現(xiàn)一個(gè)簡(jiǎn)單但功能強(qiáng)大的加密解密系統(tǒng),并深入探討它是如何工作的,有興趣的可以了解下2023-09-09
Python推導(dǎo)式之字典推導(dǎo)式和集合推導(dǎo)式使用體驗(yàn)
這篇文章主要為大家介紹了Python推導(dǎo)式之字典推導(dǎo)式和集合推導(dǎo)式使用示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-06-06
利用Anaconda創(chuàng)建虛擬環(huán)境的全過(guò)程
因?yàn)槎啻沃匦屡渲铆h(huán)境,這些命令每次都要用,每次都忘記,需要重新搜索,所以記錄這一過(guò)程,下面這篇文章主要給大家介紹了關(guān)于利用Anaconda創(chuàng)建虛擬環(huán)境的相關(guān)資料,文中通過(guò)圖文介紹的非常詳細(xì),需要的朋友可以參考下2022-07-07

