环境:
- window 10 x64 专业版
- Microsoft SQL Server 2019 (RTM) Enterprise Edition (64-bit)
参照:
- 官方文档:《SQL Server 中的 JSON 数据》
- 官方文档:《JSON 函数 (Transact-SQL)》
- 官方文档:《使用 OPENJSON 分析和转换 JSON 数据 (SQL Server)》
- 官方文档:《使用 FOR JSON 将查询结果格式化为 JSON (SQL Server)》
一、SqlServer是如何支持Json的?
Sql Server 从 2016 开始支持了一些 json 操作,但在SqlServer中Json还是被存储为字符串,只不过是符合JSON格式的字符串会允许一些JSON特有的操作,如:json对象的操作函数、json对象与关系表格的相互转换等,下面先介绍json对象的操作函数,然后再来说一下json对象和关系表格的相互转换。
二、Json操作相关函数
2.1 判断字符串是否符合JSON格式规范 ISJSON()
ISJSON函数是按照JSON格式规范进行判断的,JSON的格式规范参照《介绍JSON》。
select ISJSON('{}') as '{}',
ISJSON('') as '空',
ISJSON('{"name":"xiaoming","age":20}') as '{...}',
ISJSON('[]') as '[]',
ISJSON('[{"name":"小明"}]') as '[{...}]'
2.2 从Json对象中提取标量值 JSON_VALUE
注意:这个函数是从JSON对象中提取标量值(字符串、数字或true/false)。
json中包含六类数据:0:null、1:字符串、2:数字、3:true/false、4:数组、5:对象
如果你试图用JSON_VALUE从json字符串中取出对象或数组,你将得到一个NULL(默认为null,严格模式下将报错)。
关于路径表达式:
- 在
JSON_VALUE
/OPENJSON
/JSON_QUERY
/JSON_MODIFY
这四个函数中你将会使用到路径表达式。- 路径表达式有两种模式
宽松(lax)
和严格(strict)
并且默认是宽松
模式。在宽松模式下,当你取值出错时(比如你用JSON_VALUE取一个对象或者是取一个不存在的属性),返回的是NULL。强制使用宽松模式的实例:
select JSON_VALUE('{}','lax $.name')
在严格模式下,当你取值出错时,它就直接报错。强制使用严格模式的实例:select JSON_VALUE('{}','strict $.name')
- 路径表达式语法:
使用
$
代替json对象本身;
取对象的属性使用$.prop
格式,如果属性名包含特殊格式,则使用"
包裹,如:$."first name"
;
取数组的语法示例'$[0].name'
、'$.arr[0].name'
;
declare @jsontext nvarchar(max);
set @jsontext='
{
"name": "小明",
"first name": "first xiaoming",
"age": 20,
"sex": null,
"info": {
"addr": "xiaominglu"
},
"books": [{
"name": "语文",
"score": 85.5
}, {
"name": "数学",
"score": 98
}]
}
';
select JSON_VALUE(@jsontext,'$.name') as '$.name',
JSON_VALUE(@jsontext,'$.abc') as '$.abc',
JSON_VALUE(@jsontext,'$.age') as '$.age',
JSON_VALUE(@jsontext,'$.sex') as '$.sex',
JSON_VALUE(@jsontext,'$.info') as '$.info',
JSON_VALUE(@jsontext,'$.info.addr') as '$.info.addr',
JSON_VALUE(@jsontext,'$.books') as '$.books',
JSON_VALUE(@jsontext,'$.books[0].name') as '$.books[0].name',
JSON_VALUE(@jsontext,'$.books[1].score') as '$.books[1].score',
JSON_VALUE(@jsontext,'$.books[2].name') as '$.books[2].name'
2.3 从Json字符串中提取对象或数组 JSON_QUERY
这个函数和
JSON_VALUE
是类似的,但它返回的是一个json对象,而不是标量值,如果你试图用JSON_QUERY函数返回一个标量值,那么你将得到一个NULL。
注意:因为这个函数返回的是一个json对象,所以可用在JSON_MODIFY的赋值中,以防止SqlServer自动转义成字符串。
declare @jsontext nvarchar(max);
set @jsontext='
{
"name": "小明",
"first name": "first xiaoming",
"age": 20,
"sex": null,
"info": {
"addr": "xiaominglu"
},
"books": [{
"name": "语文",
"score": 85.5
}, {
"name": "数学",
"score": 98
}]
}
';
select
JSON_QUERY(@jsontext) as '无path',
JSON_QUERY(@jsontext,'$') as '$',
JSON_QUERY(@jsontext,'$.name') as '$.name',
JSON_QUERY(@jsontext,'$.info') as '$.info',
JSON_QUERY(@jsontext,'$.abc') as '$.abc',
JSON_QUERY(@jsontext,'$.books') as '$.books',
JSON_QUERY(@jsontext,'$.books[0]') as '$.books[0]'
2.4 更改JSON字符串的内容 JSON_MODIFY
注意:这里只介绍宽松模式。
借助JSON_MODIFY
函数,我们可以实现对JSON对象的属性新增、删除、更新操作,以及扩展的改属性名等操作。如果我们操作的属性是一个非标量的话,我们还需要借助JSON_QUERY
函数。
2.4.1 使用JSON_MODIFY
更新JSON对象属性值
-- 更改json对象name属性值
DECLARE @info NVARCHAR(100)='{"name":"John","skills":["C#","SQL"]}'
PRINT @info
SET @info=JSON_MODIFY(@info,'$.name','Mike')
PRINT @info
2.4.2 使用JSON_MODIFY
给JSON对象添加属性
--给json对象添加surname属性
DECLARE @info NVARCHAR(100)='{"name":"John","skills":["C#","SQL"]}'
PRINT @info
SET @info=JSON_MODIFY(@info,'$.surname','Smith')
PRINT @info
2.4.3 使用JSON_MODIFY
删除JSON对象的属性
--删除json对象的name属性
DECLARE @info NVARCHAR(100)='{"name":"John","skills":["C#","SQL"]}'
PRINT @info
SET @info=JSON_MODIFY(@info,'$.name',NULL)
PRINT @info
2.4.4 当json对象的属性为非标量值(对象/数组)时的增删改操作
如果我们要更改的属性是一个数组或对象的话,我们需要使用JSON_QUERY
函数以防止系统将属性值更改为转义后的字符串。
下面是一个错误的示例:
-- 在John的技能表里添加"VB"技能并将它排在第一位
DECLARE @info NVARCHAR(100)='{"name":"John","skills":["C#","SQL"]}'
PRINT @info
SET @info=JSON_MODIFY(@info,'$.skills','["VB","C#","SQL"]')
PRINT @info
注意,上面输出的属性skills
是一个字符串,而不是一个数组。
正确的书写方法如下:
DECLARE @info NVARCHAR(100)='{"name":"John","skills":["C#","SQL"]}'
PRINT @info
SET @info=JSON_MODIFY(@info,'$.skills',JSON_QUERY('["VB","C#","SQL"]'))
PRINT @info
2.4.5 当json对象的属性为数组时的快速添加操作
如果我们想向数组中添加一个值的话,有一个快速的命令可以让我们使用,如下:
DECLARE @info NVARCHAR(100)='{"name":"John","skills":["C#","SQL"]}'
PRINT @info
SET @info=JSON_MODIFY(@info,'append $.skills','Azure')
PRINT @info
2.4.6 嵌套使用的 JSON_MODIFY
可以嵌套使用JSON_MODIFY
,看下面的示例:
DECLARE @info NVARCHAR(100)='{"name":"John","skills":["C#","SQL"]}'
PRINT @info
SET @info=JSON_MODIFY(JSON_MODIFY(JSON_MODIFY(@info,'$.name','Mike'),'$.surname','Smith'),'append $.skills','Azure')
PRINT @info
2.4.6.1 修改json对象的属性名称
我们可以嵌套使用JSON_MODIFY
函数,以实现修改json对象属性名称的目的,如下:
DECLARE @product NVARCHAR(100)='{"price":49.99}'
PRINT @product
SET @product=
JSON_MODIFY(
JSON_MODIFY(@product,'$.Price',CAST(JSON_VALUE(@product,'$.price') AS NUMERIC(4,2))),
'$.price',
NULL
)
PRINT @product
2.4.6.2 使json对象的属性值递增
我们可以嵌套使用JSON_MODIFY
函数,以实现将给定json对象的属性值递增的目的,如下:
DECLARE @stats NVARCHAR(100)='{"click_count": 173}'
PRINT @stats
SET @stats=JSON_MODIFY(@stats,'$.click_count',
CAST(JSON_VALUE(@stats,'$.click_count') AS INT)+1)
PRINT @stats
三、使用OPENJSON将json对象转换为关系表
openjson的完整语法为:
OPENJSON( jsonExpression [ , path ] ) [ <with_clause> ]
<with_clause> ::= WITH ( { colName type [ column_path ] [ AS JSON ] } [ ,...n ] )
下面,我们逐步探索OPENJSON的解析过程
3.1 直接使用OpenJson(’{}’)
默认情况下OPENJSON会将jsonExpression表示的json对象解析为table(key,value,type)
的表格形式,如下:
DECLARE @json NVarChar(2048) = N'{
"Null_value": null,
"String_value": "John",
"DoublePrecisionFloatingPoint_value": 45,
"DoublePrecisionFloatingPoint_value": 2.3456,
"BooleanTrue_value": true,
"BooleanFalse_value": false,
"Array_value": ["a","r","r","a","y"],
"Object_value": {"obj":"ect"}
}';
SELECT * FROM OpenJson(@json);
对于数组,openjson也默认将它转为这样的三列表格,如下:
DECLARE @json NVarChar(2048) = N'[
null,
"Jack",
"Jone",
"Tom",
15,
false,
[1,2,3],
{"age":18,"name":"xiaoming"}
]';
SELECT * FROM OpenJson(@json);
如果json对象为空对象或空数组的话,那么转换成的表格将没有数据,如下:
select * from OPENJSON('{}')
3.2 使用path参数将json对象中的指定部分进行转换
上面默认情况下,OPENJSON将json对象整体进行转换,我们可以添加path参数来控制仅转换json对象的一部分。
示例如下:
DECLARE @json NVARCHAR(4000) = N'
{
"path": {
"to": {
"sub-object": ["en-GB", "en-UK", "de-AT", "es-AR", "sr-Cyrl"]
}
},
"path2":[1,2,3]
}
';
SELECT *
FROM OPENJSON(@json,'$.path.to."sub-object"')
对于数组也是一样的:
DECLARE @json NVARCHAR(4000) = N'
[{
"name": "小明",
"age": 20,
"addr": "天明路"
},
{
"name": "小花",
"age": 18
}
]
';
SELECT *
FROM OPENJSON(@json,'$[0]')
3.3 使用with字句自定义转换后的表格形式
直接看示例:
DECLARE @json NVARCHAR(MAX) = N'[
{
"Order": {
"Number":"SO43659",
"Date":"2011-05-31T00:00:00"
},
"AccountNumber":"AW29825",
"Item": {
"Price":2024.9940,
"Quantity":1
}
},
{
"Order": {
"Number":"SO43661",
"Date":"2011-06-01T00:00:00"
},
"AccountNumber":"AW73565",
"Item": {
"Price":2024.9940,
"Quantity":3
}
}
]'
SELECT *
FROM OPENJSON ( @json )
WITH (
Number varchar(200) '$.Order.Number',
Date datetime '$.Order.Date',
Customer varchar(200) '$.AccountNumber',
Quantity int '$.Item.Quantity',
[Order] nvarchar(MAX) AS JSON
)
注意:
上面的OPENJSON是将数组转为表格,结果是两行数据,如果最外层是对象的话,那么转换后是一行数据。
说明:
使用OPENJSON函数将json转为表格形式后,就可以按照表格的特点进行其他操作了,比如:join、where条件等。。,
四、使用FOR JSON将查询结果输出为json
For Json 子句的完整语法为:
[ FOR <JSON>]
<JSON> ::=
JSON
{
{ AUTO | PATH }
[
[ , ROOT [ ( 'RootName' ) ] ]
[ , INCLUDE_NULL_VALUES ]
[ , WITHOUT_ARRAY_WRAPPER ]
]
}
4.1 使用FOR JSON AUTO 将关系表格输出为json
drop table if exists test;
go
create table test(
id int primary key,
name varchar(50),
age int,
sex varchar(50),
birth datetime
);
go
insert into test(id,name,age,sex,birth) values
(1,'小明',null,'male','2002-01-02'),
(2,'Jack',20,'male','2000-11-02'),
(3,'小花',19,'female','2001-01-16'),
(4,'Tom',18,'male','2002-04-02');
go
select * from test for json auto;
将上面的输出格式化后:
[{
"id": 1,
"name": "小明",
"sex": "male",
"birth": "2002-01-02T00:00:00"
}, {
"id": 2,
"name": "Jack",
"age": 20,
"sex": "male",
"birth": "2000-11-02T00:00:00"
}, {
"id": 3,
"name": "小花",
"age": 19,
"sex": "female",
"birth": "2001-01-16T00:00:00"
}, {
"id": 4,
"name": "Tom",
"age": 18,
"sex": "male",
"birth": "2002-04-02T00:00:00"
}]
4.2 使用For Json Path自定义
使用For Json Auto的输出大多是是满足我们使用的,但是当我们希望能更好的控制输出JSON的格式的话,我们可以试一试For Json Path。
drop table if exists test;
go
create table test(
id int primary key,
name varchar(50),
age int,
sex varchar(50),
birth datetime
);
go
insert into test(id,name,age,sex,birth) values
(1,'小明',null,'male','2002-01-02'),
(2,'Jack',20,'male','2000-11-02'),
(3,'小花',19,'female','2001-01-16'),
(4,'Tom',18,'male','2002-04-02');
go
select
id,
name,
age as 'detail.age',
sex as 'detail.sex',
birth as 'detail.ext.birth'
from test for json path;
将上面的输出格式化后:
[{
"id": 1,
"name": "小明",
"detail": {
"sex": "male",
"ext": {
"birth": "2002-01-02T00:00:00"
}
}
}, {
"id": 2,
"name": "Jack",
"detail": {
"age": 20,
"sex": "male",
"ext": {
"birth": "2000-11-02T00:00:00"
}
}
}, {
"id": 3,
"name": "小花",
"detail": {
"age": 19,
"sex": "female",
"ext": {
"birth": "2001-01-16T00:00:00"
}
}
}, {
"id": 4,
"name": "Tom",
"detail": {
"age": 18,
"sex": "male",
"ext": {
"birth": "2002-04-02T00:00:00"
}
}
}]
4.3 使用 INCLUDE_NULL_VALUES 选项将 NULL 值包含在 JSON 输出中
观察上面的输出,我们会发现,小明的age数据在输出为json的时候被忽略了,因为他的age为null。
这是默认的处理方式,如果我们想将null值也输出的话,可以使用INCLUDE_NULL_VALUES
。
drop table if exists test;
go
create table test(
id int primary key,
name varchar(50),
age int,
sex varchar(50),
birth datetime
);
go
insert into test(id,name,age,sex,birth) values
(1,'小明',null,'male','2002-01-02'),
(2,'Jack',20,'male','2000-11-02'),
(3,'小花',19,'female','2001-01-16'),
(4,'Tom',18,'male','2002-04-02');
go
select
id,
name,
age as 'detail.age',
sex as 'detail.sex',
birth as 'detail.ext.birth'
from test
where id=1
for json path,INCLUDE_NULL_VALUES
4.4 使用 ROOT 选项将根节点添加到 JSON 输出中
上面的输出的结果都是以数组展示的,因为数组和关系表之间可以直接去映射比较方便。
如果我们想将结果输出为对象形式的,我们可以试试Root
。
drop table if exists test;
go
create table test(
id int primary key,
name varchar(50),
age int,
sex varchar(50),
birth datetime
);
go
insert into test(id,name,age,sex,birth) values
(1,'小明',null,'male','2002-01-02'),
(2,'Jack',20,'male','2000-11-02'),
(3,'小花',19,'female','2001-01-16'),
(4,'Tom',18,'male','2002-04-02');
go
select
id,
name,
age as 'detail.age',
sex as 'detail.sex',
birth as 'detail.ext.birth'
from test
where id=1
for json path,INCLUDE_NULL_VALUES,Root('persons');
4.5 从 JSON 中删除方括号 - WITHOUT_ARRAY_WRAPPER 选项
一般我们的输出结果都是json数组(除了上面提到的
Root
选项),如果我们不想要外面那个方括号,我们可以试试WITHOUT_ARRAY_WRAPPER
。
drop table if exists test;
go
create table test(
id int primary key,
name varchar(50),
age int,
sex varchar(50),
birth datetime
);
go
insert into test(id,name,age,sex,birth) values
(1,'小明',null,'male','2002-01-02'),
(2,'Jack',20,'male','2000-11-02'),
(3,'小花',19,'female','2001-01-16'),
(4,'Tom',18,'male','2002-04-02');
go
select
*
from test
for json path,WITHOUT_ARRAY_WRAPPER;
将上面输出格式化后:
{
"id": 1,
"name": "小明",
"sex": "male",
"birth": "2002-01-02T00:00:00"
}, {
"id": 2,
"name": "Jack",
"age": 20,
"sex": "male",
"birth": "2000-11-02T00:00:00"
}, {
"id": 3,
"name": "小花",
"age": 19,
"sex": "female",
"birth": "2001-01-16T00:00:00"
}, {
"id": 4,
"name": "Tom",
"age": 18,
"sex": "male",
"birth": "2002-04-02T00:00:00"
}