【问题标题】:SQL Server : OPENJSON error with dynamic JSON columns to store in DB tableSQL Server:OPENJSON 错误,动态 JSON 列存储在数据库表中
【发布时间】:2021-11-21 17:02:18
【问题描述】:

在我的项目中使用 SQL Server JSON 函数时,下面的代码出现错误。

我想尝试将 json 键值对存储到 DB 中的某些表中。

基本上,我正在尝试访问返回 JSON 并且我必须将其存储在数据库中的 REST API。

  --- JSON document ---

DECLARE @json nvarchar(max) = N'
   [    
    {
    "ProductId":1,
    "ProductName":"Food",
    "ProductDescription":"Apple",
    "DatePurchased":"1995-05-01T00:00:00"
    },   
    {
    "ProductId":2,
    "ProductName":"Electronics",
    "ProductDescription":"TV",
    "DatePurchased":"2018-09-17T00:00:00"
    }
   ]   
'
-- selecting key values from json
SELECT [Key] 
FROM OPENJSON((@json), '$')

DECLARE @columns varchar(max) = N''
DECLARE @schema varchar(max) = N''
DECLARE @stm varchar(max)

-- Columns preparation
SELECT 
   @columns = CONCAT(@columns, ',', QUOTENAME([key])),
   @schema = CONCAT(@schema, ',', QUOTENAME([key]),' varchar(max) ''$.', [key], '')
FROM OPENJSON(@json, '$[0]')


DROP TABLE IF EXISTS #TestData

-- inserting the json values to db table Statement
SET @stm = 'SELECT '+
       CONCAT(STUFF(@columns, 1, 1, ''),' INTO #TestData FROM OPENJSON((SELECT * FROM @json),''$.records'') WITH (',
   STUFF(@schema, 1, 1, ''),')'
   )

PRINT @stm
EXEC(@stm)


::::: OUTPUT ::::

SELECT 
    [ProductId], [ProductName], [ProductDescription], [DatePurchased] 
INTO #TestData 
FROM OPENJSON((SELECT * FROM @json), '$.records') 
     WITH (
             [ProductId] varchar(max) '$.ProductId,
             [ProductName] varchar(max) '$.ProductName,
             [ProductDescription] varchar(max) '$.ProductDescription,
             [DatePurchased] varchar(max) '$.DatePurchased
          )

我收到以下错误:

消息 1087,第 15 级,状态 2,第 1 行
必须声明表变量“@json”。

消息 319,第 15 级,状态 1,第 1 行
关键字“with”附近的语法不正确。如果这个语句是一个公用表表达式、一个 xmlnamespaces 子句或一个更改跟踪上下文子句,则前面的语句必须以分号结束

【问题讨论】:

  • 提示1:不要使用EXEC ({SQL Variable})语法,你不能将你的参数传递给动态语句。使用sys.sp_executesql
  • 提示 2:调试动态 SQL 的最简单方法是 PRINT/SELECT 您的语句,对其进行调试,然后将解决方案传播到创建动态语句的 SQL。如果您查看PRINT,您会发现单引号存在明显错误。
  • 提示 3:编写动态 SQL 时,格式化您创建的语句也很重要。众所周知,长的单行代码很难调试。

标签: sql sql-server open-json


【解决方案1】:

您的查询中有一些错误。我将在下面详细介绍这些内容,但首先这里有一些应该适用于我认为您正在尝试做的事情:

DECLARE @json nvarchar(max) = N'
   [    
    {
    "ProductId":1,
    "ProductName":"Food",
    "ProductDescription":"Apple",
    "DatePurchased":"1995-05-01T00:00:00"
    },   
    {
    "ProductId":2,
    "ProductName":"Electronics",
    "ProductDescription":"TV",
    "DatePurchased":"2018-09-17T00:00:00"
    }
   ]   
';

DECLARE @columns nvarchar(max) = N'';
DECLARE @schema nvarchar(max) = N'';
DECLARE @stm nvarchar(max);

-- Columns preparation
WITH keys AS (
    SELECT DISTINCT [Key]
    FROM OPENJSON(@json)
    WITH (
        ObjJson NVARCHAR(MAX) N'$' AS JSON
    ) l1
    CROSS APPLY OPENJSON(l1.ObjJson)
)
SELECT
   @columns = CONCAT(@columns, ',', QUOTENAME([key])),
   @schema = CONCAT(@schema, ',', QUOTENAME([key]),' varchar(max) ''$.', [key], '''')
FROM keys;

DROP TABLE IF EXISTS dbo.wip_testdata;

-- inserting the json values to db table Statement
SET @stm = 'SELECT '+
       CONCAT(STUFF(@columns, 1, 1, ''),
       ' INTO dbo.wip_testdata
         FROM OPENJSON(@json)
         WITH (
             ObjJson NVARCHAR(MAX) N''$'' AS JSON
         ) l1
         CROSS APPLY OPENJSON(l1.ObjJson)
         WITH (',
             STUFF(@schema, 1, 1, ''),') l2'
         );

EXEC sp_executesql @stm, N'@json NVARCHAR(MAX)', @json;

SELECT * FROM dbo.wip_testdata;

现在,解决问题:

  1. 您没有深入挖掘 JSON,无法从列名的数组中的对象中获取属性名称。 CROSS APPLY 解决了这个问题。在您生成的动态 SQL 语句中也是如此。
  2. 如 cmets 中所述,您希望使用 sp_executesql 以便可以将 JSON 向下传递到动态执行的内部范围。运行动态语句的范围无权从外部范围访问@json,因此您必须将其作为参数传递给sp_executesql。这就是导致您收到 Must declare the table variable "@json" 错误的原因。
  3. 还有一个与范围有关的问题,即您无法访问动态执行返回后动态语句创建的临时表。您将看到我将其转换为物理表,以便您可以在动态语句执行返回后在外部范围内访问它。您可以继续使用临时表,但您需要在执行动态语句之前动态创建该表。动态语句的范围确实可以访问从外部范围预先创建的临时表。

【讨论】:

    【解决方案2】:

    您所拥有的存在多个问题。我将首先介绍我的每个提示,从提示 1 开始:

    不要使用EXEC ({SQL Variable}) 语法,你不能将你的参数传递给动态语句。使用sys.sp_executesql

    这个和锡上写的差不多。您遇到的第一个问题是此错误:

    必须声明表变量“@json”。

    您收到此错误的原因是@json 在动态语句中没有上下文;变量仅在其定义的语句内具有范围;它不能被内部或外部范围访问。那么如何在动态语句中使用该变量?好吧,您将它作为参数传递给所述动态语句。

    但是,有一个问题,您使用了EXEC (@stm),这意味着不可能@json 作为参数传递。因此,您需要使用 sys.sp_executesql,在编写动态语句时应该始终使用它:

    EXEC sys.sp_executesql @stm, N'@json nvarchar(MAX)', @json;
    

    如果你确实使用它,你会得到另一个错误:

    过程需要“ntext/nchar/nvarchar”类型的参数“@statement”

    这是因为您还错误地将@stm 定义为varchar(MAX);它必须是nvarchar(MAX)。如果你修复它,你会得到一个新的错误:

    '$.' 附近的语法不正确。

    提示 2:

    调试动态 SQL 的最简单方法是 PRINT/SELECT 您的语句,对其进行调试,然后将解决方案传播到创建动态语句的 SQL。如果您查看PRINT,您会发现单引号存在明显错误。

    我的意思是字面意思。那么让我们看看你的陈述:

    SELECT [ProductId],[ProductName],[ProductDescription],[DatePurchased] INTO #TestData FROM OPENJSON((SELECT * FROM @json),'$.records') WITH ([ProductId] varchar(max) '$.ProductId,[ProductName] varchar(max) '$.ProductName,[ProductDescription] varchar(max) '$.ProductDescription,[DatePurchased] varchar(max) '$.DatePurchased)
    

    嗯,这很难看……让我们现在跳到技巧 3,然后回到技巧 2:

    在编写动态 SQL 时,格式化您创建的语句也很重要。众所周知,长的单行代码很难调试。

    如您所见,前面的 SQL 语句在阅读上非常不同。我们真的希望它是可读的,像这样:

    SELECT [ProductId],
           [ProductName],
           [ProductDescription],
           [DatePurchased]
    INTO #TestData
    FROM OPENJSON((SELECT *
                   FROM @json),'$.records')
          WITH ([ProductId] varchar(max) '$.ProductId,
                [ProductName] varchar(max) '$.ProductName,
                [ProductDescription] varchar(max) '$.ProductDescription,
                [DatePurchased] varchar(max) '$.DatePurchased)
    

    现在,上面的问题已经很清楚了,但是让我们首先完整地解决技巧 3,并让您的语句在编写时具有良好的格式。因此,我还将把你古怪的SET ~{Variable} = @{Variable} FROM dataset 语法装箱;它是无证的,不应该被信任工作。我将改为使用字符串聚合。我假设您在这里使用的是最新版本的 SQL Server;否则你需要使用“旧的”FOR XML PATH 方法。

    DECLARE @stm nvarchar(max),
            @CRLF nchar(2) = NCHAR(13) + NCHAR(10);
    
    DECLARE @SelectDelim nvarchar(15) = N',' + @CRLF + N'       ',
            @WithDelim nvarchar(15) = N',' + @CRLF + N'           ';
    
    -- Columns preparation
    SELECT @stm = N'SELECT ' +
                  STRING_AGG(QUOTENAME([key]),@SelectDelim) + @CRLF +
                  N'INTO #TestData' + @CRLF + --This INTO is actually completely pointless, but we'll come onto this later
                  N'FROM OPENJSON((SELECT *' + @CRLF +
                  N'              FROM @json),''$.records'')' + @CRLF +
                  N'     WITH (' + 
                  STRING_AGG(QUOTENAME([key]) + N' nvarchar(MAX)', @WithDelim) + N');' --Doing this fixing the issue in point 2
    FROM OPENJSON(@json, '$[0]');
    
    PRINT @stm;
    

    在此我实际上解决了您的报价问题,如果我们查看打印的声明,我们现在得到以下内容:

    SELECT [ProductId],
           [ProductName],
           [ProductDescription],
           [DatePurchased]
    INTO #TestData
    FROM OPENJSON((SELECT *
                  FROM @json),'$.records')
         WITH ([ProductId] nvarchar(MAX),
               [ProductName] nvarchar(MAX),
               [ProductDescription] nvarchar(MAX),
               [DatePurchased] nvarchar(MAX));
    

    看起来不错不是吗!现在让我们将该调用添加到sys.sp_executesql

    必须声明表变量“@json”。

    哦,那是什么?嗯,这就是这些行的原因:

    FROM OPENJSON((SELECT *
                  FROM @json),'$.records')
    

    @json 不是表变量,而是标量变量。所以让我们修复它并将其更改为以下内容:

    FROM OPENJSON(@json,'$.records')
    

    而且,它运行了!是的!现在让我们尝试在动态语句的外部获取该数据:

    SELECT *
    FROM #TestData;
    

    对象名称“#TestData”无效。

    等等,什么?!好吧,我们回到我们的朋友“范围”。当动态 SQL 语句运行并且我们回到外部范围时,临时表已被删除,因此出现上述错误。因此,让我们将 INTO 注释掉(因为我不知道您对此有何打算)。

    但是现在我们没有得到任何数据?好吧,那是因为你有OPENJSON(@json,''$.records'') 它应该只是OPENJSON(@json)。所以,让我们最终解决这个问题,然后一切正常!

    完整的工作声明:

      --- JSON document ---
    
    DECLARE @json nvarchar(max) = N'[    
        {
        "ProductId":1,
        "ProductName":"Food",
        "ProductDescription":"Apple",
        "DatePurchased":"1995-05-01T00:00:00"
        },   
        {
        "ProductId":2,
        "ProductName":"Electronics",
        "ProductDescription":"TV",
        "DatePurchased":"2018-09-17T00:00:00"
        }
       ]';
    
    DECLARE @stm nvarchar(max),
            @CRLF nchar(2) = NCHAR(13) + NCHAR(10);
    
    DECLARE @SelectDelim nvarchar(15) = N',' + @CRLF + N'       ',
            @WithDelim nvarchar(15) = N',' + @CRLF + N'           ';
    
    -- Columns preparation
    SELECT @stm = N'SELECT ' +
                  STRING_AGG(QUOTENAME([key]),@SelectDelim) + @CRLF +
                  N'--INTO #TestData' + @CRLF + --This INTO is actually completely pointless, but we'll come onto this later
                  N'FROM OPENJSON(@json)' + @CRLF +
                  N'     WITH (' + 
                  STRING_AGG(QUOTENAME([key]) + N' nvarchar(MAX)', @WithDelim) + N');' --Doing this fixing the issue in point 2
    FROM OPENJSON(@json, '$[0]');
    
    PRINT @stm;
    
    EXEC sys.sp_executesql @stm, N'@json nvarchar(MAX)', @json;
    

    动态 SQL 运行:

    SELECT [ProductId],
           [ProductName],
           [ProductDescription],
           [DatePurchased]
    --INTO #TestData
    FROM OPENJSON(@json)
         WITH ([ProductId] nvarchar(MAX),
               [ProductName] nvarchar(MAX),
               [ProductDescription] nvarchar(MAX),
               [DatePurchased] nvarchar(MAX));
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2022-08-15
      • 2021-07-31
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-03-18
      • 2020-01-07
      • 2014-07-21
      相关资源
      最近更新 更多