MySQL JSON数据类型全解析(JSON datatype and functions)

JSON(JavaScript Object Notation)是一种常见的信息交换格式,其简单易读且非常适合程序处理。MySQL从5.7版本开始支持JSON数据类型,本文对MySQL中JSON数据类型的使用进行一个总结。

目录

一、MySQL中的JSON

[1.1 JSON数据格式](#1.1 JSON数据格式)

[1.2 创建JSON格式数据](#1.2 创建JSON格式数据)

[1.2.1 JSON类型列](#1.2.1 JSON类型列)

[1.2.2 json_array/json_object/cast函数](#1.2.2 json_array/json_object/cast函数)

二、JSON数据操作

[2.1 JSON数据查询](#2.1 JSON数据查询)

[2.1.1 json_extract()函数](#2.1.1 json_extract()函数)

[2.1.2 column->path写法](#2.1.2 column->path写法)

[2.1.3 json_value()函数](#2.1.3 json_value()函数)

[2.1.4 json_keys()函数](#2.1.4 json_keys()函数)

[2.2 JSON数据新增](#2.2 JSON数据新增)

[2.2.1 json_array_append()函数](#2.2.1 json_array_append()函数)

[2.2.2 json_array_insert()函数](#2.2.2 json_array_insert()函数)

[2.3 JSON数据更新](#2.3 JSON数据更新)

[2.3.1 json_replace()函数](#2.3.1 json_replace()函数)

[2.4 JSON数据同时新增和更新](#2.4 JSON数据同时新增和更新)

[2.4.1 json_set()函数](#2.4.1 json_set()函数)

[2.5 JSON数据删除](#2.5 JSON数据删除)

[2.5.1 json_remove()函数](#2.5.1 json_remove()函数)

[2.6 JSON数据合并](#2.6 JSON数据合并)

[2.6.1 json_merge_preserve()函数](#2.6.1 json_merge_preserve()函数)

[2.6.2 json_merge_patch()函数](#2.6.2 json_merge_patch()函数)

[2.7 判断元素否存在](#2.7 判断元素否存在)

[2.7.1 json_contains_path()函数](#2.7.1 json_contains_path()函数)

[2.7.2 json_search()函数](#2.7.2 json_search()函数)

[2.7.3 value member of(json_array)](#2.7.3 value member of(json_array))

[2.8 判断JSON对象之间的关系](#2.8 判断JSON对象之间的关系)

[2.8.1 json_contains()函数](#2.8.1 json_contains()函数)

[2.8.2 json_overlaps()函数](#2.8.2 json_overlaps()函数)

三、JSON数据与索引

[3.1 生成列索引(Generated Column Index)](#3.1 生成列索引(Generated Column Index))

[3.2 多值索引(Multi-valued Index)](#3.2 多值索引(Multi-valued Index))

四、将JSON转换为结构化数据

[4.1 json_table()函数](#4.1 json_table()函数)


一、MySQL中的****JSON

在MySQL中使用JSON类型的数据有2个好处:

  • 自动数据格式校验,无效的JSON格式会报错
  • 数据快速解析,MySQL提供了多种函数,可以快速读取和操作JSON格式的数据

1.1 JSON数据格式

在MySQL中,JSON数据是以字符串形式表现的,但是它有自己的解析规则,利用某些途径可以将字符串转换为JSON类型数据,这个过程叫做规范化(normalization),在规范化过程中,MySQL会对数据格式进行验证,若字符串不是有效的JSON格式,那么就会报错。

规范化除了数据格式验证,还会对数据进行一些预处理,例如JOSN对象的键值去重,排序,将boolean类型转换为小写等,规范化后的数据即被视为JSON格式数据。

JSON数据的有效格式有两种:

  • JSON数组,包含在[]中的一系列值,例如: [1, 2, 'a', 'b']
  • JSON对象,包含在{}中的键值对,键和值之间用冒号分隔(键必须是字符串),键值对之间以逗号分隔,如: {'key1':'value1', 'key2':'value2'}

在JSON对象中,键是不能重复的,如果出现相同的键,规范化时后值会覆盖前值(MySQL 8.0.3之前保留先出现的值)。

两种JSON也格式可以互相嵌套:

  • 1, 2, 'a', {'key1':'value1', 'key2':'value2'}\] --\> JSON数组中某个元素是JSON对象

1.2 创建JSON格式数据

MySQL中主要有下列几种方式对值进行规范化,并转换成JSON格式:

  • 将数据插入json类型的列中
  • json_array() 函数将一组值转换为json数组
  • json_object() 函数将一组键值对转换为json对象
  • cast(value as json) 会根据提供的字符串格式转换为JSON数组或JSON对象
1.2.1 JSON****类型列

将表中列的类型定义为json,则所有插入的数据都会被规范化为JSON格式数据,这是最常用的创建JSON类型数据方式。

复制代码
create table js(
id int primary key,
val json);

insert into js values(1, '[1,2,"a","b"]');
insert into js values(2, '{"key1":1, "key2":2, "key1":"value3"}');

select * from js;
  • create table语句中,指定列的类型为json,表示其只能接受JSON格式的数据
  • 两条insert 语句向JSON类型的列插入字符串时,会对字符串进行规范化并转换成JSON格式数据
  • 数据插入过程中,JSON对象中重复的键key1保留了后出现的值"value3"

如果插入的字符串不是合法的JSON格式,语句会报错:

复制代码
insert into js values(3, '[1,2,"a","b"');
  • 这里少了一个],语句报错:Invalid JSON text
1.2.2 json_array/json_object/cast****函数

通过 json_array() 函数可以将一系列值转换为JSON数组:

复制代码
select json_array(TRUE, FALSE, NULL, 'TRUE', 'FALSE', 'NULL');
  • 这里TRUE, FALSE, NULL在规范化过程中均被转换为小写
  • 而字符串'TRUE', 'FALSE', 'NULL'则保持不变

通过 json_object() 函数可以把字符串形式的键值对转换成JSON格式对象:

复制代码
select json_object('key2',2, 'key1',1, 'key1',3);
  • 返回的结果中,key1后出现的值3覆盖了先出现的值1
  • 因为要凑键值对,所以提供的值的数量必须是偶数
  • 规范化后,JSON对象是按键排序的,本例中key1被排到的前面

由于键必须是字符串,即使以数字形式提供,在规范化的过程中也会被转换为字符串:

复制代码
select json_object('key2',2, 'key1',1, 3, 'key1');
  • 第三个键值对以数字3作为键,规范化后,3被转换成字符串"3"并被排到了最前面

同样,cast(... as json)会根据提供的字符串格式,转换为JSON数组或JSON对象:

复制代码
select cast('[1,2,"abc"]' as json);??? -- 转换为JSON数组

select cast('{"key1":1, "key2":2, "key1":3}' as json);??? -- 转换为JSON对象

二、JSON数据操作

使用JSON格式的最大好处是MySQL提供了一系列操作函数,可以快速对JSON类型的数据进行查询/更新,不必再对整个字符串进行格式解析,在处理JSON数据时简便许多。

2.1 JSON****数据查询

数据被转换为JSON格式后,即可对其进行快速解析,高效提取数据。

2.1.1 **json_extract()**函数

json_extract()函数可以从JSON格式的数据中快速取出指定值,语法如下:

  • json_extract(js_array, 'KaTeX parse error: Undefined control sequence: \[ at position 1: \̲[̲n\]') 通过'[n]'的形式取出JSON数组中编号为n的元素(编号从0开始)
  • json_extract(js_array, 'KaTeX parse error: Undefined control sequence: \[ at position 1: \̲[̲m to n\]') 通过'[m to n]'的形式取出JSON数组中编号m到n的所有元素(编号从0开始)
  • json_extract(js_object, ' . k e y ′ ) 通 过 ′ .key') 通过' .key′)通过′.key'的形式取出JSON对象中键为key所对应的值
  • 'KaTeX parse error: Undefined control sequence: \[ at position 1: \̲[̲n\]'和'.key'还可以替换为'KaTeX parse error: Undefined control sequence: \[ at position 1: \̲[̲\*\]'和'.*'表示返回所有的值

从JSON数组取出单个值:

复制代码
select * from js;

select id, json_extract(val, '$[1]') from js;
  • json_extract(val, '$[1]')取出JSON数组中第二个元素
  • 如果目标是JSON对象,则返回null(id为2的记录)

从JSON数组中取出范围值:

复制代码
select id, json_extract(val, '$[1 to 2]') from js;
  • json_extract(val, '$[1 to 2]')取出JSON数组中2~3范围内的元素

从JSON对象中取出单个值

复制代码
select id, json_extract(val, '$.key1') from js;
  • json_extract(val, '$.key1')取出JSON数组中键为"key1"所对应的值
2.1.2 column->path****写法

json_extract函数还有一种更简便的column->path写法,用'->'或'->>'符号来替代json_extract():

  • json_extract(js_array, 'KaTeX parse error: Undefined control sequence: \[ at position 1: \̲[̲n\]') 等价于 js\_a...[n]'
  • json_extract(js_array, 'KaTeX parse error: Undefined control sequence: \[ at position 1: \̲[̲n to m\]') 等价于 ...[n to m]'
  • json_extract(js_object, ' . k e y ′ ) 等价于 j s _ o b j e c t − > ′ .key') 等价于 js\_object->' .key′)等价于js_object−>′.key'

上面示例的查询也可以用以下的简便写法

复制代码
select val->'$[1]' from js where id=1;

select val->'$[1 to 3]' from js where id=1;

select val->'$[*]' from js where id=1;
  • val->'$[1]' 返回JSON数组中第2个元素
  • val->'$[1 to 3]' 返回JSON数组中第2至4范围内元素
  • val->'$[*]' 返回数组中所有元素

JSON对象也一样:

复制代码
select val->'$.key1' from js where id=2;

select val->>'$.key1' from js where id=2;

select val->>'$.*' from js where id=2;
  • 注意符号->和->>的输出结果差异,->的结果是带引号的,如果想要的是不带引号的值,使用->>即可
  • ->>相当于joson_unqoute(json_extract(column, path)),即对结果又做了一次去除引号的操作。
  • val->>'$.*' 返回JSON对象中所有值
2.1.3 **json_value()**函数

json_value()函数也可以通过path查询指定元素的值,但是它还有额外的类型转换及异常处理功能,在使用上比json_extract()稍复杂,但功能更强。

语法为:json_value(json_doc, path [returning type] [{null|error|default value} on empty] [{null|error|default value} on error])

语法看起来比较复杂,其实就是多了返回值类型转换(returning type),空值处理(on empty),错误处理(on error)3个模块,下面通过几个示例演示该函数用法:

首先是最简单的用法,只需要提供JSON数据及path就可以提取特定位置的值,用法和json_extract函数一样:

复制代码
set @array1 = '[1, 2, "a", "b", "12.345"]';
set @object1 = '{"key1":"value1", "key2":"value2"}';

select @array1, json_value(@array1, '$[4]');

select @object1, json_value(@object1, '$.key1');

在提取数据基础上,通过新增returning type选项即可对返回值进行类型转换:

复制代码
select @array1, json_value(@array1, '$[4]' returning decimal(4,2));

select @array1, json_value(@array1, '$[4]' returning char);

select @array1, json_value(@array1, '$[4]' returning json);
  • 第一个select返回值的类型定义为decimal(4,2),保理2位小数,因此12.345返回了1.35。
  • 第二个select返回字符串类型,由于默认就是返回字符串类型,这个returning char可以省略
  • 第三个select返回JSON类型

on empty选项定义了在找不到数据时,函数该如何处理,有3种选项:

  • null on empty, 这是默认选项,找不到数据时返回null

  • default value on empty, 找不到数据时返回默认值value

  • error on empty, 函数抛出错误,由用户处理

    select @array1, json_value(@array1, '$[100]' null on empty);

    select @array1, json_value(@array1, '$[100]' default '没找到' on empty);

    select @array1, json_value(@array1, '$[100]' error on empty);

  • 上面3个选项分别演示了3种on empty的处理,null on empty是默认选项,可以省略

on error选项定义了当函数出错时,应该如何处理,注意这里的出错是指函数在运行过程中出错,例如类型转换错误,如果提供的JSON数据格式或者path格式错误,那么函数本身直接运行出错,不会触发on error选项,这里on error选项也有3种:

  • null on error, 默认选项,错误返回null

  • default value on error, 返回一个默认值,默认值必须要和returning type匹配

  • error on error, 函数抛出错误,由用户处理

    select @array1, json_value(@array1, '$[2]' returning decimal(4,2) null on error) convert_a_to_decimal;

    select @array1, json_value(@array1, '$[2]' returning decimal(4,2) default 56.78 on error) default_value_on_error;

    select @array1, json_value(@array1, '$[2]' returning decimal(4,2) error on error) error_on_error;

  • 上面3个select尝试将'a'转换为decimal类型,on error选项演示了3种不同的处理
  • 第二个选项defulat value,value必须和前面的returning type中的type匹配(这里是decimal(4,2))
**2.1.4 json_keys()**函数

json_extract()/json_value()函数返回的都是JSON对象的值,如果想返回键,则需要用json_keys()函数。json_keys()会以JSON数组的形式返回JSON对象中顶层的键,即将所有的键组成一个数组返回。

其语法为: json_keys(json_doc [, path]),当提供path参数时(JSON对象嵌套),会返回指定path处元素的顶层键

返回JSON对象的所有顶层键:

复制代码
select json_keys('{"key1": "value3", "key2": {"a":"b"}}');
  • 这里的顶层键有'key1','key2',其中'key2'的值又是一个JSON对象(嵌套)
  • 没有指定path参数,这里只返回了顶层的键

如果要返回key2值中的键,需要提供path参数'$.key2'

复制代码
select json_keys('{"key1": "value3", "key2": {"a":"b"}}', '$.key2');

2.2 JSON****数据新增

JSON对象的中的元素新增,可以通过json_arry_append(),json_array_insert()或json_insert()函数来完成,各函数行为略有不同。

**2.2.1 json_array_append()**函数

josn_arry_append() 会在JSON数组指定的位置添加新的数据,新增的数据与原位置的数据会合并为新的JSON数组(依然算1个元素),不会改变原JSON数据的元素个数,函数语法为:json_arry_append(json_doc, path, val [,path, val] ...)

复制代码
select * from js;

update js set val=json_array_append(val,'$[0]','x', '$[3]','y') where id=1;
update js set val=json_array_append(val,'$.key2','y') where id=2;

select * from js;
  • json_array_append(val,'KaTeX parse error: Undefined control sequence: \[ at position 1: \̲[̲0\]','x', '[3]','y') 在JSON数组第一个元素中增加一个'x',第三个元素中增加一个'y',数组的元素依然是4个
  • json_array_append(val,'$.key2','y') 在JSON对象键key2的值中附件一个'y'
**2.2.2 json_array_insert()**函数

josn_arry_insert() 会在JSON数组指定的位置添加新的数据,与json_array_append()不同的是,新增的数据会作为一个独立的元素,此函数会改变JSON数组中元素的个数,函数语法为:json_arry_insert(json_doc, path, val [,path, val] ...)

复制代码
select * from js;

update js set val=json_array_insert(val,'$[0]','x', '$[3]','y', '$[100]','z') where id=1;

select * from js;
  • json_array_insert(val,'KaTeX parse error: Undefined control sequence: \[ at position 1: \̲[̲0\]','x', '[3]','y', '$[100]','z'),插入了3个元素,JSON数组的元素由4个增加至7个
  • 第一个元素插入指定位置后,从这个位置开始,所有元素向后移动一位,这会改变后续元素编号,并影响后续的插入位置
  • 'KaTeX parse error: Undefined control sequence: \[ at position 1: \̲[̲0\]'位置插入'x'后,所有...[2]'变成了'KaTeX parse error: Undefined control sequence: \[ at position 1: \̲[̲3\]',因此函数第二个插入'[3]'是插在"a"的前面,而不是原'$[3]'元素"b"的前面
  • 函数第三个元素'$[100]',超出了数组长度,因此附加在数组的最后

**2.2.3 json_insert()**函数

json_insert() 函数可以对JSON数组或JSON对象新增元素,根据给定的path,如果元素不存在,则进行新增,如果元素已存在,则忽略,不做任何操作,即只新增不更新。语法为:json_insert(json_doc, path1, val1, [path2, val2 ...])

复制代码
select * from js where id=1;

select json_insert(val,'$[0]','x','$[4]','y') from js where id=1;
  • 对JSON数组操作时,第一个path 'KaTeX parse error: Undefined control sequence: \[ at position 1: \̲[̲0\]',指定插入'x',但因...[0]'已存在,因此忽略
  • 第二个path 'KaTeX parse error: Undefined control sequence: \[ at position 1: \̲[̲4\]',指定插入'y',由于...[3]',新增成功

对json对象操作时同样,已存在的键值忽略,不存在的键值新增:

复制代码
select * from js where id=2;

select json_insert(val,'$.key1','x','$.key3','y') from js where id=2;
  • JSON对象中键key1已存在,因此新值'x'被忽略
  • JSON对象中键key3不存在,因此新键值对{"key3":"y"}新增成功

2.3 JSON****数据更新

JSON数据的更新,你可以把JSON作为一个字符串,更新完后再整体赋值回去,但这种方法在JSON对象较大的时候可能效率较低,且解析成本也高。MySQL提供了json_replace()函数可以高效的完成指定路径上的元素更新。

**2.3.1 json_replace()**函数

json_replace() 函数可以对JSON数据进行原地(in-place update)更新,即用新的值替换旧值,其语法为 json_replace(json_doc, path1, new_val1, [path2, new_val2 ...]),此函数仅对已存在的值进行更新,对不存在的值直接忽略,即只更新不新增。

将json数组前两个值由1和2更新为'x'和'y':

复制代码
select * from js where id=1;

update js set val=json_replace(val,'$[0]','x','$[1]','y','$[5]','z') where id=1;

select * from js where id=1;
  • josn_replace函数通过'KaTeX parse error: Undefined control sequence: \[ at position 1: \̲[̲0\]'和'[1]'指定第1,2个元素
  • 这里$[5]指定更新了一个不存在的元素,因此忽略

同样,对于josn对象,只需要将path的写法换成'$.key'格式即可,下面将key1,key2的值分别更新为'value1'和'value2':

复制代码
select * from js where id=2;

update js set val=json_replace(val, '$.key1', 'value1', '$.key2','value2', '$.key3','value3') where id=2;

select * from js where id=2;
  • 这里提供了一个不存在的'$.key3',所以被忽略

2.4 JSON****数据同时新增和更新

如果需要同时进行更新和新增(例如数据同步),利用json_set() 函数可以同时完成对数据的更新和新增,对于已存在的元素更新,不存在的元素新增。在功能上类似MySQL的replace into或Oracle的merge into语句。

**2.4.1 json_set()**函数

json_set() 函数语法为:json_set(json_doc, path1, new_val1, [path2, new_val2 ...])

JSON数组中,同时更新和新增:

复制代码
select val from js where id=1;

select json_set(val,'$[0]','x','$[100]','z') from js where id=1;
  • 第一个path参数'$[0]',匹配数组中第一个元素,将1更新为'x'
  • 第一个path参数'$[100]',匹配数组中第100个元素,由于不存在,新增到数组最后

JSON对象中,同时更新和新增:

复制代码
select val from js where id=2;

select json_set(val,'$.key1','x','$.key3','z') from js where id=2;
  • 第一个path参数'$key1',匹配对象中键为'key1'的,将其值更新为'x'
  • 第二个path参数'$key3',未匹配到键,直接新增键值对

json_insert(), json_replace(), json_set() 3个函数的参数格式是完全相同的,但功能不同,区别总结如下:

  • json_insert() 仅新增,不更新
  • json_replace() 仅更新,不新增
  • json_set() 既更新又新增,相当于上面两个函数合体

2.5 JSON****数据删除

json_remove() 函数从JSON数据中删除指定的元素,语法为json_remove(json_doc, path1 [,path2 ...]),对于提供多个path参数的,每次删除后都会重新评估元素的位置。

**2.5.1 json_remove()**函数

从JOSN数组中删除指定位置的元素:

复制代码
select val from js where id=1;

select json_remove(val,'$[1]','$[2]') from js where id=1;
  • 这里指定删除了第2,第3个元素,对应位置是'KaTeX parse error: Undefined control sequence: \[ at position 1: \̲[̲1\]','[2]'
  • 注意在删除'KaTeX parse error: Undefined control sequence: \[ at position 1: \̲[̲1\]'位置的元素后,所有后续...[2]'删除的是新'KaTeX parse error: Undefined control sequence: \[ at position 1: \̲[̲3\]'位置的元素"b",而不...[3]'位置的元素"a"

对于JSON对象,只需指定要删除的键即可:

复制代码
select val from js where id=2;

select json_remove(val,'$.key1') from js where id=2;

除了json_remove()函数,json_merge_patch()在对JSON对象某个键最后出现的值赋予null时,也可以删除该键值对(后面会提到)。

2.6 JSON****数据合并

MySQL提供了两个函数可以合并多个JSON格式的数据:

  • json_merge_preserve()
  • json_merge_patch()

两个函数的行为略有不同,可以总结为json_merge_preserve()会保留重复值,json_merge_patch()会替换合并。

**2.6.1 json_merge_preserve()**函数

joson_merge_preserve() 在合并JSON数组时,会把所有元素简单的拼接成一个大数组:

复制代码
SELECT json_merge_preserve('[1, 2]', '[true, false]','["a","b"]');

SELECT json_merge_preserve('[1, 2]', '{"key1": "value1"}');
  • 2个示例中,所有的元素都保留了下来

如果合并的是JSON对象,json_merge_preserve()函数会保留所有非重复键值对,对于重复的键,会将其值合并为JSON数组保留下来

复制代码
SELECT json_merge_preserve('{"key1": "value1"}','{"key2": "value2"}');

SELECT json_merge_preserve('{"key1": "value1"}','{"key2": "value2","key1":"value3"}') duplicate_key1;

SELECT json_merge_preserve('{"key1": "value1"}','{"key2": "value2","key1":"value3"}','{"key1": null}') last_value_is_null;
  • 第2,3语句中,键"key1"均出现了多次,但所有的值都被合并成数组保留了下来
**2.6.2 json_merge_patch()**函数

joson_merge_patch() 在合并JSON数组时,会将每个参数视为独立的元素,并应用"最后的值胜出"原则,只选择最后一个元素,因此只要函数中包含JSON数组,只有最后一个值会保留下来:

复制代码
SELECT json_merge_patch('[1, 2]', '[true, false]','["a","b"]');

SELECT json_merge_patch('[1, 2]', '{"key1": "value1"}');

SELECT json_merge_patch('{"key1": "value1"}','{"key2": "value2"}','[1, 2]');
  • 3个示例中,都是只有最后一个元素被保留了下来

如果合并的都是JSON对象, json_merge_patch()函数会保留所有非重复键值对,对于重复的键,只保留最后出现的值(但最后出现的值不能为null,否则会删除该键值):

复制代码
SELECT json_merge_patch('{"key1": "value1"}','{"key2": "value2"}');

SELECT json_merge_patch('{"key1": "value1"}','{"key2": "value2","key1":"value3"}') duplicate_key;

SELECT json_merge_patch('{"key1": "value1"}','{"key2": "value2","key1":"value3"}','{"key1": null}') last_value_is_null;
  • 第2个语句中,key1出现了2次,保留了最后出现的"value3"
  • 第3个语句中,key1出现了3次,但最后出现的值为null,所以该键值被删除

如果最后的键值为null会删除该键值对,利用这个特性我们可以给json_merge_path可以用传入null值来删除指定键值对(也可以用json_remove)。

2.7 判断元素否存在

某些时候,我们并不需要提取数值,而仅仅想知道某个元素是否存在(例如作判断条件时),下列函数可以实现此类功能:

  • json_contains_path() 测试某个path是否存在
  • json_search() 通过值查询其具体path
  • value member of(json_array) 通过值测试是否存在于JSON数组中
**2.7.1 json_contains_path()**函数

josn_contains_path()函数可以用来测试指定的一个或多个path是否存在。语法为:json_contains_path(json_doc, one_or_all, path [,path ...]),当path存在时返回1,不存在时返回0(不是null)。

该函数第二个参数one_or_all有2种取值,代表2种测试行为:

  • one, 至少有1个path存在即返回1
  • all, 所有path都存在才返回1

判断指定path在数组中是否存在:

复制代码
set @array1 = '["a", "b", "c"]';
set @object1 = '{"key1":"value1", "key2":"value2"}';

select json_contains_path(@array1, 'one', '$[0]', '$[100]');

select json_contains_path(@array1, 'all', '$[0]', '$[100]');
  • 这里有1个不存在的path '$[100]',根据第二个参数one_or_all,这里分别返回1和0

判断指定path在对象中是否存在:

复制代码
select json_contains_path(@object1, 'one', '$.key1', '$.key100');

select json_contains_path(@object1, 'all', '$.key1', '$.key100');

json_contains_path()函数用来判断指定的path是否存在,如果想判断指定的元素(值)是否存在,可以用json_contains()函数,它可以用来测试两个对象的包含关系,当然也可以测试值。

**2.7.2 json_search()**函数

json_search()函数可以通过值来查询path,如果存在则返回其具体的path,不存在则返回null。语法为:json_search(json_doc, one_or_all, search_str [escape_char [,path]...),

第二个参数one_or_all控制返回path的搜索行为:

  • one, 返回第一个匹配的path,当找到第一个匹配的path时搜索即终止
  • all, 返回所有匹配的path

在提供search_str时,可以用%或_来代替任意多个或单个字符(和like中用法一样)。但是如果要匹配'%'或'_'字符本身,则要加上转义字符,即后面的参数escape_str,省略该参数或提供null值,则默认为

复制代码
set @array1 = '[1,2, 1, "abc", "abd", "xyz"]';
set @object1 = '{"key1":"abc", "key2":"abd"}';

select json_search(@array1, 'one', 'abc');

select json_search(@array1, 'all', 'ab%');
  • 第二个select采用模糊匹配,'ab%'配合参数'all'返回所有以ab开头的字符串的path

这里我想查找字符串中包含1个%的元素的path:

复制代码
set @array2 = '["a%c", "a%%c"]';

select json_search(@array2, 'all', 'a%c');

select json_search(@array2, 'all', 'a%c');
  • 第一个select中,'a%c'中的'%'会被理解成通配符,因此'a%%c'的path也返回了
  • 但我们只想匹配'%'字符本身,只需要用转义字符''转义即可,这也是第四个参数escape_char,这里省略了,默认就是''

若省略第四个参数escape_char,或者使用null,默认都是'',也可以显式指定其他转义字符

复制代码
select json_search(@array2, 'all', 'a%c',null);

select json_search(@array2, 'all', 'a@%c','@');

第五个可选参数path,可以用来测试指定path上是否是某值:

复制代码
select @array1;

select json_search(@array1,'all','abc',null,'$[3]');

select json_search(@array1,'all','abc',null,'$[4]');
  • 这里提供了第五个参数path,两个select分别测试指定位置上值是否是'abc'
2.7.3 value member of(json_array)

对于JSON数组,如果只是想知道某个值是否存在,还可以用value member of(json_array)来判断值是否包含在指定数组中(MySQL 8.0.17后可用),包含返回1,不包含则返回0:

复制代码
select val, 2 member of(val), 'x' member of(val) from js where id=1;

这种方式相对于上面的函数要简练许多,但功能也相对单一。

2.8 判断JSON对象之间的关系

当我们需要判断2个JSON对象间的关系,例如包含,重叠,可以利用json_contains()或json_overlaps()函数来完成

**2.8.1 json_contains()**函数

json_contains() 可以测试两个JSON对象间是否是包含关系(也可以来测试值是否存在),其语法为:json_contains(doc, candiate [, path]),该函数会测试candidate是否包含在doc中,如果提供了第三个可选参数path,那么即限定doc是否在path指定的路径处包含candiate,如果包含则返回1,不包含则返回0.

关于包含关系的测试,根据测试对象是JSON数组和对象,还存在下列规则:

  • 对于JSON数组,candidate中每个元素都包含在doc中,即是包含

  • 如果提供了path,那么candiate会被视为一个整体,必须在指定path存在才被视为包含

  • 对于JSON对象,candidate的键必须包含在doc的键中,且值被对应的键的值包含,才视为包含

    set @array='[[1,"z"], 2, "a", "b", 1, "z"]';

    set @candidate1='[2, "b"]';

    set @candidate2='[1, "z"]';

    select json_contains(@array, @candidate1);

    select json_contains(@array, @candidate2, '$[1]');

    select json_contains(@array, @candidate2, '$[0]');

  • 第一句select,"2", "b" 分别包含在@array的元素中(独立包含),因此返回1
  • 第2,3句select,由于提供了path参数,因此数组被视为1个整体,只有在指定path包含才算作包含

对于JSON对象,

复制代码
set @object='{"key1":["x","y"], "key2":2, "key3":3}';
set @candidate3='{"key1":"x", "key3":3}';
set @candidate4='{"key1":"z", "key3":3}';

select json_contains(@object, @candidate3);

select json_contains(@object, @candidate4);
  • 第1句select,@candidate3的键和值都包含在@object中,返回1
  • 第2句select,@candidate4的键都包含在doc中,但key1的值"z"不包含在@object中,因此返回0
**2.8.2 json_overlaps()**函数

相对于json_contains函数,json_overlaps只判断两个JSON对象是否包含相同的元素或键值对,包含则返回1,不包含则返回0,语法为:json_overlaps(json_doc1, json_doc2)

判断数组是否包含相同的元素(即是否有重叠):

复制代码
set @array1 = '[1, 2]';
set @array2 = '[1, 3]';
set @object1 = '{"key1":"value1", "key2":"value2"}';
set @object2 = '{"key1":"value1", "key3":"value3"}';
set @object3 = '{"key1":"x", "key3":"value3"}';

select json_overlaps(@array1, @array2);

判断对象是否包含相同键值对(是否重叠)

复制代码
select json_overlaps(@object1, @object2);

select json_overlaps(@object1, @object3);
  • 对于JSON对象,必须键值对都相同才算相同的元素
  • @object1和@object3包含相同的键,但是值不同,因此不算重叠

三、JSON数据****与索引

在MySQL的表中,JSON类型的列通常无法直接建立索引,你可以用虚拟生成列(Virtual Generated Columns),并根据该列来建立间接索引。但是在MySQL8.0.17版本后,对于JSON数组(JSON对象不行),可以建立多值索引(Multi-valued Index)。

3.1 生成列索引(Generated Column Index

对于JSON数据类型需要建立索引,可以对将经常查询的元素提取出来,作为一个虚拟的生成列,并在该列上建立索引,查询时通过虚拟列上索引即可快速定位数据。

虚拟列的语法是:

复制代码
col_name data_type [GENERATED ALWAYS] AS (expr)
?[VIRTUAL | STORED] [NOT NULL | NULL]
?[UNIQUE [KEY]] [[PRIMARY] KEY]
?[COMMENT 'string']
  • expr 是列的生成表达式,需要依赖其他列计算
  • virtual 代表该列不实例化,不消耗存储空间,每次用到该列时计算
  • stored 代表实例化存储,消耗存储空间,且每次更新其依赖列时,都会同时更新虚拟列数据

下面示例,b是根据a生成的虚拟列,并且在b上建立了索引idx:

复制代码
create table person(
a json,
b int generated always as (a->"$.id"),
key idx(b));

insert into person(a) values('{"id":1, "name":"Vincent"}'), ('{"id":2, "name":"Victor"}'), ('{"id":3, "name":"Grace"}');

这样就可以通过b列查询时即可利用索引提速,快速定位记录:

复制代码
explain select * from person where b=2;

3.2 多值索引(Multi-valued Index

MySQL 8.0.17版本引入了多值索引,可以直接对JSON类型列创建索引,但是仅限JSON数组。在传统二级索引中,一个索引记录对应一条数据记录。但在多值索引中,会根据JSON数组中的值建立多个索引,同时指向这一条记录,其底层原理依然是通过虚拟列完成的,只是对用户来说这一步是透明的。

在对JSON数组列建立索引前,需要先用cast(... as type array)将其由JSON数组类型转换为SQL数组类型:

复制代码
create table t(
a json,
key idx((cast(a as unsigned array)))
);

insert into t values('[1, 2, "3", 4]'), ('[5, "6", 7]'),('[8, 9, 10]');
  • 注意create table语句中cast函数外面需要额外一层括号
  • 由于这里限制的是unsigned array,因此json数组中元素必须可以转换为数字,例如插入字符"a"则会报错
  • 其他可能的type类型还有:date, datetime, decimal, double, signed, time, year等

在建立多值索引后,member of(), json_contains(), json_overlaps()函数在where条件中则可以利用多值索引来加速查询。

这里查询a列中包含3的记录,通过执行计划可以发现member of()函数使用了多值索引idx:

复制代码
select * from t where 3 member of(a);

explain select * from t where 3 member of(a);

**、将JSON转换为****结构化数据**

JSON是一种非结构化数据,不需要像数据库表那样预先定义列。有时我们需要又需要将JSON数据转换为结构化数据,存储到数据库的表中。

MySQL提供了json_table()函数,可以将JSON数据转换成类似表一样的结构化数据,转化后的数据类似一张虚拟表,可以直接用select查询或者与其他表连接。

4.1 json_table()函数

json_table()的语法为:

复制代码
JSON_TABLE(
??? expr,
??? path COLUMNS (column_list)
)?? [AS] alias


column_definition:
??? name FOR ORDINALITY
??? |? name type PATH string path [on_empty] [on_error]
??? |? name type EXISTS PATH string path
??? |? NESTED [PATH] path COLUMNS (column_list)


on_empty:
??? {NULL | DEFAULT json_string | ERROR} ON EMPTY


on_error:
??? {NULL | DEFAULT json_string | ERROR} ON ERROR

部分语法解释:

  • expr 即要转换的JSON源数据
  • path 是要转换的JSON数据范围,expr + path定义了转换的数据
  • column_definition是每一列的详细定义,共4种
  • name for ordinality 为每一行数据生成一个编号,从1开始,相当于一个auto_increment列
  • name type path string_path [on empty] [one_error] 指定某列数据的具体path,[on_empty] [on_error]逻辑可以参照上面的json_value()函数
  • name type exists path string path 根据指定位置是否有值将1和0放入name 列中
  • nested [path] path columns(column_list)

下面示例将由JSON对象组成的数组转化为表:

复制代码
set @array1 = '[ {"name":"Vincent", "age":30, "salary":1000}, {"name":"Victor", "age":6}, {"name":"Grace", "age":30, "salary":3000}]';

select? * from
json_table(
@array1,
'$[*]' columns(
id for ordinality,
name varchar(32) path '$.name',
age int path '$.age',
salary decimal(6,2) path '$.salary' default '0' on empty,
have_salary int exists path '$.salary')
) as person;
  • id for orinality 为每行数据生成一个编号,从1开始
  • salary 列default '0' on empty,在对象没有'$.salary'键时,默认赋值0
  • have_salary 通过判断 '$.salary'是否存在,分别显式1或0

以上即是MySQL中JSON的主要用法总结,覆盖了日常应用中常见处理,应用好JSON类型的数据管理可以大幅提升MySQL处理非结构化数据的能力。

相关推荐
阿巴斯甜6 小时前
Android 报错:Zip file '/Users/lyy/develop/repoAndroidLapp/l-app-android-ble/app/bu
android
Kapaseker6 小时前
实战 Compose 中的 IntrinsicSize
android·kotlin
xq95277 小时前
Andorid Google 登录接入文档
android
黄林晴8 小时前
告别 Modifier 地狱,Compose 样式系统要变天了
android·android jetpack
冬奇Lab21 小时前
Android触摸事件分发、手势识别与输入优化实战
android·源码阅读
城东米粉儿1 天前
Android MediaPlayer 笔记
android
Jony_1 天前
Android 启动优化方案
android
阿巴斯甜1 天前
Android studio 报错:Cause: error=86, Bad CPU type in executable
android
张小潇1 天前
AOSP15 Input专题InputReader源码分析
android
_小马快跑_1 天前
Kotlin | 协程调度器选择:何时用CoroutineScope配置,何时用launch指定?
android