简体   繁体   English

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

[英]Build JSON out of Select but without column names in SQL Server 2016

I posted earlier today this question related to the conversion of a JSON array into a table, and was quite lucky to find the solution after further search. 我今天早些时候发布的这个问题涉及到一个JSON数组到表的转换,是很幸运地找到了后进一步搜索解决方案。

Now, and after searching more than the previous time, I'm still stuck (though I saw some entries in this forum, but they do not specifically resolve my problem). 现在,经过比上次更多的搜索,我仍然陷入困境(尽管我在该论坛中看到了一些条目,但是它们并不能专门解决我的问题)。

There are some cases in which I need to respond to a request with the result of a select that has a variable number of records (could be thousands), each having about 20 columns. 在某些情况下,我需要以选择的结果来响应请求,该选择的结果具有可变数量的记录(可以是数千条),每条记录大约有20列。

Now the way I found to build a JSON out of the select (by adding FOR JSON AUTO ) works very nicely and indeed creates an array of records, each having all the columns presided by the column name. 现在,我发现从选择中构建JSON的方式(通过添加FOR JSON AUTO )非常好,并且确实创建了一个记录数组,每个记录的所有列均以列名开头。

This, however, makes the result several times larger than needed (I'm thinking about network traffic, specially when it is not over a LAN). 但是,这会使结果超出所需的几倍(我正在考虑网络流量,尤其是当它不在LAN上时)。

To overcome this, I split the response into two, a Header and a Body, where the Header contains the list of the column names in the response (in the correct order) while the body contains, for each record, the list of values (matching the number and order of the Header). 为了克服这个问题,我将响应分为标题和正文两部分,其中标题包含响应中列名的列表(按正确的顺序),而正文为每条记录包含值列表(匹配标题的编号和顺序)。

Example: 例:

If the source table would look like this: 如果源表如下所示:

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

and the Body of the response should contain the values of columns "A" and "B" from a table, the response would look as follows: 并且响应的正文应包含表中列“ 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"]
                       ]
             }
}

Unfortunately, I'm not finding a way to get the contents of the Body without the "A" , "B" and "C" names. 不幸的是,我没有找到一种方式来获得的内容Body 没有 "A""B""C"的名字。

Update 更新

I want to stress the fact that the different columns within the table record may be of different types, so I would convert them all to strings. 我想强调一个事实,表记录中的不同列可能具有不同的类型,因此我将它们全部转换为字符串。 See the updated example table and the expected result. 请参阅更新的示例表和预期结果。

As @Jeroen-Mostert notes, this is pretty simple to do in procedural code. 正如@ Jeroen-Mostert所指出的那样,这在过程代码中非常简单。 You can even have SQL Server do it using SQL CLR. 您甚至可以让SQL Server使用SQL CLR来做到这一点。 Because this shape is not natural for FOR JSON queries, a CLR-based solution is probably be better than a TSQL one. 因为这种形状对于FOR JSON查询而言并不自然,所以基于CLR的解决方案可能比TSQL更好。

Below concatenates the results into a single string, but you could change that to stream the results over multiple rows (like FOR JSON queries do), or add in GZip compression. 下面将结果连接成一个字符串,但是您可以更改该结果以将结果流式传输到多行中(就像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();

            }
        }
    }
}

