浅谈一下关系型数据库中json类型字段的处理

文章目录

  • 背景
    • mysql json type
    • postgresql jsonb type
    • 场景说明
    • mysql实验案例
    • postgresql 实验案例
  • 总结

背景

最近涉及到在关系型数据库中解析json数据的问题,数据库表中有一个json类型的字段,业务场景涉及到展开单行json数据为*多行数据并和其他的表进行join查询. 以往只是知道当前主流的关系型数据库版本是支持存放json数据的,但并未深入研究过这些数据库支持json数据处理到什么样的程度.本文在两种关系行数据库(mysql以及postgresql)上进行实验然后进行总结,因为两者针对json字段处理提供的函数有较大出入,需要分开讨论.

mysql json type

参考mysql json datatype 以及 mysql json functions
其中 json_table函数是此次实验需要使用的函数 json table
这个函数本质上就是将稍显复杂的单行json 列展开成多行多列的table, 方便进行表级别的操作.
举个例子,将 [{"a":1, "b":2}, {"a":2, "b":3}] json array 变成a,b两列的表

select * from json_table('[{"a":1, "b":2}, {"a":2, "b":3}]', '$[*]' columns(a int path '$.a',b int path '$.b'
)) as t
a|b|
-+-+
1|2|
2|3|

如果待拆分的json数据来自某个表的json字段, 比如 test_json 表有一个test字段是json类型,里面存放如下的json数据

test              |
------------------+
[{"a": 1, "b": 2}]|
[{"a": 2, "b": 1}]|

现在把它拆分成a,b两列的新表

select a, b from 
test_json,
json_table(test_json.test, '$[*]' columns(a int path '$.a',b int path '$.b'
)) as t

结果

a|b|
-+-+
1|2|
2|1|

json_table还支持嵌套json解析,这个在当json字段存储的数据结构相对比较复杂的时候,发挥重要作用.比如往test_json表的test字段存入如下json数据

test                                                              |
------------------------------------------------------------------+
{"id": 1, "records": [{"id": 1, "data": 1}, {"id": 2, "data": 2}]}|
{"id": 2, "records": [{"id": 3, "data": 3}, {"id": 4, "data": 4}]}|

现在将其变成一个id, record_id 以及record_data 一行的结构, 使用如下sql

select t.id, record_id, record_data
from test_json,
json_table(test_json.test, '$' columns(id int path '$.id',nested path '$.records[*]' columns(record_id int path '$.id',record_data int path '$.data')
)) as t

结果为

id|record_id|record_data|
--+---------+-----------+1|        1|          1|1|        2|          2|2|        3|          3|2|        4|          4|

postgresql jsonb type

pg对json数据存储提供了两种类型json以及jsonb
相比于mysql, postgresql提供的操作json类型数据的函数更多.
json type
json functions
官方推荐使用jsonb类型来存储json数据.
在诸多jsonb的方法中, jsonb_path_query可以根据jsonpath解析json数据并展开数据.它是本次实验需要使用的函数.

举个例子有如下测试json数据

select jsonb_path_query('{"content":[{"user_id":1, "score":0.7}, {"user_id":2, "score":0.8}]}', '$.content[*].user_id') as user_id,
jsonb_path_query('{"content":[{"user_id":1, "score":0.7}, {"user_id":2, "score":0.8}]}', '$.content[*].score') as score

展开成多行就可以得到如下的user_id和score两列的结果.

user_id|score|
-------+-----+
1      |0.7  |
2      |0.8  |

场景说明

假设使用关系型数据库存一个书籍推荐场景的数据, 定义如下三张表:
第一张推荐任务表 recommend_job

field_nametypedescription
idint一次推荐任务的id
resultjson当前推荐任务的结果
statusvarchar当前任务的状态:成功/失败/运行中

json 字段会存储基于物品的推荐结果, 样本数据如下:

{"recommend": [{"book_id": 1,"users": [{"user_id": 1,"score": 0.8},{"user_id": 2,"score": 0.7}]},{"book_id": 2,"users": [{"user_id": 3,"score": 0.6},{"user_id": 4,"score": 0.8}]},{"book_id": 3,// empty recommendation"users": []}],"meta": {"count": 100,"coverage": 0.7}
}

mysql ddl如下:

create table if not exists `recommend_job`(`id` bigint unsigned not null auto_increment comment 'id',`result` json comment 'recommend result',`status` varchar(64) not null comment 'recommend job status',`create_time` timestamp not null default current_timestamp comment 'create time',`update_time` timestamp null default null on update current_timestamp comment 'update time',primary key(`id`)
)engine=innodb default charset=utf8mb4 comment 'recommend job table';

pg ddl 如下

create table if not exists "recommend_job" ("id" serial not null,"result" jsonb,"status" varchar(64) not null,"create_time" timestamp not null default current_timestamp,"update_time" timestamp null,primary key("id")
)

第二张用户表user

field_nametypedescription
idint用户id
namevarchar用户名字
ageint用户年龄

mysql ddl如下:

create table if not exists `user`(`id` bigint unsigned not null auto_increment comment 'id',`name` varchar(255) not null comment 'username',`age` int unsigned not null comment 'user age',`create_time` timestamp not null default current_timestamp comment 'create time',`update_time` timestamp null default null on update current_timestamp comment 'update time',primary key(`id`)
)engine=innodb default charset=utf8mb4 comment 'user table';

postgresql ddl如下

create table if not exists "user" ("id" serial not null,"name" varchar(255) not null,"age" int not null,"create_time" timestamp not null default current_timestamp,"update_time" timestamp null default null,primary key("id")
)

第三张书籍表book

field_nametypedescription
idint书籍id
namevarchar书籍名字
authorvarchar书籍作者

mysql ddl如下:

create table if not exists `book`(`id` bigint unsigned not null auto_increment comment 'id',`name` varchar(255) not null comment 'book name',`author` varchar(255) not null comment 'book author',`create_time` timestamp not null default current_timestamp comment 'create time',`update_time` timestamp null default null on update current_timestamp comment 'update time',primary key(`id`)
)engine=innodb default charset=utf8mb4 comment 'book table';

postgresql ddl 如下:

create table if not exists "book" ("id" serial not null,"name" varchar(255) not null,"author" varchar(255) not null,"create_time" timestamp not null default current_timestamp,"update_time" timestamp null default null,primary key("id")
)

sample data数据注入db,生成脚本如下

# -*- coding:utf8 -*-import pandas as pd
from sqlalchemy import create_engine
from pathlib import Path
import random, logging, os, math, jsonlogging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
random.seed(10)def get_score():return round(random.random(), 3)def gen_recs(users, books, rec_count, ratio, job_id=1):recommend = []sp_books = random.sample(books, k = math.floor(len(books)*ratio))for item in sp_books:sp_users = random.sample(users, k=min(len(users), rec_count))rec_users = [{"user_id": user[0], "score": get_score()} for user in sp_users]recommend.append({"book_id": item[0],"users": rec_users})result = json.dumps({"recommend": recommend,"meta": {"count": len(sp_books),"coverage": ratio}})return pd.DataFrame({"id": [job_id],"status": ["succeed"],"result": [result]})if __name__ == '__main__':users_file_path = Path('./users.txt')books_file_path = Path('./books.txt')users_df = pd.read_csv(users_file_path)books_df = pd.read_csv(books_file_path)res = gen_recs(users_df.values.tolist(), books_df.values.tolist(), 3, 0.8, 1)db_user = os.getenv("DB_USER")db_pass = os.getenv("DB_PASS")db_host = os.getenv("DB_HOST")db_port = os.getenv("DB_PORT")db_name = os.getenv("DB_NAME")db_url = f"mysql+pymysql://{db_user}:{db_pass}@{db_host}:{db_port}/{db_name}"engine = create_engine(db_url)# users_df.to_sql("user", engine, if_exists='replace', index=False)# books_df.to_sql("book", engine, if_exists='replace', index=False)#res.to_sql("recommend_job", engine, if_exists='replace', index=False)# save res to db# res.to_sql("recommend_job", engine, if_exists="append", index=False)

users.txt

id,name,age
1,A,10
2,B,11
3,C,12
4,D,9
5,E,10
6,F,13
7,G,12
8,H,10
9,I,11
10,J,12

books.txt

id,name,author
1,红楼梦,曹雪芹
2,活着,余华
3,1984,乔治.奥威尔
4,哈利.波特,J.K.罗琳
5,三体,刘慈欣
6,百年孤独,加西亚.马尔克斯
7,飘,玛格丽特.米切尔
8,动物农场,乔治.奥威尔
9,房思琪的初恋乐园,林亦含
10,三国演义,罗贯中
11,福尔摩斯侦探全集,阿.柯南道尔
12,白夜行,东野圭吾
13,小王子,圣埃克苏佩里
14,安徒生童话故事集,安徒生
15,天龙八部,金庸

mysql实验案例

输出每一本书对应的推荐用户列表,book_id对应所有的推荐用户list

select book_id, users from
recommend_job,
json_table(recommend_job.result, '$.recommend[*]' columns(book_id int path '$.book_id', users json path '$.users')) as t;

结果

book_id|users                                                                                          
-------+-----------------------------------------------------------------------------------------------10|[{"score": 0.328, "user_id": 1}, {"score": 0.25, "user_id": 9}, {"score": 0.953, "user_id": 8}]1|[{"score": 0.86, "user_id": 6}, {"score": 0.603, "user_id": 1}, {"score": 0.382, "user_id": 7}]7|[{"score": 0.175, "user_id": 5}, {"score": 0.303, "user_id": 10}, {"score": 0.363, "user_id": 88|[{"score": 0.613, "user_id": 8}, {"score": 0.044, "user_id": 4}, {"score": 0.004, "user_id": 1015|[{"score": 0.536, "user_id": 3}, {"score": 0.772, "user_id": 4}, {"score": 0.24, "user_id": 5}]
......

这里我们还可以将嵌套的users展开和user表进行join得到最终的每一个用户推荐书籍和相应的score

with rec_tbl as (
select t.book_id, t.user_id, t.score from
recommend_job,
json_table(recommend_job.result, '$.recommend[*]' columns(
book_id int path '$.book_id',
nested path '$.users[*]' columns(user_id int path '$.user_id',score float path '$.score'
)
)
) t)
select user.id user_id,book_id,score,user.name user_name,user.age user_age
from 
user left join
rec_tbl
on user.id = rec_tbl.user_id

结果

user_id|book_id|score|user_name|user_age|
-------+-------+-----+---------+--------+1|     10|0.328|A        |      10|1|      1|0.603|A        |      10|2|     12|0.567|B        |      11|2|     13|0.442|B        |      11|3|     15|0.536|C        |      12|3|      4|0.238|C        |      12|3|     13|0.773|C        |      12|
......

postgresql 实验案例

postgresql同样的业务场景下查询语句相比于mysql就更加简单

with rec_tbl as (select id,book_id,cast (jsonb_path_query(users, '$[*].user_id') as int) user_id,jsonb_path_query(users, '$[*].score') score
from(select id,jsonb_path_query(result, '$.recommend[*].book_id') book_id,jsonb_path_query(result, '$.recommend[*].users') usersfromrecommend_job) t
)
select user_id,book_id,score,name user_name,age user_age
from 
public.user left join
rec_tbl
on public.user.id = rec_tbl.user_id
order by user_id asc;

结果

user_id|book_id|score|user_name|user_age|
-------+-------+-----+---------+--------+1|1      |0.603|A        |      10|1|10     |0.328|A        |      10|2|13     |0.442|B        |      11|2|12     |0.567|B        |      11|3|2      |0.773|C        |      12|3|13     |0.773|C        |      12|3|3      |0.865|C        |      12|
......

总结

mysql以及postgresql针对json类型字段提供的处理方法会有差异,因此使用的时候需要注意这个点.其次实际开发过程中我们也可以不用使用如下这个表结构存储推荐job的结果

file_nametype
idint
statusvarchar
resultjson

我们完全可以在定义推荐结果表的时候将book_id, user_id和score 也定义为字段,这样也不必做比较复杂的json字段解析.

field_nametype
idint
statusvarchar
book_idint
user_idint
scorefloat

当然这个看具体项目需求,不过即便是需要对复杂的json数据进行解析,看完本文也不存在障碍了.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/850967.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

一个完整的java项目通常包含哪些层次(很全面)

1.View层(视图层) 职责:负责数据的展示和用户交互。在Web应用中,View层通常与HTML、CSS和JavaScript等技术相关。 技术实现:在Spring MVC中,View层可以使用JSP、Thymeleaf、FreeMarker等模板引擎来实现。…

MATLAB | 透明度渐变颜色条

hey 各位好久不见,今天提供一段有趣的小代码,之前刷到公众号闻道研学的一篇推送MATLAB绘图技巧 | 设置颜色条的透明度(附完整代码)(https://mp.weixin.qq.com/s/bVx8AVL9jGlatja51v4H0A),文章希…

机器学习周记(第四十二周:AT-LSTM)2024.6.3~2024.6.9

目录 摘要Abstract一、文献阅读1. 题目2. abstract3. 网络架构3.1 LSTM3.2 注意力机制概述3.3 AT-LSTM3.4 数据预处理 4. 文献解读4.1 Introduction4.2 创新点4.3 实验过程4.3.1 训练参数4.3.2 数据集4.3.3 实验设置4.3.4 实验结果 5. 基于pytorch的transformer 摘要 本周阅读…

免费,C++蓝桥杯等级考试真题--第11级(含答案解析和代码)

C蓝桥杯等级考试真题--第11级 答案:D 解析: A. a b; b a; 这种方式会导致a和b最终都等于b原来的值,因为a的原始值在被b覆盖前没有保存。 B. swap(a,b); 如果没有自定义swap函数或者没有包含相应的库,这个选项会编…

【C++题解】1389 - 数据分析

问题:1389 - 数据分析 类型:简单循环 题目描述: 该方法的操作方式为,如果要传递 2 个数字信息给友军,会直接传递给友军一个整数 n(n 是一个 10 位以内的整数),该整数的长度代表要传…

汇编语言LDS指令

在8086架构的实模式下,LDS指令(Load Pointer Using DS)用于从内存中加载一个32位的指针到指定寄存器和DS寄存器。我们来详细解释一下这条指令为什么会修改DS段寄存器。 LDS指令的功能 LDS指令格式如下: LDS destination, sourc…

程序猿大战Python——运算符

常见的运算符 目标:了解Python中常见的运算符有哪些? 运算符是用于执行程序代码的操作运算。常见的运算符有: (1)算术运算符:、-、*、/、//、% 、**; (2)赋值运算符&am…

树莓派 ubuntu linux 去除蓝牙历史配对信息

linux 去除蓝牙历史配对信息 在Linux系统中,蓝牙配对信息通常存储在/var/lib/bluetooth目录下的文件中。要删除所有的历史配对信息,你可以删除这个目录下的所有文件。 以下是一个命令行示例,用于删除蓝牙历史配对信息: sudo rm…

macOS - 终端快捷键

本文转自 Mac 上“终端”中的键盘快捷键 https://support.apple.com/zh-cn/guide/terminal/trmlshtcts/mac 以下基于系统版本 macOS Sonoma 14 文章目录 Mac 上“终端”中的键盘快捷键1、使用“终端”窗口和标签页2、编辑命令行3、在“终端”窗口中选择和查找文本4、使用标记和…

【Uniapp】uniapp微信小程序定义图片地址全局变量

错误写法: main.js Vue.prototype.$imgUrl 图片地址这么写之后 就发现压根不起作用;获取到的是undefined 正确写法: 返回函数,后面可以拼上OSS图片完整路径 Vue.prototype.$imgUrl (url) > {return ("https://地址…

[大师C语言(第二十四篇)]C语言指针探秘

引言 在C语言的学习和应用中,指针无疑是最重要、最难以掌握的概念之一。它为C语言提供了强大的功能和灵活性,同时也带来了不少的复杂性。本文将深入探讨C语言指针背后的技术,帮助你更好地理解和应用指针。 第一部分:指针的基本概…

探索Excel的隐藏功能:如何求和以zzz开头的列

哈喽,大家好,我是木头左! 步骤一:定位"zzz"开头的列 需要找到所有以"zzz"开头的列。在Excel中,你可以通过以下几种方法来实现: 手动查找:滚动查看列标题,找到…

Android——热点开关(优化中)

SoftAP打开与关闭 目录 1.三个名词的解释以及关系 Tethering——网络共享,WiFi热点、蓝牙、USB SoftAp——热点(无线接入点),临时接入点 Hostapd——Hostapd是用于Linux系统的软件,,支持多种无线认证和加密协议,将任…

LabVIEW进行图像拼接的实现方法与优化

在工业检测和科研应用中,对于大尺寸物体的拍摄需要通过多次拍摄后进行图像拼接。LabVIEW 作为强大的图形化编程工具,能够实现图像拼接处理。本文将详细介绍LabVIEW进行图像拼接的实现方法、注意事项和提高效率的策略。 图像拼接的实现方法 1. 图像采集…

c++引用的本质(反汇编角度分析)

目录 一、引用基础理论 二、 引用的本质 三、从反汇编角度进行分析 1.变量赋值 2.引用和指针初始化 3.通过引用和指针赋值 4.eaxd的作用 一、引用基础理论 在c中我们都知道,引用(&)就是变量的一个别名,它允许我们为已存…

Python魔法之旅专栏(导航)

目录 推荐阅读 1、Python筑基之旅 2、Python函数之旅 3、Python算法之旅 4、博客个人主页 首先,感谢老铁们一直以来对我的支持与厚爱,让我能坚持把Python魔法方法专栏更新完毕! 其次,为了方便大家查阅,我将此专栏…

C#操作MySQL从入门到精通(21)——删除数据

前言: 谈到数据库,大家最容易脱口而出的就是增删改查,本文就是来详细介绍如何删除数据。 本文测试使用的数据库如下: 1、删除部分数据 使用delete 关键字,并且搭配where条件使用,否则会导致表中数据全部被删除 string sql = string.Empty;if (radioButton_DeletePart…

保存图片奇怪的bug

今天发现一个奇怪的bug 这个的dpi是100de ,但是我取完切片之后,发现这个结果就变了

Vivado时序报告之Datasheet详解

目录 一、前言 二、Datasheet配置选项说明 2.1 Options 2.2 Groups 2.3 Timer Settings 2.4 Common Options 三、Datasheet报告 3.1 General Information 3.2 Input Ports Setup/Hold 3.3 Output Ports Clock-to-out 3.4 Setup between Clocks 3.5 Combinational…