改进SQL Server查询以将任意表转换为JSON

时间:2013-02-13 23:26:30

标签: sql sql-server json converter

经过大量搜索并拼凑出使用Web上的FOR XML和.nodes()命令转换结果集的非常出色的技术,我能够创建这个单一查询(不是存储过程)将任意SQL查询转换为JSON数组做得相当不错。

查询将每个数据行编码为具有前导逗号的单个JSON对象。 数据行用括号括起来,然后整个结果集将被导出到文件中。

我想看看有没有人能看到改善其表现的方法?

以下是带有示例表的查询:

declare @xd table (col1 varchar(max), col2 int, col3 real, colNull int) 

insert into @xd 
select '', null, null, null
UNION ALL select 'ItemA', 123, 123.123, null
UNION ALL select 'ItemB', 456, 456.456, null
UNION ALL select '7890', 789, 789.789, null

select '[{}'
UNION ALL
select ',{' + STUFF((
    (select ','
        + '"' + r.value('local-name(.)', 'varchar(max)') + '":'
        + case when r.value('./@xsi:nil', 'varchar(max)') = 'true' then 'null'
        when isnumeric(r.value('.', 'varchar(max)')) = 1
            then r.value('.', 'varchar(max)')
        else '"' + r.value('.', 'varchar(max)') + '"'
        end
    from rows.nodes('/row/*') as x(r) for xml path(''))
    ), 1, 1, '') + '}'
from (
    -- Arbitrary query goes here, (fields go where t.* is, table where @xd t is)
    select (select t.* for xml raw,type,elements XSINIL) rows
    from @xd t
) xd
UNION ALL
select ']'

我对它的最大批评是,它的速度非常慢 目前大约需要3:30,大约42,000行。

我的另一个大批评是,它目前假设所有看起来像数字的数字都是数字。它至少不会尝试发现列类型(我甚至不确定它是否可以)。

最后的一个小批评是第一个数据行预先有逗号,技术上不应该。为了弥补这一点,它需要在第一行中启动JSON数组的空JSON对象。

邀请其他批评(最好是解决方案),我唯一真正的限制是解决方案在许多任意SQL查询上都可以很好地重复,而不必明确地识别列名。

我正在使用SQL Server 2012。

非常感谢和我一样寻找广义SQL结果的人 - > JSON阵列转换器,ENJOY!

2 个答案:

答案 0 :(得分:11)

我说如果你真的想提高性能,请使用元编程。下面的示例尝试使用40,000行并在不到一秒的时间内返回结果(不计入插入最初的40k行,在此示例中仅需要大约2秒)。它还会考虑您的数据类型,以便不将数字括在引号中。

declare @xd table (col1 varchar(max), col2 int, col3 real, colDate datetime, colNull int);

declare @i int = 0;

while @i < 10000 begin
    set @i += 1;
    insert into @xd
    select '', null, null, null, null
    union all select 'ItemA', 123, 123.123, getDate(), null
    union all select 'ItemB', 456, 456.456, getDate(), null
    union all select '7890', 789, 789.789, getDate(), null;
end;

select *
into #json_base
from (
    -- Insert SQL Statement here
    select * from @xd
) t;

declare @columns table (
    id int identity primary key,
    name sysname,
    datatype sysname,
    is_number bit,
    is_date bit);

insert into @columns(name, datatype, is_number, is_date)
select columns.name, types.name,
       case when number_types.name is not NULL
            then 1 else 0
       end as is_number,
       case when date_types.name is not NULL
            then 1 else 0
       end as is_date
from tempdb.sys.columns
join tempdb.sys.types
    on (columns.system_type_id = types.system_type_id)
left join (values ('int'), ('real'), ('numeric'),
                  ('decimal'), ('bigint'), ('tinyint')) as number_types(name)
    on (types.name = number_types.name)
left join (values ('date'), ('datetime'), ('datetime2'),
                  ('smalldatetime'), ('time'), ('datetimeoffset')) as date_types(name)
    on (types.name = date_types.name)
where object_id = OBJECT_ID('tempdb..#json_base');

