【问题标题】:Converting JSON to table in SQL Server 2016在 SQL Server 2016 中将 JSON 转换为表
【发布时间】:2018-07-16 07:08:59
【问题描述】:

我正在开发一个 Web 项目,其中客户端应用程序通过 JSON 与 DB 进行通信。

最初的实现发生在 SQL Server 2012(不支持 JSON,因此我们实现了一个处理解析的存储函数),现在我们正在转向 2016(支持 JSON)。

到目前为止,我们正在将处理时间大大缩短(在某些情况下,速度提高了 200 倍以上!)。

有些交互包含需要转换成表格的数组。为此,OPENJSON 函数几乎满足了我们的需要。

在其中一些(基于数组的)情况下,数组中的记录具有一个或多个字段,这些字段也是 OBJECTS(在这种特殊情况下,也是数组),例如:

    [{
        "Formal_Round_Method": "Floor",
        "Public_Round_Method": "Closest",
        "Formal_Precision": "3",
        "Public_Precision": "3",
        "Formal_Significant_Digits": "3",
        "Public_Significant_Digits": "3",
        "General_Comment": [{
            "Timestamp": "2018-07-16 09:19",
            "From": "1",
            "Type": "Routine_Report",
            "Body": "[To + Media + What]: Comment 1",
            "$$hashKey": "object:1848"
        }, {
            "Timestamp": "2018-07-16 09:19",
            "From": "1",
            "Type": "User_Comment",
            "Body": "[]: Comment 2",
            "$$hashKey": "object:1857"
        }, {
            "Timestamp": "2018-07-16 09:19",
            "From": "1",
            "Type": "Routine_Report",
            "Body": "[To + Media + What]: Comment 3",
            "$$hashKey": "object:1862"
        }]
    }, {
        "Formal_Round_Method": "Floor",
        "Public_Round_Method": "Closest",
        "Formal_Precision": "3",
        "Public_Precision": "3",
        "Formal_Significant_Digits": "3",
        "Public_Significant_Digits": "3",
        "General_Comment": []

    }]

这里General_Comment也是一个数组。

运行命令时:

SELECT *
  FROM OPENJSON(@_l_Table_Data)
  WITH (    Formal_Round_Method                 NVARCHAR(16)    '$.Formal_Round_Method'               ,
            Public_Round_Method                 NVARCHAR(16)    '$.Public_Round_Method'               ,
            Formal_Precision                    INT             '$.Formal_Precision'                  ,
            Public_Precision                    INT             '$.Public_Precision'                  ,
            Formal_Significant_Digits           INT             '$.Formal_Significant_Digits'         ,
            Public_Significant_Digits           INT             '$.Public_Significant_Digits'         ,
            General_Comment                     NVARCHAR(4000)  '$.General_Comment'                   
        ) ;

[@_l_Table_Data 是一个保存 JSON 字符串的变量]

我们得到了 General_Comment = NULL 列,即使里面有数据(至少在数组的第一个元素中)。

我想我应该对那些可能包含 OBJECTS 而不是 SIMPLE VALUES 的列使用不同的语法,但我不知道该语法应该是什么。

【问题讨论】:

  • 您应该考虑一下您的数据库设计。看起来你应该有 2 张桌子。一个用于 cmets,一个用于父实体。
  • 谢谢@PhilippSander,但是这个方向需要创建几十个新表(我只提供了一个示例)以及添加许多JOINs(性能)。与此同时,我找到了一个非常简单的解决方案(丢失了 MS 页面的链接,所以我将其添加为我原始帖子的答案)。干杯!!!!
  • 这些对我来说听起来不是有效的论据,但这是你的选择。您可以使用数据库提供的其他工具来处理性能
  • @PhilippSander,很长一段时间我才意识到人们在设计系统时有不同的策略、方法和优先级。我的其中之一是在不损害结果的灵活性和可维护性的情况下使结果尽可能简单。在这种情况下,保持表的总数尽可能小是我的首选技术之一。不过,这在很大程度上是一个品味问题,因此,对我来说适合(和有效)的东西不一定适合(或有效)其他人。
  • 这很公平。正如我所说,这是你的选择。我只是想指出来

标签: json sql-server-2016


【解决方案1】:

我找到了一个真正解决问题的微软页面。

查询应该是这样的:

SELECT *
  FROM OPENJSON(@_l_Table_Data)
  WITH (    Formal_Round_Method        NVARCHAR(16)    '$.Formal_Round_Method'               ,
            Public_Round_Method        NVARCHAR(16)    '$.Public_Round_Method'               ,
            Formal_Precision           INT             '$.Formal_Precision'                  ,
            Public_Precision           INT             '$.Public_Precision'                  ,
            Formal_Significant_Digits  INT             '$.Formal_Significant_Digits'         ,
            Public_Significant_Digits  INT             '$.Public_Significant_Digits'         ,
            General_Comment            NVARCHAR(MAX)   '$.General_Comment'   AS JSON                
    ) ;

因此,您需要在列定义的末尾添加AS JSON,并且(天知道为什么)类型必须NVARCHAR(MAX)

确实很简单!!!

【讨论】:

    【解决方案2】:

    创建函数ParseJson

    Create or Alter FUNCTION [dbo].[ParseJson]  (@JSON NVARCHAR(MAX))
    RETURNS @Unwrapped TABLE
      (
      [id] INT IDENTITY, --just used to get a unique reference to each json item
      [level] INT, --the hierarchy level
      [key] NVARCHAR(100), --the key or name of the item
      [Value] NVARCHAR(MAX),--the value, if it is a null, int,binary,numeric or string
      type INT, --0 TO 5, the JSON type, null, numeric, string, binary, array or object
      SQLDatatype sysname, --whatever the datatype can be parsed to
      parent INT, --the ID of the parent
      [path] NVARCHAR(4000) --the path as used by OpenJSON
      )
    AS begin
    INSERT INTO @Unwrapped ([level], [key], Value, type, SQLDatatype, parent,
    [path])
    VALUES
      (0, --the level
       NULL, --the key,
       @json, --the value,
       CASE WHEN Left(ltrim(@json),1)='[' THEN 4 ELSE 5 END, --the type
       'json', --SQLDataType,
       0 , --no parent
       '$' --base path
      );
    DECLARE @ii INT = 0,--the level
    @Rowcount INT = -1; --the number of rows from the previous iteration
    WHILE @Rowcount <> 0 --while we are still finding levels
      BEGIN
        INSERT INTO @Unwrapped ([level], [key], Value, type, SQLDatatype, parent,
        [path])
          SELECT [level] + 1 AS [level], new.[Key] AS [key],
            new.[Value] AS [value], new.[Type] AS [type],
    -- SQL Prompt formatting off
    /* in order to determine the datatype of a json value, the best approach is to a determine
    the datatype that can be parsed. It JSON, an array of objects can contain attributes that arent
    consistent either in their name or value. */
           CASE
            WHEN new.Type = 0 THEN 'bit null'
            WHEN new.[type] IN (1,2)  then COALESCE(
                CASE WHEN TRY_CONVERT(INT,new.[value]) IS NOT NULL THEN 'int' END,
                CASE WHEN TRY_CONVERT(NUMERIC(14,4),new.[value]) IS NOT NULL THEN 'numeric' END,
                CASE WHEN TRY_CONVERT(FLOAT,new.[value]) IS NOT NULL THEN 'float' END,
              CASE WHEN TRY_CONVERT(MONEY,new.[value]) IS NOT NULL THEN 'money' END,
                CASE WHEN TRY_CONVERT(DateTime,new.[value],126) IS NOT NULL THEN 'Datetime2' END,
              CASE WHEN TRY_CONVERT(Datetime,new.[value],127) IS NOT NULL THEN 'Datetime2' END,
              'nvarchar')
           WHEN new.Type = 3 THEN 'bit'
           WHEN new.Type = 5 THEN 'object' ELSE 'array' END AS SQLDatatype,
            old.[id],
            old.[path] + CASE WHEN old.type = 5 THEN '.' + new.[Key]
                           ELSE '[' + new.[Key] COLLATE DATABASE_DEFAULT + ']' END AS path
    -- SQL Prompt formatting on
          FROM @Unwrapped old
            CROSS APPLY OpenJson(old.[Value]) new
              WHERE old.[level] = @ii AND old.type IN (4, 5);
        SELECT @Rowcount = @@RowCount;
        SELECT @ii = @ii + 1;
      END;
      return
    END
    

    用途:

    select * from ParseJson(jsonString)
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-08-31
      • 1970-01-01
      • 2019-06-22
      • 2022-08-12
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多