mysql json类型和虚拟列总结


字段定义和插入

创建一个带有json字段的表

create table test10 (
	id int not null auto_increment comment 'id',
	name varchar(64) null default "" comment 'name',
	json_data json default null comment "json格式数据",
	primary key(id)
)

插入记录

insert into test10(id,name,json_data) values(1,"name1",'{"age":1,"des":"des1"}');
insert into test10(id,name,json_data) values(2,"name2",'{"age":2,"des":"des1"}');
insert into test10(id,name,json_data) values(3,"name3",'{"age":3,"des":"des3"}');

虚拟列和索引

添加json格式的数据列可以方便的查询

  • 并且虚拟列多占用了一份空间
  • 虚拟列的数据来自其他列,虚拟列不能直接修改,只能查询
  • 如果要修改只能改原始的字段
  • 可以对虚拟列建立索引,提升查询速度
#添加实体列
#添加一个普通列
ALTER TABLE hl.test2 ADD name varchar(100) NOT NULL;
#添加一个json列
ALTER TABLE hl.test2 ADD json_data2 json NOT NULL;



# column 的作用
# generated always 的作用
# virtual 的作用 

#添加虚拟列的语法[]里面的都可以省略,并且默认会被加上
ALTER TABLE hl.test2 ADD [column] v_age7 INT(2) [GENERATED ALWAYS] AS (json_data2->>"$.age") [VIRTUAL] NULL;

#最简单写法
ALTER TABLE hl.test2 ADD  v_age7 INT(2)  AS (json_data2->>"$.age");
#等价于
ALTER TABLE hl.test2 ADD  v_age7 INT(2)  AS (json_unquote(json_extract(`json_data2`,'$.age')));


#如果json字段的对应虚拟字段的值不能被转换,那么写入操作会被截断,然后报错
#估计写入过程是先写入json字段,然后同步带虚拟列,这时候如果装换出错会报错回滚
insert into test2(id,json_data1,json_data2,name) values(4,'{"age":"a1","age2":"a2"}','{"age":"a1","age2":"a2"}','name1');

虚拟列和普通列之间不能互转,虚拟转普通和普通转虚拟都会报下面的错误

'Changing the STORED status' is not supported for generated columns.

虚拟列数据来源不仅可以是json列,也可以引用普通字段

#虚拟列来自于普通列的装换
ALTER TABLE hl.test2 MODIFY COLUMN v_age10 VARCHAR(10) GENERATED ALWAYS AS (substring(name,0,51)) VIRTUAL NULL;

虚拟列修改

只能修改原始列的数据,不能直接修改,这种修改的修改数据量几乎是全量的,修改数据量偏大,更新大量数据的时候容易undo日志缓存区溢出

#虚拟列不能直接修改,异常信息:The value specified for generated column 'v_age2' in table 'test2' is not allowed.
update test2 set v_age2  = 11 where id =3;

#虚拟列只能通过修改原数据字段修改
update test2  set json_data2 = json_set(json_data2,'$.age',"22")   where id =3;

虚拟列索引

虚拟列创建索引和普通字段创建索引一样,查询效果也几乎一样
备注:没有索引普通列查询速度比json列映射的虚拟列快

#普通列加索引
CREATE INDEX test2_name_IDX USING BTREE ON hl.test2 (name);

#虚拟列加索引
CREATE INDEX test2_v_age_IDX USING BTREE ON hl.test2 (v_age);

不创建虚拟列,也可以对json结构创建索引

语法: add index 索引名字( (cast( json表达式 as 数据类型 array)) );
数据类型可选:unsigned/signed/char(N)等

这个用的比较少,生效的场景比较少,不推荐,为啥呢,因为不容易看出哪些字段上有索引

注意:索引要索引命中必须在数据参数位置填入和索引定义一样的表达式,不能通过jsonPath修正,不然找不到

注意2:虽然没有定义虚拟列其实效果和定义虚拟列一样,需要考虑老数据数据类型转换是否兼容

注意3:

