我想创建一个包含以下列的只读视图:
Id - Unique integer
ActivityKind - Identifies what is in PayloadAsJson. Could be an int, char whatever
PayloadAsJson - Record from corresponding table presented as JSON
这样做的原因是我有许多表具有不同的结构,我想要UNION并以某种日期顺序出现。例如:
表1
Id Date EmailSubject EmailRecipient
-- ----------- -------------- ---------------
1 2014-01-01 "Hello World" "me@there.com"
2 2014-01-02 "Hello World2" "me@there.com"
表2
Id Date SensorId SensorName
-- ----------- -------- ------------------
1 2014-01-01 1 "Some Sensor Name"
对于以下视图,我会使用SQL similair:
SELECT Date, 'E' AS ActivityKind, <SPCallToGetJSONForThisRecord> AS PayloadAsJson
FROM Table1
UNION
SELECT Date, 'S' AS ActivityKind, <SPCallToGetJSONForThisRecord> AS PayloadAsJson
FROM Table2
ORDER BY Date
我希望视图看起来像:
1, "E", "{ "Id": 1, "Date": "2014-01-01", "EmailSubject": "Hello World", "EmailRecipient": me@there.com" }"
2, "S", "{ "Id": 1, "Date": "2014-01-01", "SensorId": 1, "SensorName": "Some Sensor Name" }"
3, "E", "{ "Id": 2, "Date": "2014-01-01", "EmailSubject": "Hello World2", "EmailRecipient": me@there.com" }"
这里的理由是:
现在有许多存储过程实现将整个SQL结果转换为JSON http://jaminquimby.com/joomla253/servers/95-sql/sql-2008/145-code-tsql-convert-query-to-json,但我正在努力的是:</ p>
简而言之,我正在寻找一种解决方案,向我展示如何根据上述要求创建视图。所有指针和帮助非常感谢。
答案 0 :(得分:2)
虽然没有进入关于是否应该在数据库中进行的争论,但这似乎是一个有趣的难题,并且越来越多的人似乎想要在数据库级别至少进行简单的JSON转换。所以,有一些想法。
第一个想法是让SQL Server通过FOR XML
子句将行转换为XML来完成大部分工作。一行的简单,基于属性的XML表示在本质上与JSON结构非常相似;它只需要一点变换'。虽然解析可以通过PATINDEX
等纯粹在T-SQL中完成,但这只会使事情复杂化。因此,一个简单的正则表达式替换使得将name="value"
结构更改为"name": "value"
变得相当简单。以下示例使用SQL#库中提供的RegEx函数(我是其作者,但RegEx_Replace是免费版本)。
就在我完成时,我记得你可以通过.query()
函数对XML
字段或变量进行转换,并使用FLWOR Statement and Iteration循环遍历属性。所以我为第二次使用XML中间输出添加了另一列,但这是在纯T-SQL中完成的,而不是在RegEx的情况下需要CLR。我认为最简单的方法是放在相同的整体测试设置中,而不是重复大部分测试,只是为了改变几行。
第三个想法是返回SQLCLR,但要创建一个标量函数,该函数将表名和ID作为参数。然后,您可以使用“上下文连接”,它是进程内连接(因此速度很快),并构建“SELECT * FROM {table} WHERE ID = {value}”的动态SQL语句(显然检查单个输入 - 引号和破折号以避免SQL注入)。当您调用SqlDataReader时,您不仅可以轻松地逐步浏览每个字段,而且还可以深入了解每个字段的数据类型,并确定它是否为数字,如果是,则不要将双引号放在输出中的值。 [如果我明天或周末有时间,我会尝试把东西放在一起。]
SET NOCOUNT ON; SET ANSI_NULLS ON;
DECLARE @Table1 TABLE (
ID INT NOT NULL PRIMARY KEY,
[Date] DATETIME NOT NULL,
[EmailSubject] NVARCHAR(200) NOT NULL,
[EmailRecipient] NVARCHAR(200) NOT NULL );
INSERT INTO @Table1 VALUES (1, '2014-01-01', N'Hello World', N'me@here.com');
INSERT INTO @Table1 VALUES (2, '2014-03-02', N'Hello World2', N'me@there.com');
DECLARE @Table2 TABLE (
ID INT NOT NULL PRIMARY KEY,
[Date] DATETIME NOT NULL,
[SensorId] INT NOT NULL,
[SensorName] NVARCHAR(200) NOT NULL );
INSERT INTO @Table2 VALUES (1, '2014-01-01', 1, N'Some Sensor Name');
INSERT INTO @Table2 VALUES (2, '2014-02-01', 34, N'Another > Sensor Name');
---------------------------------------
;WITH cte AS
(
SELECT tmp.[Date], 'E' AS ActivityKind,
(SELECT t2.* FROM @Table1 t2 WHERE t2.ID = tmp.ID FOR XML RAW('wtf'))
AS [SourceForJSON]
FROM @Table1 tmp
UNION ALL
SELECT tmp.[Date], 'S' AS ActivityKind,
(SELECT t2.*, NEWID() AS [g=g] FROM @Table2 t2 WHERE t2.ID = tmp.ID
FOR XML RAW('wtf')) AS [SourceForJSON]
FROM @Table2 tmp
)
SELECT ROW_NUMBER() OVER (ORDER BY cte.[Date]) AS [Seq],
cte.ActivityKind,
cte.SourceForJSON,
N'{' +
REPLACE(
REPLACE(
REPLACE(
SUBSTRING(SQL#.RegEx_Replace(cte.SourceForJSON,
N' ([^ ="]+)="([^"]*)"',
N' "$1": "$2",', -1, 1, N'IgnoreCase'),
6, 4000),
N'",/>', '"}'),
N'>', N'>'),
N'<', N'<') AS [JSONviaRegEx],
N'{' + REPLACE(CONVERT(NVARCHAR(MAX),
CONVERT(XML, cte.SourceForJSON).query('
let $end := local-name((/wtf/@*)[last()])
for $item in /wtf/@*
return concat(""",
local-name($item),
"": "",
data($item),
""",
if (local-name($item) != $end) then ", " else "")
')), N'>', N'>') + N'}' AS [JSONviaXQuery]
FROM cte;
请记住,在上面的SQL中,cte查询可以很容易地封装在View中,并且转换(无论是通过SQLCLR / RegEx还是XML / XQuery)可以封装在T-SQL内联表中。函数并在主SELECT(从cte中选择的那个)中通过CROSS APPLY
使用。
修改强>
说到将XQuery封装到函数中并通过CROSS APPLY
调用,这里是:
功能:
CREATE FUNCTION dbo.JSONfromXMLviaXQuery (@SourceRow XML)
RETURNS TABLE
AS RETURN
SELECT N'{'
+ REPLACE(
CONVERT(NVARCHAR(MAX),
@SourceRow.query('
let $end := local-name((/wtf/@*)[last()])
for $item in /wtf/@*
return concat(""",
local-name($item),
"": "",
data($item),
""",
if (local-name($item) != $end) then "," else "")
')
),
N'>',
N'>')
+ N'}' AS [TheJSON];
设置:
CREATE TABLE #Table1 (
ID INT NOT NULL PRIMARY KEY,
[Date] DATETIME NOT NULL,
[EmailSubject] NVARCHAR(200) NOT NULL,
[EmailRecipient] NVARCHAR(200) NOT NULL );
INSERT INTO #Table1 VALUES (1, '2014-01-01', N'Hello World', N'me@here.com');
INSERT INTO #Table1 VALUES (2, '2014-03-02', N'Hello World2', N'me@there.com');
CREATE TABLE #Table2 (
ID INT NOT NULL PRIMARY KEY,
[Date] DATETIME NOT NULL,
[SensorId] INT NOT NULL,
[SensorName] NVARCHAR(200) NOT NULL );
INSERT INTO #Table2 VALUES (1, '2014-01-01', 1, N'Some Sensor Name');
INSERT INTO #Table2 VALUES (2, '2014-02-01', 34, N'Another > Sensor Name');
视图(或者如果我没有使用临时表,那将是什么):
--CREATE VIEW dbo.GetMyStuff
--AS
;WITH cte AS
(
SELECT tmp.[Date], 'E' AS ActivityKind,
(SELECT t2.* FROM #Table1 t2 WHERE t2.ID = tmp.ID
FOR XML RAW('wtf'), TYPE) AS [SourceForJSON]
FROM #Table1 tmp
UNION ALL
SELECT tmp.[Date], 'S' AS ActivityKind,
(SELECT t2.*, NEWID() AS [g=g] FROM #Table2 t2 WHERE t2.ID = tmp.ID
FOR XML RAW('wtf'), TYPE) AS [SourceForJSON]
FROM #Table2 tmp
)
SELECT ROW_NUMBER() OVER (ORDER BY cte.[Date]) AS [Seq],
cte.ActivityKind,
json.TheJSON
FROM cte
CROSS APPLY dbo.JSONfromXMLviaXQuery(cte.SourceForJSON) json;
结果:
Seq ActivityKind TheJSON
1 E {"ID": "1", "Date": "2014-01-01T00:00:00", "EmailSubject": "Hello World", "EmailRecipient": "me@here.com"}
2 S {"ID": "1", "Date": "2014-01-01T00:00:00", "SensorId": "1", "SensorName": "Some Sensor Name", "g_x003D_g": "3AE13983-6C6C-49E8-8E9D-437DAA62F910"}
3 S {"ID": "2", "Date": "2014-02-01T00:00:00", "SensorId": "34", "SensorName": "Another > Sensor Name", "g_x003D_g": "7E760F9D-2B5A-4FAA-8625-7B76AA59FE82"}
4 E {"ID": "2", "Date": "2014-03-02T00:00:00", "EmailSubject": "Hello World2", "EmailRecipient": "me@there.com"}