我今天早些时候发布了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"
我想强调一个事实,表记录中的不同列可能具有不同的类型,因此我将它们全部转换为字符串。请参阅更新的示例表和预期结果。
答案 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上时)。
鉴于:
您可以尝试...
仅不使用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。
通过内置的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的评论,即使只是“有效”,请参阅我的帖子: