背景

Microsoft SQL Server
对于数据平台的开发者来说越来越友好。比如已经原生支持XML很多年了,在这个趋势下,如今也能在SQLServer2016中使用内置的JSON。尤其对于一些大数据很数据接口的解析环节来说这显得非常有价值。与我们现在所做比如在SQL中使用CLR或者自定义的函数来解析JSON相比较,新的内置JSON会大大提高性能,同时优化了编程以及增删查改等方法。

   
那么是否意味着我们可以丢弃XML,然后开始使用JSON?当然不是,这取决于数据输出处理的目的。如果有一个外部的通过XML与外部交互数据的服务并且内外的架构是一致的,那么应该是使用XML数据类型以及原生的函数。如果是针对微型服务架构或者动态元数据和数据存储,那么久应该利用最新的JSON函数。

SQL Server2016 原生支持JSON

 

SQL Server 2005 开始支持 XML 数据类型,提供原生的 XML数据类型、XML
索引以及各种管理 XML 或输出 XML 格式的函数。

在 SQL Server 时隔 4
个主要版本
之后,终于在 Microsoft Ignite 2015
大会上正式宣布,新一代的 SQL Server 2016
正式支持现在最流行的数据交换格式— JSON(JavaScript Object Notation)。

 

SQL Server 2016
对JSON的支持并不是增加一个JSON数据类型,而是提供一个更轻便的框架,帮助用户在数据库里处理JSON格式数据。

用户不需要更变现有的表结构,因为SQL Server使用NVARCHAR
数据类型来存储JSON文件,并且跟现有技术相互兼容,比如全文搜索、列存储索引、in-memory
OLTP,应用程序不需要做任何修改

 

不需要使用JSON.Net这类工具分析和处理JSON数据,利用SQL
Server内置函数就可以处理JSON数据,轻松将查询结构输出为JSON格式,或者搜索JSON文件内容。

 


使用 JSON AUTO 输出JSON 格式

要将select语句的结果以JSON输出,最简单的方法是在后面加上 FOR JSON AUTO

测试版本

Microsoft SQL Server 2016 (CTP2.2) - 13.0.407.1 (X64)   Jul 22 2015 21:19:11   Copyright (c) Microsoft Corporation  Enterprise Evaluation Edition (64-bit) on Windows Server 2012 R2 Standard 6.3 <X64> (Build 9600: ) (Hypervisor) 

SELECT * FROM [dbo].[Client] 
GO

SELECT * FROM [dbo].[Client] FOR JSON AUTO
GO

 

金沙国际官网 1

 

我们可以把每列中显示的最大字符数 设置为8192

金沙国际官网 2

金沙国际官网 3

 


加上Root Key

如果想为FOR JSON 加上Root Key,可以使用ROOT选项来指定 Root Key 名称

SELECT * FROM [dbo].[Client] FOR JSON AUTO,ROOT('SUSU')
GO

金沙国际官网 4

 


使用JSON PATH 输出JSON格式

当想要自定义输出JSON格式结构的时候,必须用JSON PATH描述,若SELECT
的字段名称相同,必须用别名方式来重新命名字段名这样才可以继续查询

另外,如果字段的默认值为NULL,那么输出JSON时,JSON会忽略null的只。如果要显示null值,可以加上INCLUDE_NULL_VALUES
选项(同样适用于JSON AUTO字句)

select * from  [dbo].[Client]

--FOR JSON PATH
SELECT * FROM [dbo].[Client] WHERE ClientID =2
FOR JSON PATH

[{"ClientID":2,"Firstname":"Peter","Lastname":"Nielsen","Birthdate":"1998-05-19T00:00:00","Email":"Peter@126.com","PhoneNumber":"+86-16326269674","Birthplace":"Stockholm","SocialSecurityNumber":"1901531234"}]

--FOR JSON PATH
SELECT * FROM [dbo].[Client] WHERE ClientID =4
FOR JSON PATH,INCLUDE_NULL_VALUES

[{"ClientID":4,"Firstname":"kade","Lastname":null,"Birthdate":"1980-01-06T00:00:00","Email":"Lotte@SOHU.com","PhoneNumber":"+86-16326269674","Birthplace":"Aalborg","SocialSecurityNumber":"1061234"}]