declare @field_list varchar(max) = STUFF((
    select '+'',''+' + QUOTENAME(QUOTENAME(name, '"') + ':', '''')
           + '+' + case when is_number = 1
                        then 'COALESCE(LTRIM('
                                + QUOTENAME(name) + '),''null'')'
                        when is_date = 1
                        then 'COALESCE(QUOTENAME(LTRIM(convert(varchar(max), '
                                + QUOTENAME(name) + ', 126)),''"''),''null'')'
                        else 'COALESCE(QUOTENAME('
                                + QUOTENAME(name) + ',''"''),''null'')'
                   end
    from @columns
    for xml path('')),
    1, 5, '');

create table #json_result (
    id int identity primary key,
    line varchar(max));

declare @sql varchar(max) = REPLACE(
    'insert into #json_result '
  + 'select '',{''+{f}+''}'' '
  + 'from #json_base', '{f}', @field_list);

exec(@sql);

update #json_result
set line = STUFF(line, 1, 1, '')
where id = 1;

select '['
UNION ALL
select line
from #json_result
UNION ALL
select ']';

drop table #json_base;
drop table #json_result;

答案 1 :(得分:1)

来自Firoz Ansari

CREATE PROCEDURE [dbo].[GetJSON] (
@ParameterSQL AS VARCHAR(MAX)
)
AS
BEGIN

DECLARE @SQL NVARCHAR(MAX)
DECLARE @XMLString VARCHAR(MAX)
DECLARE @XML XML
DECLARE @Paramlist NVARCHAR(1000)
SET @Paramlist = N'@XML XML OUTPUT'
SET @SQL = 'WITH PrepareTable (XMLString) '
SET @SQL = @SQL + 'AS ( '
SET @SQL = @SQL + @ParameterSQL+ ' FOR XML RAW, TYPE, ELEMENTS '
SET @SQL = @SQL + ') '
SET @SQL = @SQL + 'SELECT @XML = XMLString FROM PrepareTable '
EXEC sp_executesql @SQL, @Paramlist, @XML=@XML OUTPUT
SET @XMLString = CAST(@XML AS VARCHAR(MAX))

DECLARE @JSON VARCHAR(MAX)
DECLARE @Row VARCHAR(MAX)
DECLARE @RowStart INT
DECLARE @RowEnd INT
DECLARE @FieldStart INT
DECLARE @FieldEnd INT
DECLARE @Key VARCHAR(MAX)
DECLARE @Value VARCHAR(MAX)

DECLARE @StartRoot VARCHAR(100); SET @StartRoot = ''
DECLARE @EndRoot VARCHAR(100); SET @EndRoot = ''
DECLARE @StartField VARCHAR(100); SET @StartField = ''

SET @RowStart = CharIndex(@StartRoot, @XMLString, 0)
SET @JSON = ''
WHILE @RowStart &gt; 0
BEGIN
    SET @RowStart = @RowStart+Len(@StartRoot)
    SET @RowEnd = CharIndex(@EndRoot, @XMLString, @RowStart)
    SET @Row = SubString(@XMLString, @RowStart, @RowEnd-@RowStart)
    SET @JSON = @JSON+'{'

    -- for each row
    SET @FieldStart = CharIndex(@StartField, @Row, 0)
    WHILE @FieldStart &gt; 0
    BEGIN
        -- parse node key
        SET @FieldStart = @FieldStart+Len(@StartField)
        SET @FieldEnd = CharIndex(@EndField, @Row, @FieldStart)
        SET @Key = SubString(@Row, @FieldStart, @FieldEnd-@FieldStart)
        SET @JSON = @JSON+'"'+@Key+'":'

        -- parse node value
        SET @FieldStart = @FieldEnd+1
        SET @FieldEnd = CharIndex('0 SET @JSON = SubString(@JSON, 0, LEN(@JSON))
    SET @JSON = @JSON+'},'
    --/ for each row

    SET @RowStart = CharIndex(@StartRoot, @XMLString, @RowEnd)
END
IF LEN(@JSON) > 0 SET @JSON = SubString(@JSON, 0, LEN(@JSON))
SET @JSON = '[' + @JSON + ']'
SELECT @JSON
END