2013-02-13 55 views
8

經過大量搜索並將使用Web上的FOR XML和.nodes()命令轉換結果集的非常優秀的技術拼湊起來之後,我能夠創建這個單一的查詢(不是存儲過程),它將任何任意的SQL查詢轉換爲JSON數組做了相當不錯的工作。改進SQL Server查詢以將任意表轉換爲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點半~42,000行。

我的另一個大的批評是,它目前假設所有看起來像數字的東西都是數字。它不嘗試發現列類型(至少我不確定它是否可以)。

最後一個小問題是,第一個數據行在前面會有一個逗號,在技術上它不應該。爲了彌補這一點,它需要在第一行中啓動JSON數組的空JSON對象。

其他的批評(最好是有解決方案的)被邀請,我唯一真正的限制是該解決方案在很多任意的SQL查詢中可以重複使用,而無需明確標識列名。

我使用SQL Server 2012的

感謝,並給其他人喜歡我,誰一直在尋找一個廣義SQL結果 - > JSON數組轉換器,盡情享受!

+0

雖然我很讚賞你的SQL福,我要問:爲什麼?真實世界中你需要這樣做的場景是什麼?我不想在這裏消極,只是爲什麼你需要這個而感到困惑。 – 2013-02-14 00:03:12

+0

在這種情況下,我正在尋找一種特別的方式來快速將結果集加載到NoSQL數據庫(如CouchDB)中,而無需構建大量基礎結構或將任何內容添加到我的生產SQL環境中。 Mongo,Couch等人。似乎使用JSON作爲數據傳輸的通用語言。一旦進入NoSQL數據庫,我們可以嘗試對數據集進行切片和切塊,以瞭解它們的性能。複製CouchDB數據庫以在遠程桌面,便攜式計算機,智能手機等上創建本地存儲庫比管理SQL複製基礎架構要容易得多。所以我們正在做一些盡職調查。 – 2013-02-14 00:30:10

+0

讓我們不要在這裏被愚弄,大多數是通過搜索現有的SQL到JSON和SQL來找到鍵/值對的答案。是的,我把它放在一起,通過UNION ALL在它周圍放置了一些括號,使用case語句來處理引號更聰明一點,並開始嘗試使用XSINIL的東西(也許我甚至知道其中一些實際上是如何工作的; ))但我真的不能承認這一點。關於我最近做的唯一事情是將底部的子查詢合併爲一個XML數據集,然後讓上層查詢將其轉換爲鍵/值對。 – 2013-02-14 01:02:38

回答

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; 
+0

不錯。我知道我不應該睡在它上面 - 這也是我要建議的方法。實際上可能變得非常有用... – 2013-02-14 12:48:11

+0

不錯!在我的實際桌子上只有6秒鐘。另一個副作用是日期,因爲2月8日2013年12:00 AM。讓我看看 – 2013-02-14 16:58:38

+0

好吧,我擴展了我的解決方案以添加一個「is_date」位字段,在接受之前是否適合使用我的修改來調整原始答案?感謝您向我展示如何加入「價值觀」,我從未見過這種技術。這很酷! – 2013-02-14 17:46:26

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, @[email protected] 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, @[email protected]) 
    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, @[email protected]) 
     SET @JSON = @JSON+'"'[email protected]+'":' 

     -- 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 
+0

此方法的問題/限制是結果僅限於@JSON varchar(max)字符串限制。所以當結果變大時,結果開始被截斷。 – 2013-02-14 17:30:57

+0

順便說一句,謝謝你的回答,即使我無法使用它! – 2013-02-14 23:45:53