SQL Server视图使用JSON提供一致的表结构

时间:2014-11-19 03:00:29

标签: c# sql-server json entity-framework

我想创建一个包含以下列的只读视图:

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来生成视图
  • 此数据仅在客户端读取
  • 通过拥有一致的视图结构,即Id,ActivityKind,Payload,我想在其中添加一些额外的表,我可以这样做,客户端代码将被修改为处理基于ActivityKind的JSON解码

现在有许多存储过程实现将整个SQL结果转换为JSON http://jaminquimby.com/joomla253/servers/95-sql/sql-2008/145-code-tsql-convert-query-to-json,但我正在努力的是:<​​/ p>

  1. 获取整个视图的单一运行顺序
  2. 实际执行因为这必须在逐个记录的基础上进行。
  3. 简而言之,我正在寻找一种解决方案,向我展示如何根据上述要求创建视图。所有指针和帮助非常感谢。

1 个答案:

答案 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'&gt;', N'>'),
                N'&lt;', 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("&quot;",
                local-name($item),
                "&quot;: &quot;",
                data($item),
                "&quot;",
                if (local-name($item) != $end) then ", " else "")
          ')), N'&gt;', 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("&quot;",
                                    local-name($item),
                                    "&quot;: &quot;",
                                    data($item),
                                   "&quot;",
                                   if (local-name($item) != $end) then "," else "")
                      ')
                    ),
             N'&gt;',
             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"}