从Select中构建JSON,但在SQL Server 2016中没有列名

时间:2018-07-16 11:10:27

标签: sql json sql-server sql-server-2016

我今天早些时候发布了this question,该内容与将JSON数组转换为表有关,很幸运在进一步搜索后找到了解决方案。

现在,经过比上次更多的搜索,我仍然很困惑(尽管我在该论坛中看到了一些条目,但是它们并不能专门解决我的问题)。

在某些情况下,我需要以选择的结果来响应请求,该选择的结果具有可变的记录数(可以是数千条),每条记录大约有20列。

现在,我发现通过选择(通过添加FOR JSON AUTO)来构建JSON的方法非常有效,并且确实创建了一个记录数组,每个记录的所有列均以列名开头。

但是,这会使结果超出所需的几倍(我正在考虑网络流量,尤其是当它不在LAN上时)。

为克服这个问题,我将响应分为标题和正文两部分,其中标题包含响应中列名的列表(按正确的顺序),而正文为每条记录包含列表值(与标题的数量和顺序匹配)。

示例:

如果源表如下所示:

  A     |      B      |         C
--------+-------------+--------------------
 11     |     22      |   2018-04-07 12:44
 33     |     44      |   2017-02-21 18:55
 55     |     66      |   2016-11-12 00:03

并且响应的主体应包含表中列“ A”和“ B”的值,响应将如下所示:

{"Response": {
               "Header":["A","B","C"],
                "Body":[["11","22","2018-04-07 12:44"],
                        ["33","44","2017-02-21 18:55"],
                        ["55","66","2016-11-12 00:03"]
                       ]
             }
}

很遗憾,我没有找到一种方法来获取Body"A""B"名称中的"C"

更新

我想强调一个事实,表记录中的不同列可能具有不同的类型,因此我将它们全部转换为字符串。请参阅更新的示例表和预期结果。

2 个答案:

答案 0 :(得分:1)

正如@ Jeroen-Mostert所指出的那样,这在过程代码中非常简单。您甚至可以让SQL Server使用SQL CLR来做到这一点。因为这种形状对于FOR JSON查询而言并不自然,所以基于CLR的解决方案可能比TSQL更好。

下面将结果连接成一个字符串,但是您可以更改它以将结果流式传输到多行中(例如FOR JSON查询),或者添加GZip压缩。

using System;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using System.Text;
using Microsoft.SqlServer.Server;

public partial class StoredProcedures
{
    [Microsoft.SqlServer.Server.SqlProcedure]
    public static void RunQuery (string sql)
    {
        /*
         {"Response":{"Header":["A","B","C"],
                "Body":[["11","22","2018-04-07 12:44"],
                        ["33","44","2017-02-21 18:55"],
                        ["55","66","2016-11-12 00:03"]
                       ]
             }
         }
         * */
        using (var con = new SqlConnection("Context Connection=true"))
        {
            var sb = new StringBuilder();
            con.Open();
            var cmd = con.CreateCommand();
            cmd.CommandText = sql;
            using (var rdr = cmd.ExecuteReader())
            {
                sb.Append("{\"Response\":{\"Header\":[");
                for (int i = 0; i < rdr.FieldCount; i++)
                {
                    var fn = rdr.GetName(i);
                    sb.Append('"').Append(fn).Append('"');
                    if (i + 1 < rdr.FieldCount)
                        sb.Append(',');
                }
                sb.Append("],\"Body\":[");

                //SqlContext.Pipe.Send(sb.ToString());

                if (rdr.Read())
                {
                    while (true)
                    {

                        sb.Append('[');
                        for (int i = 0; i < rdr.FieldCount; i++)
                        {
                            var val = rdr[i].ToString();
                            sb.Append('"').Append(val).Append('"');
                            if (i + 1 < rdr.FieldCount)
                                sb.Append(',');
                        }
                        sb.Append(']');
                        if (rdr.Read())
                        {
                            sb.Append(',');
                        }
                        else
                        {
                            break;
                        }

                    }
                }
                sb.Append("]}}");

                var md = new SqlMetaData("JSON", SqlDbType.NVarChar,-1);


                var r = new SqlDataRecord(md);
                r.SetString(0, sb.ToString());
                SqlContext.Pipe.SendResultsStart(r);
                SqlContext.Pipe.SendResultsRow(r);
                SqlContext.Pipe.SendResultsEnd();

            }
        }
    }
}

答案 1 :(得分:1)

  

但是,这会使结果超出所需的几倍(我正在考虑网络流量,尤其是当它不在LAN上时)。

鉴于:

  1. 您的目标是减少网络流量,并且
  2. 您正在使用servlet处理数据库通信
  3. 涉及多种数据类型

您可以尝试...

  1. 使用JSON将结果集返回到Servlet。将结果集转换为JSON会占用大量资源,需要额外的CPU,RAM和时间。而且,尽管JSON是比XML更有效的 text 格式,但它并不一定比坚持本机TDS格式更有效。考虑到0到3位的整数将更有效,因为文本,4位将同样有效,而5位或更多位的效率将更低。因此,对于INT字段,哪种格式更有效取决于实际结果。另一方面,DATETIME字段本机为8个字节,但是当序列化为JSON的文本时,它变为23个字节。当然,这假定您正在将JSON转换为VARCHAR。如果由于结果中包含Unicode字符串而导致无法转换为VARCHAR以将JSON返回到Servlet,则这些字符串日期各占46个字节(SQL Server仅对Unicode使用UTF-16, (没有UTF-8选项)。而且,这也改变了INT值之间的差异,使得仅0或1位数字的值作为文本更有效,而2位数字等效,3位或更多数字效率更低。

    总的观点是:您需要彻底测试这两种方法,因为我(显然是这里的其他人)怀疑您可能充其量同样高效的传输方式,却要付出更多的代码的代价,并且需要更多的系统资源。因此为净负数。但是,除了为此付出代价之外,这种方法更有可能会变慢。

    因此,正如@ PanagiotisKanavos,@ JeroenMostert和@DavidBrowne在各种注释中所建议的那样:只需在servlet中转换为JSON。

  2. 通过内置的COMPRESS函数(在SQL Server 2016中引入)压缩返回的标准JSON。通过简单地向查询添加单个函数,您可以将返回的JSON减少90%以上。例如:

    DECLARE @Results VARCHAR(MAX);
    SET @Results = (SELECT * FROM sys.objects FOR JSON AUTO);
    SELECT @Results AS [JSON],
           DATALENGTH(@Results) AS [UncompressedBytes],
           DATALENGTH(COMPRESS(@Results)) AS [CompressedBytes];
    

    返回:

      

    [{“ name”:“ sysrscols”,“ object_id”:3,“ schema_id”:4,“ parent_object_id”:0,“ type”:“ S”,“ type_desc”:“ SYSTEM_TABLE”,“ create_date” :。“ 2017-08-22T19:38:02.860”,“ modify_date”:“ 2017-08-22T19:38:02.867”,“ is_ms_shipped”:true,“ is_published”:false,“ is_schema_published”:false} ..

         

    29521

         

    2594

    这极大地减少了网络流量,而不会使代码复杂化或提供专有格式。

    使用GZip进行压缩,这在Java中似乎很容易处理:

    Java GZIP Example – Compress and Decompress File

    证明您确实可以拥有JSON并对其进行压缩?


建议。关于您对David的回答的评论:

  

我想附加一个JAVA存储过程

否,这不是SQL Server的选项。

P.P.S。关于已弃用SQLCLR的评论,即使只是“有效”,请参阅我的帖子:

SQLCLR vs SQL Server 2017, Part 8: Is SQLCLR Deprecated in Favor of Python or R (sp_execute_external_script)?