web-dev-qa-db-fra.com

Comment faire JSON à partir d'une requête SQL dans MS SQL 2014

Question: Quelle est la meilleure solution pour générer du JSON à partir d'une requête SQL dans MS SQL 2014? J'ai créé une procédure, mais elle est très lente.

Mon exemple:

DECLARE @customers xml;
DECLARE @json NVARCHAR(max);
SET @customers  =  (SELECT * FROM dbo.Customers FOR XML path, root)
EXEC [dbo].[HTTP_JSON]  @customers, @json

EXEC [dbo].[HTTP_JSON](@Shopping)

Create PROCEDURE [dbo].[HTTP_JSON]
@parameters xml, @response NVARCHAR(max) OUTPUT
WITH EXEC AS CALLER
AS
set @response = (SELECT Stuff(  
  (SELECT * from  
    (SELECT ',
    {'+  
      Stuff((SELECT ',"'+coalesce(b.c.value('local-name(.)', 'NVARCHAR(MAX)'),'')+'":"'+
                    b.c.value('text()[1]','NVARCHAR(MAX)') +'"'

             from x.a.nodes('*') b(c)  
             for xml path(''),TYPE).value('(./text())[1]','NVARCHAR(MAX)')
        ,1,1,'')+'}' 
   from @parameters.nodes('/root/*') x(a)  
   ) JSON(theLine)  
  for xml path(''),TYPE).value('.','NVARCHAR(MAX)' )
,1,1,''))
GO
8

Juste pour le plaisir, j'ai créé une fonction scalaire basée sur ma réponse précédente.

Mis à part le paramètre XML évident, j'en ai ajouté deux supplémentaires: 1) Inclure l'en-tête (illustré ci-dessous), et 2) Cas ToLower (je préfère mes noms de champs JSON en minuscules qui sont liés à mes classes et autres).

Si la requête comprend plusieurs enregistrements, un tableau formaté sera renvoyé.

Declare @Table table (ID int,Active bit,First_Name varchar(50),Last_Name varchar(50),EMail varchar(50))
Insert into @Table values
(1,1,'John','Smith','[email protected]'),
(2,0,'Jane','Doe'  ,'[email protected]')

Select A.ID
      ,A.Last_Name
      ,A.First_Name
      ,B.JSON
From  @Table A 
Cross Apply (Select JSON=[dbo].[udf-Str-JSON](0,1,(Select A.* For XML Raw)) ) B

retourne

ID  Last_Name   First_Name  JSON
1   Smith       John        {"id":"1","active":"1","first_name":"John","last_name":"Smith","email":"[email protected]"}
2   Doe         Jane        {"id":"2","active":"0","first_name":"Jane","last_name":"Doe","email":"[email protected]"}

Ou encore plus simplement

Select JSON=[dbo].[udf-Str-JSON](0,1,(Select * From @Table for XML RAW))

retourne avec l'en-tête ON

{
    "status": {
        "successful": "true",
        "timestamp": "2016-10-09 06:08:16 GMT",
        "rows": "2"
    },
    "results": [{
        "id": "1",
        "active": "1",
        "first_name": "John",
        "last_name": "Smith",
        "email": "[email protected]"
    }, {
        "id": "2",
        "active": "0",
        "first_name": "Jane",
        "last_name": "Doe",
        "email": "[email protected]"
    }]
}

retourne avec en-tête désactivé

[{
    "id": "1",
    "active": "1",
    "first_name": "John",
    "last_name": "Smith",
    "email": "[email protected]"
}, {
    "id": "2",
    "active": "0",
    "first_name": "Jane",
    "last_name": "Doe",
    "email": "[email protected]"
}]

l'UDF

ALTER FUNCTION [dbo].[udf-Str-JSON] (@IncludeHead int,@ToLowerCase int,@XML xml)
Returns varchar(max)
AS
Begin
    Declare @Head varchar(max) = '',@JSON varchar(max) = ''
    ; with cteEAV as (Select RowNr=Row_Number() over (Order By (Select NULL))
                            ,Entity    = xRow.value('@*[1]','varchar(100)')
                            ,Attribute = xAtt.value('local-name(.)','varchar(100)')
                            ,Value     = xAtt.value('.','varchar(max)') 
                       From  @XML.nodes('/row') As R(xRow) 
                       Cross Apply R.xRow.nodes('./@*') As A(xAtt) )
          ,cteSum as (Select Records=count(Distinct Entity)
                            ,Head = IIF(@IncludeHead=0,IIF(count(Distinct Entity)<=1,'[getResults]','[[getResults]]'),Concat('{"status":{"successful":"true","timestamp":"',Format(GetUTCDate(),'yyyy-MM-dd hh:mm:ss '),'GMT','","rows":"',count(Distinct Entity),'"},"results":[[getResults]]}') ) 
                       From  cteEAV)
          ,cteBld as (Select *
                            ,NewRow=IIF(Lag(Entity,1)  over (Partition By Entity Order By (Select NULL))=Entity,'',',{')
                            ,EndRow=IIF(Lead(Entity,1) over (Partition By Entity Order By (Select NULL))=Entity,',','}')
                            ,JSON=Concat('"',IIF(@ToLowerCase=1,Lower(Attribute),Attribute),'":','"',Value,'"') 
                       From  cteEAV )
    Select @JSON = @JSON+NewRow+JSON+EndRow,@Head = Head From cteBld, cteSum
    Return Replace(@Head,'[getResults]',Stuff(@JSON,1,1,''))
End
-- Parameter 1: @IncludeHead 1/0
-- Parameter 2: @ToLowerCase 1/0 (converts field name to lowercase
-- Parameter 3: (Select * From ... for XML RAW)

** EDIT - Typo corrigé

12
John Cappelletti

Les éléments suivants doivent créer le tableau JSON pour à peu près n'importe quel ensemble de données. Cependant, je n'ai pas encore créé de moyen de convertir le bit en vrai/faux.

Un seul point à considérer: la première colonne du SELECT initial doit être la clé primaire qui équivaut au champ ENTITY. Dans ce cas, Select * from @User for XML RAW ... ID est l'entité et il se trouve que c'est le premier champ de la table

En ce qui concerne les performances, 500 enregistrements avec 19 champs créent une chaîne JSON 191 987 octets en 0,694 seconde (50 enregistrements en 0,098 seconde)

Considérer ce qui suit:

Declare @User table (ID int,Active bit,First_Name varchar(50),Last_Name varchar(50),EMail varchar(50),LastOn DateTime)
Insert into @User values
(1,1,'John','Smith','[email protected]','2016-10-05 17:32:41.903'),
(2,0,'Jane','Doe'  ,'[email protected]','2016-10-05 08:25:18.203')

Declare @XML   xml = (Select * From @User  for XML RAW)
Declare @JSON  varchar(max) = ''

;with cteEAV as (
      Select RowNr     = Row_Number() over (Order By (Select NULL))
            ,Entity    = xRow.value('@*[1]','varchar(100)')
            ,Attribute = xAtt.value('local-name(.)','varchar(100)')
            ,Value     = xAtt.value('.','varchar(max)') 
       From  @XML.nodes('/row') As A(xRow)
       Cross Apply A.xRow.nodes('./@*') As B(xAtt) )
     ,cteBld as (
      Select *
            ,NewRow = IIF(Lag(Entity,1)  over (Partition By Entity Order By (Select NULL))=Entity,'',',{')
            ,EndRow = IIF(Lead(Entity,1) over (Partition By Entity Order By (Select NULL))=Entity,',','}')
            ,JSON   = Concat('"',Attribute,'":','"',Value,'"')
       From  cteEAV )
Select @JSON = @JSON+NewRow+JSON+EndRow
 From  cteBld 

Select '['+Stuff(@JSON,1,1,'')+']'

Renvoie

[{"ID":1, "Active":1, "First_Name":"John", "Last_Name":"Smith", "EMail":"[email protected]", "LastOn":"2016-10-05T17:32:41.903", "TotalSales":25569.0000} ,{"ID":2, "Active":0, "First_Name":"Jane", "Last_Name":"Doe", "EMail":"[email protected]", "LastOn":"2016-10-05T08:25:18.203", "TotalSales":22888.0000}]

Une version plus lisible

enter image description here

cteEAV détournera dynamiquement les données et générera ce qui suit:

enter image description here

cteBLD étendra et ajoutera des drapeaux New/End Row enter image description here

La sélection finale

Cela mettra tout cela ensemble et générera une chaîne finale qui peut être enveloppée ou imbriquée à votre guise.

4
John Cappelletti