OPENJSON 不会将所有文档都选择到 SQL 表中

Posted

技术标签:

【中文标题】OPENJSON 不会将所有文档都选择到 SQL 表中【英文标题】:OPENJSON does not select all documents into the SQL table 【发布时间】:2019-03-13 12:39:35 【问题描述】:

我一直在尝试将 JSON 文件的内容导出到 SQL Server 表。但是,尽管 JSON 中存在多行,但输出 SQL 表仅包含 JSON 中的第一行。我使用的代码如下:

DROP TABLE IF EXISTS testingtable;

DECLARE @json VARCHAR(MAX) = ' "_id" : "01001", "city" : "AGAWAM", "loc" : [ -72.622739, 42.070206 ], "pop" : 15338, "state" : "MA" , 
                               "_id" : "01002", "city" : "CUSHMAN", "loc" : [ -72.51564999999999, 42.377017 ], "pop" : 36963, "state" : "MA" ';
SELECT * INTO testingtable FROM OPENJSON(@json) WITH (_id int, city varchar(20), loc float(50), pop int, state varchar(5)
)

SELECT * FROM testingtable

而得到的输出如下: Click to view

【问题讨论】:

这是一个单个对象 为什么OPENJSON会返回多行? 我只看到一行。 单个对象或无效的 JSON。 JSON 文档中不能有两个根对象 这似乎是一个缺少分隔符的数组。将该字符串包裹在 [] 中,您将获得两个对象。 @DarshKhetan 它是 invalid JSON 和 OPENJSON 只读取第一个有效部分,丢弃其余部分。您不能有多个根对象。您不能在 JSON 文档中包含 ..,..。多项表示一个数组,即[..,..] 【参考方案1】:

例如,多行 JSON 文本用方括号括起来;

[
    first data set,
    second data set, .....
]

您可以在向此查询传递数据时添加方括号,也可以在 @json 变量中添加方括号(例如,'['+ @json + ']')

DECLARE @json VARCHAR(MAX) = ' "_id" : "01001", "city" : "AGAWAM", "loc" : [ -72.622739, 42.070206 ], "pop" : 15338, "state" : "MA" , 
                               "_id" : "01002", "city" : "CUSHMAN", "loc" : [ -72.51564999999999, 42.377017 ], "pop" : 36963, "state" : "MA" ';
SELECT * INTO testingtable FROM OPENJSON ('['+ @json + ']') WITH (_id int, city varchar(20), loc float(50), pop int, state varchar(5)
)

SELECT * FROM testingtable

【讨论】:

【参考方案2】:

字符串不是有效的 JSON。 JSON 文档中不能有 两个 根对象。格式正确,JSON 字符串如下所示

DECLARE @json VARCHAR(MAX) = ' "_id" : "01001", "city" : "AGAWAM", "loc" : [ -72.622739, 42.070206 ], "pop" : 15338, "state" : "MA" ,
                               "_id" : "01002", "city" : "CUSHMAN", "loc" : [ -72.51564999999999, 42.377017 ], "pop" : 36963, "state" : "MA" ';

应该是

DECLARE @json VARCHAR(MAX) = '[ "_id" : "01001", "city" : "AGAWAM", "loc" : [ -72.622739, 42.070206 ], "pop" : 15338, "state" : "MA" ,
                                "_id" : "01002", "city" : "CUSHMAN", "loc" : [ -72.51564999999999, 42.377017 ], "pop" : 36963, "state" : "MA" 
                              ]';

看起来 OPENJSON 解析了第一个对象,并在遇到无效文本时立即停止。

解决此问题的快速而肮脏的方法是添加缺少的方括号:

SELECT * FROM OPENJSON('[' + @json + ']') WITH (_id int, city varchar(20), loc float(50), pop int, state varchar(5))

我怀疑该字符串来自将单个记录存储在单独行中的日志或事件文件。这是无效的,也没有任何类型的标准或规范(尽管名称抢注者),但许多高流量应用程序使用它,例如在日志文件或事件流中。

他们这样做的原因是不需要构造或读取整个数组来获取记录。 简单只需为每条记录附加一个新行。读取大文件并并行处理它们也更容易 - 只需逐行读取文本并将其提供给工作人员。或者将文件分成 N 部分到最近的换行符,并将各个部分提供给不同的机器。这就是 Map-Reduce 的工作原理。

这就是为什么添加方括号是一个肮脏的解决方案 - 您必须先从多 MB 或 GB 大小的文件中读取整个文本,然后才能解析它。这不是 OPENJSON 的初衷。

正确的解决方案是使用另一个工具逐行读取文件,解析记录并将值插入到目标表中。

【讨论】:

【参考方案3】:

如果您知道 JSON 文档不会包含任何内部换行符,您可以使用 string_split 拆分字符串。 OPENJSON 不关心前导空格或尾随 ,。这样您就可以避免添加 [ ] 字符,并且不必将其解析为一个大文档。

EG:

DROP TABLE IF EXISTS testingtable;

DECLARE @jsonFragment VARCHAR(MAX) = ' "_id" : "01001", "city" : "AGAWAM", "loc" : [ -72.622739, 42.070206 ], "pop" : 15338, "state" : "MA" , 
                               "_id" : "01002", "city" : "CUSHMAN", "loc" : [ -72.51564999999999, 42.377017 ], "pop" : 36963, "state" : "MA" ';

SELECT * 
INTO testingtable 
FROM  string_split(@jsonFragment,CHAR(10))  docs
cross apply 
(
  select *
  from openjson(docs.value)
  WITH (_id int, city varchar(20), loc float(50), pop int, state varchar(5))
) d

SELECT * FROM testingtable

类似于 XML,这种格式可以称为“JSON 片段”。这是 SQL Server 中 XML 和 JSON 之间的另一个区别。对于 XML,引擎很乐意解析和存储 XML 片段,但不适用于 JSON。

【讨论】:

以上是关于OPENJSON 不会将所有文档都选择到 SQL 表中的主要内容,如果未能解决你的问题,请参考以下文章

OPENJSON 在 SQL Server 中不起作用?

OPENJSON - 无法查询嵌套元素

SQL Server Openjson 将详细信息提取到表中

使用 C# 和 OPENJSON 将 JSON 插入 SQL Server 2016

具有深度嵌套数组循环的 SQL 查询 OpenJson

无法使用 OPENJSON 和 SQL Server 2017 解析具有动态键的对象数组