#表结构
#json_data我打算存json
#json_data2我打算存数组
CREATE TABLE `test2` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `json_data` json DEFAULT NULL,
  `age` int GENERATED ALWAYS AS (json_unquote(json_extract(`json_data`,_utf8mb4'$.age'))) VIRTUAL,
  `name` varchar(100) GENERATED ALWAYS AS (json_unquote(json_extract(`json_data`,_utf8mb4'$.name'))) VIRTUAL,
  `json_data2` json DEFAULT NULL,
  PRIMARY KEY (`id`),
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;


#插入数据
INSERT  into test2(json_data) values('{"name":"zhansan1","age":11}'),('{"name":"lisi1","age":12}'),('{"name":"wanwu1","age":13}');


#json字段上创建索引1
alter table test2 add index idx_age1((cast(json_data->'$.age' as unsigned array)));
#json字段上创建索引2,区别在于json提取表达式
alter table test2 add index idx_age2((cast(json_data->>'$.age' as unsigned array)));

#命中索引1
explain SELECT  * from test2 where 2 MEMBER of(json_data->'$.age');
#名字索引2
explain SELECT  * from test2 where 2 MEMBER of(json_data->>'$.age');


#命中索引1
explain SELECT  * from test2 where  json_overlaps(json_data->'$.age','2');
#命中索引2
explain SELECT  * from test2 where  json_overlaps(json_data->>'$.age','2');
#无法命索引,没有第三个参数
explain SELECT  * from test2 where  json_overlaps(json_data,'2');

#命中索引1
explain SELECT  * from test2 where  json_contains(json_data->'$.age',"2");
#命中索引2
explain SELECT  * from test2 where  json_contains(json_data->>'$.age',"2");
#无法命中索引,索引要索引命中必须在数据字段参数填入和索引定义一样的表达式
explain SELECT  * from test2 where  json_contains(json_data,"2",'$.age');

#不能命中索引
explain SELECT  * from test2 where  json_data->'$.age' = 2;





#数组字段上创建索引3
alter table test2 add index idx_3((cast(json_data2 as unsigned array)));
#数组字段上创建索引4
alter table test2 add index idx_4((cast(json_data2 as char(32) array)));

#命中索引3
explain SELECT  * from test2 where 12 MEMBER of(json_data2);
#命中索引4
explain SELECT  * from test2 where “12” MEMBER of(json_data2);
#优先命中索引3,如果没有索引3也能命中索引4
explain SELECT  * from test2 where  json_contains(json_data2,"12",'$');
#优先命中索引3,如果没有索引3也能命中索引4
explain SELECT  * from test2 where  json_overlaps(json_data2,'12');

#直接表达式都用不上索引,所以还是建虚拟列把,那样可以明确索引使用列
explain SELECT  * from test2 where  json_data2->'$' ="12";
explain SELECT  * from test2 where  json_data2->'$' =12;


数据的写入和修改

全量修改

update users set json_data = '{"a":1}';

部分修改

json_insert

添加数据,不存在的才会插入,存在的忽略

格式: json_insert(json,path1,value1,path2,value2...)

set @testData = '{ "a": 1, "b": [2, 3]}';

#a已经存在则忽略,c不存在则添加,结果:{"a": 1, "b": [2, 3], "c": "[true, false]"}
select json_insert(@testData, '$.a', 10, '$.c', '[true, false]');

#上面插入的c是一个带引号的字符串,想要插入一个数组,必须进行转换,结果:{"a": 1, "b": [2, 3], "c": [true, false]}
select json_insert(@testData, '$.a', 10, '$.c', cast('[true, false]' as json));

#空的父节点是会被忽略,{"a": 1, "b": [2, 3]}
select json_insert(@testData, '$.a', 10, '$.c.c1', "c1");

#只能向直接子节点插入数据
select json_insert(@testData, '$.a', 10, '$.c', cast( '{"c1":"c1"}' as json ));
json_replace

修改数据,已经存在替换,不存在的值忽略。
格式: json_insert(json,path1,value1,path2,value2...)

set @jsonData = '{"a":"1","b":2,"c":[1,2]}';
#结果{"a": 2, "b": 2, "c": ["a", 2]}
select json_replace(@jsonData,'$.a',2,'$.c[0]',"a",'$.d',4);
json_set

添加或者修改数据,存在的修改,不存在添加
格式: json_set(json,path1,value1,path2,value2...)

set @testData = '{ "a": 1, "b": [2, 3]}';

#a已经存在则忽略,c不存在则添加,结果:{"a": 1, "b": [2, 3], "c": "[true, false]"}
select json_set(@testData, '$.a', 10, '$.c', '[true, false]');

#上面插入的c是一个带引号的字符串,想要插入一个数组,必须进行转换,结果:{"a": 1, "b": [2, 3], "c": [true, false]}
select json_set(@testData, '$.a', 10, '$.c', cast('[true, false]' as json));

#不存在父路径的时候修改会被忽略,结果{"a": 10, "b": [2, 3]}
select json_set(@testData, '$.a', 10, '$.c.c1', "c1");

#只能向直接子节点插入数据,结果{"a": 10, "b": [2, 3], "c": {"c1": {"c2": "c2"}}}
select json_set(@testData, '$.a', 10, '$.c', cast('{"c1":{"c2":"c2"}}' as json));

数组修改

json_array_append

向json数组追加数据,不是数组也能强行加一个元素然后变成数组,也可以不指定下标也能不指定下标
格式: json_array_append(json,path1,value1,path2,value2...)

set @testData = '["a", ["b", "c"], "d"]';

#在数组第二个元素的数组中追加1,结果:["a", ["b", "c", 1], "d"]
select json_array_append(@testData, '$[1]', 1);

#结果:[["a", 2], ["b", "c"], "d"]
select json_array_append(@testData, '$[0]', 2);

#结果:["a", [["b", 3], "c"], "d"]
select json_array_append(@testData, '$[1][0]', 3);

#多个参数,结果:[["a", 1], [["b", 2], "c"], "d"]
select json_array_append(@testData, '$[0]', 1, '$[1][0]', 2, '$[3]', 3);


#对非数组对象也能添加,会强行转出数组
set @testData = '{"a": 1, "b": [2, 3], "c": 4}';

#往b中追加,结果:{"a": 1, "b": [2, 3, "x"], "c": 4}
select json_array_append(@testData, '$.b', 'x');
#结果:{"a": 1, "b": [2, 3], "c": [4, "y"]}
select json_array_append(@testData, '$.c', 'y');


set @testData = '{"a": 1}';
#结果:[{"a": 1}, "z"]
select json_array_append(@testData, '$', 'z');
json_array_insert

只能向普通数组插入,并且必须指定下标,比json_array_append约束多

set @testData= '{"a":[1]}';
#结果:[{"a": 1}, "z"]
select json_array_insert(@testData, '$.a[0]', 'z');
#下面语法是可以的,结果#{"a": ["zz", "z", 1]}
select json_array_insert(@testData, '$.a[0]', 'z', '$.a[1]', 'zz');


#下面的语法是错误的,但是json_array_append是可以的
select json_array_insert(@testData, '$', 'z');
#下面语法是可以的,结果[{"a": [1]}, "z"]
select json_array_append(@testData, '$', 'z');


#下面的语法是错误的,但是json_array_append是可以的
select json_array_insert(@testData, '$.a', 'z');
#下面语法是可以的,结果{"a": [1, "z"]}
select json_array_append(@testData, '$.a', 'z');

数据合并

json_merge

合并的时候新旧值放到数组里面,支持一个值是数组一个值是子对象。

#对象合并重复的key变成数组,结果是{"id": 47, "age": [1, 2], "name": "x"}
select json_merge_patch('{"name": "x","age":1}', '{"id": 47,"age":2}');

#对象个非对象合并,直接变成数组,结果是[1, 2, {"id": 47}]
select json_merge('[1, 2]', '{"id": 47}');

#多个合并,结果:{"a": [1, 3, 5], "b": 2, "c": 4, "d": 6}
select json_merge('{ "a": 1, "b":2 }','{ "a": 3, "c":4 }', '{ "a": 5, "d":6 }');

#有null,也是变成数组,结果:{"a": 1, "b": [2, null]}
select json_merge('{"a":1, "b":2}', '{"b":null}');
json_merge_preserve

和json_merge一样,mysql8.0.3以后建议使用这个json_merge_preserve

json_merge_patch

合并的时候用新的值覆盖旧的值,不一样的都会保留

格式: json_merge_patch(json,json2,json3)

select json_merge_patch('{"name": "x","age":1}', '{"id": 47,"age":2}');

#都不是对象,取第二个,结果:true
select json_merge_patch('1', 'true');
select json_merge_patch('[2, 2]', '[true, false]');

#第一个不是对象,取第二个 ,结果:{"id": 47}
select json_merge_patch('[1, 2]', '{"id": 47}');

#第二个不是对象,取第二个 ,结果[1, 2]
select json_merge_patch( '{"id": 47}','[1, 2]');

#第二个覆盖第一个,结果:{"a": 3, "b": 2, "c": 4}
select json_merge_patch('{ "a": 1, "b":2 }','{ "a": 3, "c":4 }');

#多个合并,结果:{"a": 5, "b": 2, "c": 4, "d": 6}
select json_merge_patch('{ "a": 1, "b":2 }','{ "a": 3, "c":4 }', '{ "a": 5, "d":6 }');

#第二个有null,会删除该key,结果:{"a": 1}
select json_merge_patch('{"a":1, "b":2}', '{"b":null}');

#嵌套json也可以合并,结果:{"a": {"x": 1, "y": 2}}
select json_merge_patch('{"a":{"x":1}}', '{"a":{"y":2}}');

删除数据

json_remove

删除元素
格式:json_remove(json_doc, path1,path2,...)

#结果是 {}
select json_remove('{"a":1, "b":2}', '$.a', '$.b');

#结果是都是,{"b": 2}
select json_remove('{"a":1, "b":2}', '$.a' );
select json_merge_patch('{"a":1, "b":2}', '{"a":null}' );


数据的查询和获取

很多json函数都是mysql8以后才支持的,使用select version();查询mysql版本

数据提取函数

cast()

cast 可以把json字符串装换成json对象

格式:cast( json对象 as 类型)

描述
DATE 将value转换成'YYYY-MM-DD'格式
DATETIME 将value转换成'YYYY-MM-DD HH:MM:SS'格式
TIME 将value转换成'HH:MM:SS'格式
CHAR 将value转换成CHAR(固定长度的字符串)格式
SIGNED 将value转换成INT(有符号的整数)格式
UNSIGNED 将value转换成INT(无符号的整数)格式
DECIMAL 将value转换成FLOAT(浮点数)格式
BINARY 将value转换成二进制格式
JSON 将json格式
select  cast( '{"age":4,"des":"des4"}' as json)  from dual;
json_extract

提取json对象的属性,支持json字符串和json对象
格式:json_extract(json,path1,path2...)

#获取对象属性
select json_extract('{"age":4,"des":"des4"}',"$.age") from dual;

#获取数组元素
select json_extract("[1,2,3,4]","$[0]") from dual;

#可以同时获取多个属性
select json_extract("[1,2,3,4]","$[2]","$[3]") from dual;

#json_extract里面可以传入json对象或者json字符串
SELECT JSON_EXTRACT(json_data ,"$")  from test10;
SELECT JSON_EXTRACT('{"a":1}' ,"$")  from test10;


#-----------------------jsonPath的一些例子---------------------
#* 获取全部属性
select json_extract("[1,2,3,4]","$[*]") from dual;

# n to m 获取范围
select json_extract("[1,2,3,4]","$[1 to 2]") from dual;

#last表示最后一个
select json_extract("[1,2,3,4]","$[last-1 to last]") from dual;

#jsonPath语法中有特殊字符的时候,可以用双""表示整体,下面的例子中“a c”是一个特殊的整体
set @queryData='{"a": 1,"a c": 4}';
select json_extract(@queryData, '$."a"'), json_extract(@queryData, '$."a c"')from dual;

#支持父路径*号,$**.xxx
select json_extract('{"a": {"b": 1}, "c": {"b": 2}}', '$**.b');
-->和-->>,可以用于json属性提取

区别在-->不会去掉引号,-->>会去掉引号
需要注意箭头表达式不适合原始字符串json,只适合json对象

#测试数据,结果是: "jack"	jack
insert into test10(id,json_data) values(10,'{"empno": 1001, "ename": "jack"}');
#引号测试
select
	json_data->'$.ename',
	json_data->>'$.ename'
from
	test10
where
	id = 10;
	
#下面的写法是不行的,所以箭头表达式不适合原始字符串json,只适合json对象
select
	"{}"->'$',
	"{}"->>'$'
from
	test10
where
	id = 10;
	
#这也不行?明明是对象
select cast( @testData as json ) ->> "$.age";
json_unquote和json_quote

区别在于是否保留引号json_unquote可以和别的json提取函数和箭头表达式配合使用

json_unquote可以接收json字符串或者是json对象
json_quote只能接受json字符串

select
	json_unquote(json_data->'$.ename'),
	json_unquote(JSON_EXTRACT(json_data,"$.ename"))
from
	test10
where
	id = 10;
	
	
#都可接受json字符串,但是json_quote不能接受json对象
select
	json_unquote("{}"),
	json_quote("{}")
from
	test10
where
	id = 10;

#保存错
select
	json_quote(json_data)
from
	test10
where
	id = 10;
	

查询相关函数

json_contains_path

判断路径是否存在
格式:json_contains_path(json_doc,"one/all", path1,path2 …)

set @testData = '{"a": 1, "b": 2, "c": {"d": 4}}';

#判断存在一个就行,结果是0
select json_contains_path(@testData, 'one', '$.a', '$.e');

#判断所有path都存在,结果是0
select json_contains_path(@testData, 'all', '$.a', '$.e');
json_contains

判断值是否存在,如果value是一个json,并且有多个值,那么是and的关系
格式:json_contains(json_doc,value,[path])

#json_contains,判断值是否存在
set @testdata = '{"a": 1, "b": 2, "c": {"d": 4}}';

#可以查询单层对象
select json_contains(@testdata, '{"a":1}');
#可以查询多层嵌套的的对象
select json_contains(@testdata, '{"c":{"d": 4}}');
#可以指定查询path,不指定的时候默认是根
select json_contains(@testdata, '{"d": 4}',"$.c");

#查询普通的值
select json_contains(@testdata,"1", '$.a');
select json_contains("1", "1","$");

#可以查询数组
set @testdata= '[1, "a", 1.02]';

#可以不指定下标,表示任意元素
select json_contains(@testdata, '"a"');
#可以指定下标查询指定数组位置
select json_contains(@testdata, '"a"',"$[0]");

#结果是1
select  json_contains(@testdata,'{"a":1,"b":"2"}');
#结果是0
select  json_contains(@testdata,'{"a":1,"b":2}');
json_overlaps

比较两个json是否有重叠,只需要查询对象任意一个属性匹配就行,需要属性和值都相等,类似or
格式:json_overlaps(json,queryjson)

select * from test10 where json_overlaps(json_data,'{"age":1,"des":"des3"}');
#等价于
select * from test10 where json_data ->> "$.age" = 1 or  json_data ->> "$.des" = "des3";

#测试
set @jsonData = '{"age":1,"des":"des3"}';
#结果是1
select  json_overlaps(@jsonData,'{"age":1,"des":3}');
#结果是1
select  json_overlaps(@jsonData,'{"age":1,"des":2}');
member of

判断元素是否存在,和类似 exists 和contans有什么区别?
格式:xxx member of(json)

#对json对象使用
select * from test10  where "des1" member of(json_data -> "$.des");

#对json数组使用
select * from test10 where 1 member of(json_data);
select * from test10 where 1 member of(json_data->"$[0]") ;

分组聚合

json_arrayagg()

分组查询中,json_arrayagg可以把非分组字段聚合成数组

#测试数据
CREATE TABLE `test1` (
  `id` int NOT NULL AUTO_INCREMENT COMMENT 'id',
  `name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT '' COMMENT 'name',
  `key` varchar(64) DEFAULT NULL COMMENT 'json格式数据',
  `value` varchar(100) DEFAULT NULL,
  PRIMARY KEY (`id`)
);
INSERT INTO hl.test1(id, name, `key`, value)VALUES(1, '张三', 'key1', 'value1');
INSERT INTO hl.test1(id, name, `key`, value)VALUES(2, '张三', 'key2', 'value2');
INSERT INTO hl.test1(id, name, `key`, value)VALUES(3, '李四', 'key3', 'value3');
INSERT INTO hl.test1(id, name, `key`, value)VALUES(4, '李四', 'key4', 'value4');

#测试json_arrayagg
SELECT  name,json_arrayagg(`key`)  as `kyes` from test1 group by name;
SELECT  name,json_arrayagg(`value`)  as `values` from test1 group by name;
json_objectagg()

分组查询中,json_objectagg把非分组字段聚合成json对象

SELECT  name,json_objectagg(`key`,`value`)  as a from test1 group by name;

其他信息获取

json_keys

得到当前json的最外层所有key
格式:json_keys(json,[path])

#结果:["a", "b"]
select json_keys('{"a": 1, "b": {"c": 30}}');

#结果:["c"]
select json_keys('{"a": 1, "b": {"c": 30}}', '$.b');

通过值去查询path
格式:json_search(json,"all/one","搜索的值")
测算表test10如图

image-20240716102841172


#结果是"$.des"
select json_search(json_data,"all","des1") from test10;

#结果是"$.ename"
select json_search(json_data,"all","jack") from test10;

#结果是null,不能查到数字?
select json_search(json_data,"all",1) from test10;
json_value

提取json 的值和 json_extract的区别?
格式:json_value(json,path,returntype,“路径是空的时候的默认值”,“路径错误的时候的默认值”)

select json_value(json_data,"$.des") from test10;

#后面三个参数不能用?
select json_value(json_data,"$.des","string","0","0") from test10;  
json_depth

获取json的深度

#空json是1
select  json_depth("{}") from dual;

#普通一层的json对象是2
select  json_depth('{"a":1}') from dual;

#对象里面在有数组是3层
select  json_depth('{"a":1,"b":[1,2]}');
json_length

json_length()得到元素个数


#结果4
select json_length('[1,2,3,4]') from dual;
#结果0
select json_length('{}') from dual;
#结果2
select json_length('{"a":1,"b":2}') from dual;
json_valid

判断json是否有效,貌似只要是有两层引号就会认为有效

# 结果是1
select 	json_valid("{}") from dual;

# 结果是0 ,如果把a改成1 结果是1
select 	json_valid("a") from dual;

# 结果是1 ?
select 	json_valid('"a"') from dual;

#结果也是1 ?
SELECT JSON_VALID('false'), JSON_VALID('true');

#结果是 0
select 	json_valid('a') from dual;


#结果:1
select json_valid('{"a": 1}');
#结果:0
select json_valid('hello');
#结果:1
select json_valid('"hello"');
json_type

得到类型

#结果array
select json_type('[1,2,3,4]') from dual;

#结果 OBJECT
select json_type('{}') from dual;
#OBJECT
select json_type('{"a":1,"b":2}') from dual;

#STRING
select json_type('""') from dual;
#STRING
select json_type('"1"') from dual;

有关列长度和记录长度

varchar的长度限制16383,是256*256/4=16384,每添加一个字段需要占用一个字符,16383是在只有一个varchar字段的时候的极限,16383不仅是一个字段的极限也是表记录的极限。

text的上限是65535,并且text是存在行记录以外的,所以它可以突破16383的极限。

相关文章json字效率和普通字段对比

https://www.cnblogs.com/cxygg/p/18293968

posted on 2024-07-17 17:36  zhangyukun  阅读(179)  评论(0编辑  收藏  举报

导航