金沙国际官网 5

 

 

更多关于JSON的功能

目前SQL Server 2016 CTP2
对于JSON的功能支持还是比较有限,例如内置处理JSON格式化的函数,

ISJSON(判断是否是JSON格式)、JSON_VALUE(分析JSON文件并提取出值)
、OPENJSON(将JSON文件转换为普通数据表)

这些功能要等到CTP3才能陆续推出

 

更多SQL Server2016好用的功能,敬请期待o(∩_∩)o 

SQL Server2016 原生支持JSON

 

SQL Server 2005 开始支持 XML 数据类型,提供原生的 XML数据类型、XML
索引以及各种管理 XML 或输出 XML 格式的函数。

在 SQL Server 时隔 4
个主要版本
之后,终于在 Microsoft Ignite 2015
大会上正式宣布,新一代的 SQL Server 2016
正式支持现在最流行的数据交换格式— JSON(JavaScript Object Notation)。

 

SQL Server 2016
对JSON的支持并不是增加一个JSON数据类型,而是提供一个更轻便的框架,帮助用户在数据库里处理JSON格式数据。

用户不需要更变现有的表结构,因为SQL Server使用NVARCHAR
数据类型来存储JSON文件,并且跟现有技术相互兼容,比如全文搜索、列存储索引、in-memory
OLTP,应用程序不需要做任何修改

 

不需要使用JSON.Net这类工具分析和处理JSON数据,利用SQL
Server内置函数就可以处理JSON数据,轻松将查询结构输出为JSON格式,或者搜索JSON文件内容。

 


使用 JSON AUTO 输出JSON 格式

要将select语句的结果以JSON输出,最简单的方法是在后面加上 FOR JSON AUTO

测试版本

Microsoft SQL Server 2016 (CTP2.2) - 13.0.407.1 (X64)   Jul 22 2015 21:19:11   Copyright (c) Microsoft Corporation  Enterprise Evaluation Edition (64-bit) on Windows Server 2012 R2 Standard 6.3 <X64> (Build 9600: ) (Hypervisor) 

SELECT * FROM [dbo].[Client] 
GO

SELECT * FROM [dbo].[Client] FOR JSON AUTO
GO

 

金沙国际官网 1

 

我们可以把每列中显示的最大字符数 设置为8192

金沙国际官网 2

金沙国际官网 3

 


加上Root Key

如果想为FOR JSON 加上Root Key,可以使用ROOT选项来指定 Root Key 名称

SELECT * FROM [dbo].[Client] FOR JSON AUTO,ROOT('SUSU')
GO

金沙国际官网 4

 


使用JSON PATH 输出JSON格式

当想要自定义输出JSON格式结构的时候,必须用JSON PATH描述,若SELECT
的字段名称相同,必须用别名方式来重新命名字段名这样才可以继续查询

另外,如果字段的默认值为NULL,那么输出JSON时,JSON会忽略null的只。如果要显示null值,可以加上INCLUDE_NULL_VALUES
选项(同样适用于JSON AUTO字句)

select * from  [dbo].[Client]

--FOR JSON PATH
SELECT * FROM [dbo].[Client] WHERE ClientID =2
FOR JSON PATH

[{"ClientID":2,"Firstname":"Peter","Lastname":"Nielsen","Birthdate":"1998-05-19T00:00:00","Email":"Peter@126.com","PhoneNumber":"+86-16326269674","Birthplace":"Stockholm","SocialSecurityNumber":"1901531234"}]

--FOR JSON PATH
SELECT * FROM [dbo].[Client] WHERE ClientID =4
FOR JSON PATH,INCLUDE_NULL_VALUES

[{"ClientID":4,"Firstname":"kade","Lastname":null,"Birthdate":"1980-01-06T00:00:00","Email":"Lotte@SOHU.com","PhoneNumber":"+86-16326269674","Birthplace":"Aalborg","SocialSecurityNumber":"1061234"}]

金沙国际官网 5

 

 

更多关于JSON的功能

目前SQL Server 2016 CTP2
对于JSON的功能支持还是比较有限,例如内置处理JSON格式化的函数,

ISJSON(判断是否是JSON格式)、JSON_VALUE(分析JSON文件并提取出值)
、OPENJSON(将JSON文件转换为普通数据表)

这些功能要等到CTP3才能陆续推出

 

更多SQL Server2016好用的功能,敬请期待o(∩_∩)o 

创建数据库

实例

    当使用查询这些已经有固定架构的JSON的数据表时,使用“FOR
JSON”
提示在你的T-SQL脚本后面,用这种方式以便于格式化输出。一下实例我使用了SQLServer
2016 Worldwide Importers sample
database,可以在GitHub上直接下载下来(下载地址)。看一下视图Website.customers。我们查询一个数据并格式化输出JSON格式:

SELECT [CustomerID]
      ,[CustomerName]
      ,[CustomerCategoryName]
      ,[PrimaryContact]
      ,[AlternateContact]
      ,[PhoneNumber]
      ,[FaxNumber]
      ,[BuyingGroupName]
      ,[WebsiteURL]
      ,[DeliveryMethod]
      ,[CityName]

 ,DeliveryLocation.ToString() as DeliveryLocation
      ,[DeliveryRun]
      ,[RunPosition]
  FROM [WideWorldImporters].[Website].[Customers]
  WHERE CustomerID=1
  FOR JSON AUTO

  

 

请注意我们有一个地理数据类型列(DeliveryLocation),这需要引入两个重要的变通方案(标黄):

首先,需要转换一个string字符,否则就会报错:

FOR JSON cannot serialize CLR objects. Cast CLR types explicitly into one of the supported types in FOR JSON queries.

其次,JSON采用键值对的语法因此必须指定一个别名来转换数据,如果失败会出现下面的错误:

Column expressions and data sources without names or aliases cannot be formatted as JSON text using FOR JSON clause. Add alias to the unnamed column or table.

确认了这些,改写的格式化输出如下:

[
    {
        "CustomerID": 1,
        "CustomerName": "Tailspin Toys (Head Office)",
        "CustomerCategoryName": "Novelty Shop",
        "PrimaryContact": "Waldemar Fisar",
        "AlternateContact": "Laimonis Berzins",
        "PhoneNumber": "(308) 555-0100",
        "FaxNumber": "(308) 555-0101",
        "BuyingGroupName": "Tailspin Toys",
        "WebsiteURL": "http://www.tailspintoys.com",
        "DeliveryMethod": "Delivery Van",
        "CityName": "Lisco",
        "DeliveryLocation": "POINT (-102.6201979 41.4972022)",
        "DeliveryRun": "",
        "RunPosition": ""
    }
]

  

 

当然也可以使用JSON作为输入型DML语句,例如INSERT/UPDATE/DELETE
语句中使用“OPENJSON”。因此可以在所有的数据操作上加入JSON提示。

如果不了解数据结构或者想让其更加灵活,那么可以将数据存储为一个JSON格式的字符类型,改列的类型可以使NVARCHAR
类型。Application.People 表中的CustomFields
列就是典型这种情况。可以用如下语句看一下表格格式这个列的内容:

declare @json nvarchar(max)

SELECT @json=[CustomFields]
FROM [WideWorldImporters].[Application].[People]
where PersonID=8

select * from openjson(@json)

  

 

结果集在表格结果中的显示:

金沙国际官网 11

 

用另一种方式来查询这条记录,前提是需要知道在JSON数据结构和关键的名字,使用JSON_VALUE
和JSON_QUERY
函数:

  SELECT
       JSON_QUERY([CustomFields],'$.OtherLanguages') as OtherLanguages,
       JSON_VALUE([CustomFields],'$.HireDate') as HireDate,
       JSON_VALUE([CustomFields],'$.Title') as Title,
       JSON_VALUE([CustomFields],'$.PrimarySalesTerritory') as PrimarySalesTerritory,
       JSON_VALUE([CustomFields],'$.CommissionRate') as CommissionRate
  FROM [WideWorldImporters].[Application].[People]
  where PersonID=8

  

 

在表格结果集中展示表格格式的结果:

金沙国际官网 12

 

这个地方最关心就是查询条件和添加索引。设想一下我们打算去查询所有2011年以后雇佣的人,你可以运行下面的查询语句:

SELECT personID,fullName,JSON_VALUE(CustomFields,'$.HireDate') as hireDate
FROM [WideWorldImporters].[Application].[People]
where IsEmployee=1
and year(cast(JSON_VALUE(CustomFields,'$.HireDate') as date))>2011

  

 

切记JSON_VALUE
返回一个单一的文本值(nvarchar(4000))。需要转换返回值到一个时间字段中,然后分离年来筛选查询条件。实际执行计划如下:

金沙国际官网 13

 

为了验证如何对JSON内容创建索引,需要创建一个计算列。为了举例说明,Application.People
表标记版本,并且加入计算列,当系统版本为ON的时候不支持。我们这里使用Sales.Invoices表,其中ReturnedDeliveryData
中插入json数据。接下来获取数据,感受一下:

SELECT TOP 100 [InvoiceID]
      ,[CustomerID]
      ,JSON_QUERY([ReturnedDeliveryData],'$.Events')
  FROM [WideWorldImporters].[Sales].[Invoices]

  

 

发现结果集第一个event都是“Ready for collection”:

金沙国际官网 14

 

然后获取2016年3月的发票数据:

SELECT [InvoiceID]
      ,[CustomerID]
      ,CONVERT(datetime, CONVERT(varchar,JSON_VALUE([ReturnedDeliveryData],'$.Events[0].EventTime')),126)
  FROM [WideWorldImporters].[Sales].[Invoices]
  WHERE CONVERT(datetime, CONVERT(varchar,JSON_VALUE([ReturnedDeliveryData],'$.Events[0].EventTime')),126)
       BETWEEN '20160301' AND '20160331'

  

实际执行计划如下:

金沙国际官网 15

 

    加入一个计算列叫做“ReadyDate”, 准备好集合表达式的结果:

ALTER TABLE [WideWorldImporters].[Sales].[Invoices]
ADD ReadyDate AS CONVERT(datetime, CONVERT(varchar,JSON_VALUE([ReturnedDeliveryData],'$.Events[0].EventTime')),126)

  

 

之后,重新执行查询,但是使用新的计算列作为条件:

SELECT [InvoiceID]
      ,[CustomerID]
      ,ReadyDate
  FROM [WideWorldImporters].[Sales].[Invoices]
  WHERE ReadyDate BETWEEN '20160301' AND '20160331'

  

 

执行计划是一样的,除了SSMS建议的缺失索引:

金沙国际官网 16

 

因此,根据建议在计算列上建立索引来帮助查询,建立索引如下:

/*
The Query Processor estimates that implementing the following index could improve the query cost by 99.272%.
*/
CREATE NONCLUSTERED INDEX IX_Invoices_ReadyDate
ON [Sales].[Invoices] ([ReadyDate])
INCLUDE ([InvoiceID],[CustomerID])
GO

  

 

我们重新执行查询验证执行计划:

金沙国际官网 17

 

有了索引之后,大大提升了性能,并且查询JSON的速度和表列是一样快的。

create database accountInfo/*创建账户信息数据库*/

总结:

本篇通过对SQL2016 中的新增的内置JSON进行了简单介绍,主要有如下要点:

 

  • JSON能在SQLServer2016中高效的使用,但是JSON并不是原生数据类型;
  • 如果使用JSON格式必须为输出结果是表达式的提供别名;
  • JSON_VALUE 和 JSON_QUERY 
    函数转移和获取Varchar格式的数据,因此必须将数据转译成你需要的类型。
  • 在计算列的帮助下查询JSON可以使用索引进行优化。

 

创建数据表

 1 /*定义主码、外码、和人数、余额的取值范围。*/
 2 /*创建储蓄所表*/
 3 create table bank(
 4     bank_ID int primary key identity(10001,1),/*从10001开始,每次增加1*/
 5     bank_name nvarchar(20),    /*储蓄所名称,*/
 6     bank_address nvarchar(40),
 7     bank_peopleNum int check(bank_peopleNum between 1 and 30),
 8     bank_city nvarchar(10)
 9 )
10 
11 /*创建账户表*/
12 /*帐户(编号,姓名,余额,建立日期,储蓄所编号)*/
13 create table account(
14     account_id int primary key identity(1,1),
15     account_name nvarchar(15),
16     account_balance int check(account_balance>= 0 ),
17     account_found_date char(8),
18     bank_ID int foreign key references bank(bank_id)
19 )
20 
21 /*创建借贷表*/
22 /*借贷(帐户,借贷类型,金额,日期)*/
23 create table borrow(
24     borrow_id int primary key identity(1,1),
25     account_ID int foreign key references account(account_ID),
26     type_borrow nvarchar(10),
27     money_sum int check(money_sum>=0),
28     date_borrow char(8)
29 )

 

插入数据

 1 --插入bank数据
 2 insert into bank values('长春南湖路','朝阳区',10,'长春市')
 3 insert into bank values('西安大路','朝阳区',13,'长春市')
 4 insert into bank values('幸福二路','南关区',11,'长春市')
 5 insert into bank values('幸福三路','南关区',12,'长春市')
 6 insert into bank values('桃林街','城关区',18,'灵宝市')
 7 
 8 --插入account数据
 9 insert into account values('赵大',10000,20170510,10001)
10 insert into account values('钱二',5000,20160120,10001)
11 insert into account values('张三',50000,20161120,10001)
12 insert into account values('李四',50000,20161120,10005)
13 insert into account values('孙五',500000,20161110,10005)
14 
15 --插入borrow数据
16 insert into borrow values(1,'短期',1000,20170516)
17 insert into borrow values(2,'长期',3000,20170416)
18 insert into borrow values(3,'长期',200000,20170510)

 

连接查询

--内连接
select account_name,account_balance,bank_name
from account , bank
where account.bank_ID=bank.bank_id and bank_name='长春南湖路'

--左外连接
select bank.bank_ID,count(account_id)'账户个数',sum(account_balance)'余额总数'
from bank left outer join account on(bank.bank_ID=account.bank_ID)
group by bank.bank_ID

 

 嵌套查询

select account_name,account_balance
from account
where bank_ID in(select bank_ID from bank where bank_name='长春南湖路')

 

select account_name
from account
where account_balance>(select MAX(account_balance)
from account where account_name='长春南湖路')

 

select*
from account as A,Bank as B
where A.bank_ID=B.bank_ID and B.bank_city in
(select bank_city
from bank
group by bank_city
having count(bank_ID)=(select min(Nm)
from(select bank_city,count(bank_ID)
from bank
group by bank_city
)as _bank(Cy,Nm)
)
);

 

select account_id from account
where account_id not in 
(select id from( select account_ID from borrow
group by account_ID ) as v(id)--,Nm))

 

组合查询

select account_id
from account except  (select account_ID from borrow )

 

创建及使用视图

 1 create view view1 as
 2 select account.bank_ID,
 3 sum(account_balance)'balanceSum'
 4 from bank,account
 5 where bank.bank_ID=account.bank_ID
 6 group by account.bank_ID
 7 
 8 select v1.bank_ID vid
 9 from(select max(v.balanceSum)as balanceSum from view1 v)
10 su,view1 v1
11 where v1.balanceSum=su.balanceSum

 

存储过程

--根据上面基本表的信息定义一个存储过程,完成下面功能:
--入口参数:储蓄所编号
--1 显示储蓄所信息
--2 如果没有帐户,删除该储蓄所记录
--3 如果帐户余额总额低于100万元,开一个新帐户
alter PROCEDURE pr_bank(@bank_id int)
as
BEGIN
    select *from bank 
    where bank_ID = @bank_id
    if (select count(*) from account where bank_id=@bank_id)=0
         delete  from bank where bank_ID=@bank_id
    if (select sum(account_balance) from account where bank_id=@bank_id group by bank_id)<1000000
        insert into account(bank_id) values(@bank_id)
END

exec pr_bank 10005

 

作者:耑新新,发布于  博客园

转载请注明出处,欢迎邮件交流:zhuanxinxin@foxmail.com

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图