This, however, makes the result several times larger than needed (I'm thinking about network traffic, specially when it is not over a LAN). 但是,这会使结果超出所需的几倍(我正在考虑网络流量,尤其是当它不在LAN上时)。

Given that: 鉴于:

  1. your goal is to reduce network traffic, and 您的目标是减少网络流量,并且
  2. you are using a servlet to handle the DB communication 您正在使用Servlet处理数据库通信
  3. there are multiple datatypes involved 涉及多种数据类型

you can try... 你可以试试...

  1. simply not using JSON to return the result set to the servlet. 只是使用JSON将结果集返回给servlet。 Converting the result set to JSON is resource-intensive, requiring additional CPU, RAM, and time. 将结果集转换为JSON会占用大量资源,需要额外的CPU,RAM和时间。 And while JSON is a more efficient text format than XML, it is not necesarilly more efficient than sticking with the native TDS format. 而且,尽管JSON是比XML更有效的文本格式,但它并不一定比坚持本机TDS格式更有效。 Consider that integers of 0 to 3 digits will be more efficient as text, 4 digits will be equally efficient, and 5 digits or more are less efficient. 考虑到0到3位的整数将更有效,因为文本,4位将同样有效,而5位或更多位的效率将更低。 So for INT fields, which format is more efficient depends on the actual results. 因此,对于INT字段,哪种格式更有效取决于实际结果。 On the other hand, DATETIME fields are 8 bytes natively, but when serialized as text for JSON, it becomes 23 bytes. 另一方面, DATETIME字段本机为8个字节,但是当序列化为JSON的文本时,它将变为23个字节。 This, of course, assumes that you are converting the JSON to VARCHAR . 当然,这假设您要将JSON转换为VARCHAR If you are unable to convert to VARCHAR to return the JSON to the servlet due to having Unicode strings in the results, then those string dates take up 46 bytes each (SQL Server uses UTF-16 only for Unicode, there is no UTF-8 option). 如果由于结果中包含Unicode字符串而导致无法转换为VARCHAR以将JSON返回到Servlet,则这些字符串日期各占46个字节(SQL Server仅对Unicode使用UTF-16,因此没有UTF-8选项)。 And that also changes the difference between INT values such that only values of 0 or 1 digits is more efficient as text, 2 digits is equivalent, and 3 or more digits are less efficient. 并且这也改变了INT值之间的差异,以使得仅0或1位数字的值更有效,因为文本,2位数字等效,而3位或更多位数字效率更低。

    The overall point being: you need to thoroughly test both approach because, I (and apparently others on here) suspect that you would, at best , end up with an equally efficient transport, yet would have paid the price in terms of having more convoluted code and require more system resources. 总的说来:您需要彻底测试这两种方法,因为我(显然还有其他人)怀疑您充其量只能得到同样有效的运输,但会付出更多麻烦的代价代码, 需要更多的系统资源。 Hence a net-negative. 因此为净负数。 But, more than likely this approach will be slower, in addition to paying that price for it. 但是,除了为此付出代价之外,这种方法更有可能会变慢。

    So, as has already been suggested in various comments by @PanagiotisKanavos, @JeroenMostert, and @DavidBrowne: just convert to JSON in the servlet. 因此,正如@ PanagiotisKanavos,@ JeroenMostert和@DavidBrowne在各种注释中所建议的那样:只需在servlet中转换为JSON。

  2. compressing the returned, standard JSON via the built-in COMPRESS function (introduced in SQL Server 2016). 通过内置的COMPRESS函数(在SQL Server 2016中引入)压缩返回的标准JSON。 By simply adding a single function to the query you can reduce the returned JSON by over 90%. 通过简单地向查询添加单个函数,您可以将返回的JSON减少90%以上。 For example: 例如:

     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]; 

    returns: 收益:

    [{"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},... [{ “名称”: “sysrscols”, “的object_id”:3 “schema_id”:4 “parent_object_id”:0, “类型”: “S”, “type_desc”: “SYSTEM_TABLE”, “CREATE_DATE”:“2017年-08-22T19:38:02.860" , “modify_date”: “2017-08-22T19:38:02.867”, “is_ms_shipped”:真实的, “is_published”:假的, “is_schema_published”:假},...

    29521 29521

    2594 2594

    This dramastically reduces the network traffic without convoluting the code or coming up with a proprietary format. 这极大地减少了网络流量,而无需卷积代码或使用专有格式。

    The compression is done using GZip, which appears to be rather easy to handle in Java: 压缩是使用GZip完成的,在Java中似乎很容易处理:

    Java GZIP Example – Compress and Decompress File Java GZIP示例–压缩和解压缩文件

    Proving that you really can have your JSON and compress it too 😸 证明您确实可以拥有JSON并对其进行压缩😸


PS Regarding your comment, on David's answer, of: PS关于您对David的回答的评论:

I thought of attaching a JAVA stored procedure 我想到附加一个JAVA存储过程

No, this is not an option with SQL Server. 不,这不是SQL Server的选项。

PPS Regarding the comment that SQLCLR is deprecated, even just "effectively", please see my post: PPS关于不推荐使用SQLCLR的评论,即使只是“有效”,请参阅我的文章:

SQLCLR vs SQL Server 2017, Part 8: Is SQLCLR Deprecated in Favor of Python or R (sp_execute_external_script)? SQLCLR与SQL Server 2017,第8部分:是否赞成使用Python或R(sp_execute_external_script)?

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM