使用内置函数 (SQL Server) 验证、查询和更改 JSON 数据

适用于: SQL Server 2016 (13.x) 及更高版本 Azure SQL 数据库Azure SQL 托管实例

JSON 的内置支持包括本文简要介绍的下列内置函数。

  • ISJSON 测试字符串是否包含有效 JSON。
  • JSON_VALUE 从 JSON 字符串中提取标量值。
  • JSON_QUERY 从 JSON 字符串中提取对象或数组。
  • JSON_MODIFY 更新 JSON 字符串中属性的值,并返回已更新的 JSON 字符串。

本文要求使用 AdventureWorks2022 示例数据库,你可从 Microsoft SQL Server 示例和社区项目主页下载。

此页上的示例 JSON 文本

此页上的示例使用与以下示例中所示内容类似的 JSON 文本:

{
    "id": "DesaiFamily",
    "parents": [
        { "familyName": "Desai", "givenName": "Prashanth" },
        { "familyName": "Miller", "givenName": "Helen" }
    ],
    "children": [
        {
            "familyName": "Desai",
            "givenName": "Jesse",
            "gender": "female",
            "grade": 1,
            "pets": [
                { "givenName": "Goofy" },
                { "givenName": "Shadow" }
            ]
        },
        {
            "familyName": "Desai",
            "givenName": "Lisa",
            "gender": "female",
            "grade": 8
        }
    ],
    "address": {
        "state": "NY",
        "county": "Manhattan",
        "city": "NY"
    },
    "creationDate": 1431620462,
    "isRegistered": false
}

此 JSON 文档包含嵌套的复杂元素,存储在下面的示例表中:

CREATE TABLE Families (
    id INT identity CONSTRAINT PK_JSON_ID PRIMARY KEY,
    doc NVARCHAR(MAX)
);

使用 ISJSON 函数验证 JSON 文本

ISJSON 函数测试字符串是否包含有效 JSON。

下面的示例将返回 JSON 列包含有效 JSON 文本的行。 如果没有显式 JSON 约束,则可在 NVARCHAR 列中输入任意文本:

SELECT *
FROM Families
WHERE ISJSON(doc) > 0;

有关详细信息,请参阅 ISJSON (Transact-SQL)

使用 JSON_VALUE 函数从 JSON 文本中提取值

JSON_VALUE 函数从 JSON 字符串中提取标量值。 下面的查询会返回其中 id JSON 字段与值 DesaiFamily 匹配的文档,按 citystate JSON 字段排序:

SELECT JSON_VALUE(f.doc, '$.id') AS Name,
    JSON_VALUE(f.doc, '$.address.city') AS City,
    JSON_VALUE(f.doc, '$.address.county') AS County
FROM Families f
WHERE JSON_VALUE(f.doc, '$.id') = N'DesaiFamily'
ORDER BY JSON_VALUE(f.doc, '$.address.city') DESC,
    JSON_VALUE(f.doc, '$.address.state') ASC

此查询结果显示在下表中:

名称 City
DesaiFamily NY Manhattan

有关详细信息,请参阅 JSON_VALUE (Transact-SQL)

使用 JSON_QUERY 函数从 JSON 文本中提取对象或数组

JSON_QUERY 函数从 JSON 字符串中提取对象或数组。 下面的示例演示了如何在查询结果中返回 JSON 片段。

SELECT JSON_QUERY(f.doc, '$.address') AS Address,
    JSON_QUERY(f.doc, '$.parents') AS Parents,
    JSON_QUERY(f.doc, '$.parents[0]') AS Parent0
FROM Families f
WHERE JSON_VALUE(f.doc, '$.id') = N'DesaiFamily';

此查询结果显示在下表中:

Address 父项 Parent0
{ "state": "NY", "county": "Manhattan", "city": "NY" } [ { "familyName": "Desai", "givenName": "Prashanth" }, { "familyName": "Miller", "givenName": "Helen" } ] { "familyName": "Desai", "givenName": "Prashanth" }

有关详细信息,请参阅 JSON_QUERY (Transact-SQL)

分析嵌套式 JSON 集合

通过 OPENJSON 函数,可将 JSON 子数组转换为行集,然后将其与父元素联接在一起。 例如,可返回所有家庭文档,并将其与存储为内部 JSON 数组的 children 对象“联接”起来:

SELECT JSON_VALUE(f.doc, '$.id') AS Name,
    JSON_VALUE(f.doc, '$.address.city') AS City,
    c.givenName,
    c.grade
FROM Families f
CROSS APPLY OPENJSON(f.doc, '$.children') WITH (
    grade INT,
    givenName NVARCHAR(100)
) c

此查询结果显示在下表中:

名称 City givenName 年级
DesaiFamily NY Jesse 1
DesaiFamily NY Lisa 8

将返回两行,因为一个父行与通过分析子级子数组的两个元素产生的两个子行联接在一起。 OPENJSON 函数分析 doc 列中的 children 片段,并返回每个元素中的 gradegivenName 作为一个行集。 此行集可以与父文档进行联接。

查询嵌套式分层 JSON 子数组

可应用多个 CROSS APPLY OPENJSON 调用以查询嵌套式 JSON 结构。 本示例中使用的 JSON 文档具有名为 children 的嵌套数组,其中每个子级都有 pets 的嵌套数组。 下面的查询会分析每个文档中的子级,将每个数组对象作为行返回,然后分析 pets 数组:

SELECT c.familyName,
    c.givenName AS childGivenName,
    p.givenName AS petName
FROM Families f
CROSS APPLY OPENJSON(f.doc) WITH (
    familyName NVARCHAR(100),
    children NVARCHAR(MAX) AS JSON
) AS a
CROSS APPLY OPENJSON(children) WITH (
    familyName NVARCHAR(100),
    givenName NVARCHAR(100),
    pets NVARCHAR(max) AS JSON
) AS c
OUTER APPLY OPENJSON(pets) WITH (givenName NVARCHAR(100)) AS p;

首次进行 OPENJSON 调用会使用 AS JSON 子句返回 children 数组的片段。 此数组片段将提供给第二个 OPENJSON 函数,其将返回 givenName、每个子级的 firstName 以及 pets 的数组。 pets 的数组将提供给第三个 OPENJSON 函数,其返回宠物的 givenName

此查询结果显示在下表中:

familyName childGivenName petName
Desai Jesse Goofy
Desai Jesse Shadow
Desai Lisa NULL

根文档与两个 children 行进行了联接,这两个行由生成两个行(或元组)的首次 OPENJSON(children) 调用返回。 然后,使用 OUTER APPLY 运算符将每行与由 OPENJSON(pets) 生成的新行进行联接。 Jesse 有两只宠物,因此 (Desai, Jesse) 与为 GoofyShadow 生成的两行进行了联接。 Lisa 没有宠物,因此 OPENJSON(pets) 没有为该元组返回的任何行。 但是,由于我们使用的是 OUTER APPLY,因此我们将在列中得到 NULL。 如果使用 CROSS APPLY 而不是 OUTER APPLY,则不会在结果中返回 Lisa,因为没有可以与该元组联接的任何宠物行。

对比 JSON_VALUE 与 JSON_QUERY

JSON_VALUEJSON_QUERY 之间的主要区别在于 JSON_VALUE 返回标量值,而 JSON_QUERY 返回对象或数组。

请参考以下示例 JSON 文本。

{
    "a": "[1,2]",
    "b": [1, 2],
    "c": "hi"
}

在此示例 JSON 文本中,数据成员“a”和“c”是字符串值,而数据成员“b”是数组。 JSON_VALUEJSON_QUERY 会返回以下结果:

路径 JSON_VALUE 返回 JSON_QUERY 返回
$ NULL 或错误 { "a": "[1,2]", "b": [1, 2], "c": "hi" }
$.a [1,2] NULL 或错误
$.b NULL 或错误 [1,2]
$.b[0] 1 NULL 或错误
$.c hi NULL 或错误

使用 AdventureWorks 示例数据库测试 JSON_VALUE 和 JSON_QUERY

通过使用 AdventureWorks2022 示例数据库运行以下示例,对本文中所述的内置函数进行测试。 有关如何通过运行脚本添加 JSON 数据进行测试的更多信息,请参阅测试驱动内置的 JSON 支持

在下面的示例中,SalesOrder_json 表中的 Info 列包含了 JSON 文本。

示例 1 - 返回标准列和 JSON 数据

下面的查询返回将返回标准关系列以及 JSON 列的值。

SELECT SalesOrderNumber,
    OrderDate,
    Status,
    ShipDate,
    AccountNumber,
    TotalDue,
    JSON_QUERY(Info, '$.ShippingInfo') ShippingInfo,
    JSON_QUERY(Info, '$.BillingInfo') BillingInfo,
    JSON_VALUE(Info, '$.SalesPerson.Name') SalesPerson,
    JSON_VALUE(Info, '$.ShippingInfo.City') City,
    JSON_VALUE(Info, '$.Customer.Name') Customer,
    JSON_QUERY(OrderItems, '$') OrderItems
FROM Sales.SalesOrder_json
WHERE ISJSON(Info) > 0;

示例 2 - 聚合和筛选 JSON 值

下面的查询将按客户名称(存储在 JSON 中)和状态(存储在普通列中)来汇总小计。 然后将按市/县(存储在 JSON 中)和 OrderDate(存储在普通列中)来筛选结果。

DECLARE @territoryid INT;
DECLARE @city NVARCHAR(32);

SET @territoryid = 3;
SET @city = N'Seattle';

SELECT JSON_VALUE(Info, '$.Customer.Name') AS Customer,
    Status,
    SUM(SubTotal) AS Total
FROM Sales.SalesOrder_json
WHERE TerritoryID = @territoryid
    AND JSON_VALUE(Info, '$.ShippingInfo.City') = @city
    AND OrderDate > '1/1/2015'
GROUP BY JSON_VALUE(Info, '$.Customer.Name'),
    Status
HAVING SUM(SubTotal) > 1000;

使用 JSON_MODIFY 函数更新 JSON 文本中的属性值

JSON_MODIFY 函数会更新 JSON 字符串中属性的值,并返回已更新的 JSON 字符串。

以下示例将更新包含 JSON 的变量中的 JSON 属性的值。

SET @info = JSON_MODIFY(@jsonInfo, "$.info.address[0].town", 'London');

有关详细信息,请参阅 JSON_MODIFY (Transact-SQL)