Analyser JSON dans TSQL

115

Est - il possible d'analyser JSONenTSQL ?

Je ne veux pas créer une JSONchaîne; je souhaite plutôt analyser une JSONchaîne transmise en tant que fichier parameter.

R0b0tn1k
la source

Réponses:

61

Mise à jour: à partir de SQL Server 2016, l' analyse JSON dans TSQL est désormais possible .

Nativement, il n'y a pas de support. Vous devrez utiliser CLR. C'est aussi simple que cela, sauf si vous avez une énorme strie masochiste et que vous voulez écrire un analyseur JSON en SQL

Normalement, les gens demandent une sortie JSON à la base de données et il existe des exemples sur Internet. Mais dans une DB?

gbn
la source
60
JSON est un protocole assez simple, il ne nécessite donc pas vraiment de masochisme. Une fois que vous l'avez, vous pouvez utiliser la routine unique pour tous vos JSON. Quoi qu'il en soit, je l'ai fait pour vous ici simple-talk.com/sql/t-sql-programming/…
Phil Factor
10
Phil Factor: Je lis vos articles depuis de nombreuses années. Si vous n'aviez pas écrit cet article aujourd'hui, je l'aurais probablement cité il y a 6 mois quand j'ai répondu ...
gbn
9
Il existe une prise en charge intégrée pour l'analyse du texte JSON dans le nouveau SQL Server 2016.
Jovan MSFT
Voici un article très utile du site Web Simple Talk qui explique comment prendre une chaîne JSon et la générer dans des tables et des colonnes pouvant être interrogées. Ceci est pour SQL Server 2016: https://www.simple-talk.com/sql/learn-sql-server/json-support-in-sql-server-2016/
codeaf
Voici un exemple de code sur la façon d'analyser JSON à l'aide de l'approche CLR: blog.dotnetframework.org/2016/12/06
Fiach Reid
238

Il me semble avoir une énorme tendance masochiste dans la mesure où j'ai écrit un analyseur JSON. Il convertit un document JSON en une table de liste SQL Adjacency, qui est facile à utiliser pour mettre à jour vos tables de données. En fait, j'ai fait pire, en ce sens que j'ai fait du code pour faire le processus inverse, qui consiste à passer d'une table hiérarchique à une chaîne JSON

L'article et le code sont ici: Consommer des chaînes Json dans un serveur SQL .

Select * from parseJSON('{
  "Person":
  {
     "firstName": "John",
     "lastName": "Smith",
     "age": 25,
     "Address":
     {
        "streetAddress":"21 2nd Street",
        "city":"New York",
        "state":"NY",
        "postalCode":"10021"
     },
     "PhoneNumbers":
     {
        "home":"212 555-1234",
        "fax":"646 555-4567"
     }
  }
}
')

Obtenir:

entrez la description de l'image ici

Facteur Phil
la source
C'est une fonctionnalité intéressante mais qui a quelques limitations, par exemple en supprimant le "-" des nombres négatifs.
Gavin
très cool! vous avez une faute de frappe dans le script: IF OBJECT_ID (N'dbo.parseJSON ') N'EST PAS NULL DROP FUNCTION dbo.JSONEscaped GO - devrait tester dbo.JSONEscaped dans le test IF.
isapir
@phil dbo.parseJSON travaille très lentement en cas de données volumineuses. alors pouvons-nous réduire le temps de cela en utilisant d'autres méthodes à l'intérieur?
cracker
Je suis curieux, que pensez-vous de la prise en charge JSON native nouvellement ajoutée de SQL Server 2016?
Consultation gratuite du
C'est génial, mais y a-t-il un moyen de ne pas enlever le "-" des nombres négatifs? Je ne peux pas vraiment comprendre où ni pourquoi cela se produit ...
Nikoline Hejbøl
26

Enfin SQL Server 2016 ajoutera le support JSON natif !!

Réf:

Les fonctionnalités supplémentaires de SQL Server 2016 incluent:

  • Améliorations supplémentaires de la sécurité pour la sécurité au niveau des lignes et le masquage dynamique des données pour compléter nos investissements en matière de sécurité avec Always
    Encrypted.
  • Améliorations apportées à AlwaysOn pour une disponibilité et une reprise après sinistre plus robustes avec plusieurs réplicas synchrones et un
    équilibrage de charge secondaire .
  • Prise en charge native de JSON pour offrir de meilleures performances et une meilleure prise en charge de vos nombreux types de données.
  • Les outils SQL Server Enterprise Information Management (EIM) et Analysis Services bénéficient d'une mise à niveau en termes de performances, d'utilisabilité et d'évolutivité.
  • Sauvegardes hybrides plus rapides, haute disponibilité et scénarios de reprise après sinistre pour sauvegarder et restaurer vos bases de données
    locales sur Azure et placer vos secondaires SQL Server AlwaysOn dans Azure.

Annonce: http://blogs.technet.com/b/dataplatforminsider/archive/2015/05/04/sql-server-2016-public-preview-coming-this-summer.aspx

Article de blog sur les fonctionnalités: http://blogs.msdn.com/b/jocapc/archive/2015/05/16/json-support-in-sql-server-2016.aspx

Irvin Dominin
la source
2
Probablement SQL Server 2016 CTP 3 aura le support JSON vers SQL Server avec la syntaxe OpenJSON
Eralper le
7

SQL Server 2016 prend en charge l' json dataanalyse à l'aide de OPENJSON. Vous pouvez utiliser OPENJSONpour mapper json dataaux lignes et aux colonnes.

Votre json Data

[
 { "id" : 2,"name": "John"},
 { "id" : 5,"name": "John"}
]

Voici comment vous pouvez gérer json dans SQL

//@pJson is json data passed from code.  

INSERT INTO YourTable (id, Name)
 SELECT id, name
 FROM OPENJSON(@pJson)
 WITH (id int,
       name nvarchar(max))

Voici un article détaillé qui couvre ce sujet.

Mairaj Ahmad
la source
5

J'ai développé mon propre analyseur JSON SQL Server 2016+ il y a quelque temps. J'utilise cela dans tous mes projets - de très bonnes performances. J'espère que cela peut aussi aider quelqu'un d'autre.

Code complet de la fonction:

ALTER FUNCTION [dbo].[SmartParseJSON] (@json NVARCHAR(MAX))
RETURNS @Parsed TABLE (Parent NVARCHAR(MAX),Path NVARCHAR(MAX),Level INT,Param NVARCHAR(4000),Type NVARCHAR(255),Value NVARCHAR(MAX),GenericPath NVARCHAR(MAX))
AS
BEGIN
    -- Author: Vitaly Borisov
    -- Create date: 2018-03-23
    ;WITH crData AS (
        SELECT CAST(NULL AS NVARCHAR(4000)) COLLATE DATABASE_DEFAULT AS [Parent]
            ,j.[Key] AS [Param],j.Value,j.Type
            ,j.[Key] AS [Path],0 AS [Level]
            ,j.[Key] AS [GenericPath]
        FROM OPENJSON(@json) j
        UNION ALL
        SELECT CAST(d.Path AS NVARCHAR(4000)) COLLATE DATABASE_DEFAULT AS [Parent]
            ,j.[Key] AS [Param],j.Value,j.Type 
            ,d.Path + CASE d.Type WHEN 5 THEN '.' WHEN 4 THEN '[' ELSE '' END + j.[Key] + CASE d.Type WHEN 4 THEN ']' ELSE '' END AS [Path]
            ,d.Level+1
            ,d.GenericPath + CASE d.Type WHEN 5 THEN '.' + j.[Key] ELSE '' END AS [GenericPath]
        FROM crData d 
        CROSS APPLY OPENJSON(d.Value) j
        WHERE ISJSON(d.Value) = 1
    )
    INSERT INTO @Parsed(Parent, Path, Level, Param, Type, Value, GenericPath)
    SELECT d.Parent,d.Path,d.Level,d.Param
        ,CASE d.Type 
            WHEN 1 THEN CASE WHEN TRY_CONVERT(UNIQUEIDENTIFIER,d.Value) IS NOT NULL THEN 'UNIQUEIDENTIFIER' ELSE 'NVARCHAR(MAX)' END 
            WHEN 2 THEN 'INT' 
            WHEN 3 THEN 'BIT' 
            WHEN 4 THEN 'Array' 
            WHEN 5 THEN 'Object' 
                ELSE 'NVARCHAR(MAX)'
         END AS [Type]
        ,CASE 
            WHEN d.Type = 3 AND d.Value = 'true' THEN '1'
            WHEN d.Type = 3 AND d.Value = 'false' THEN '0'
                ELSE d.Value
         END AS [Value]
        ,d.GenericPath
    FROM crData d
    OPTION(MAXRECURSION 1000) /*Limit to 1000 levels deep*/
    ;
    RETURN;
END
GO

Exemple d'utilisation:

DECLARE @json NVARCHAR(MAX) = '{"Objects":[{"SomeKeyID":1,"Value":3}],"SomeParam":"Lalala"}';
SELECT j.Parent, j.Path, j.Level, j.Param, j.Type, j.Value, j.GenericPath 
FROM dbo.SmartParseJSON(@json) j;

Exemple d'utilisation à plusieurs niveaux:

DECLARE @json NVARCHAR(MAX) = '{"Objects":[{"SomeKeyID":1,"Value":3}],"SomeParam":"Lalala"}';
DROP TABLE IF EXISTS #ParsedData;
SELECT j.Parent, j.Path, j.Level, j.Param, j.Type, j.Value, j.GenericPath 
INTO #ParsedData
FROM dbo.SmartParseJSON(@json) j;

SELECT COALESCE(p2.GenericPath,p.GenericPath) AS [GenericPath]
    ,COALESCE(p2.Param,p.Param) AS [Param]
    ,COALESCE(p2.Value,p.Value) AS [Value]
FROM #ParsedData p
LEFT JOIN #ParsedData p1 ON p1.Parent = p.Path AND p1.Level = 1
LEFT JOIN #ParsedData p2 ON p2.Parent = p1.Path AND p2.Level = 2
WHERE p.Level = 0
;
DROP TABLE IF EXISTS #ParsedData;
Vitaly Borisov
la source
1
Merci d'avoir partagé.
André Voltolini
4

J'ai aussi une énorme séquence masochiste car j'ai écrit un autre analyseur JSON. Celui-ci utilise une approche procédurale. Il utilise une table de liste de hiérarchie SQL similaire pour stocker les données analysées. Le paquet contient également:

  • Processus inverse: de la hiérarchie au JSON
  • Fonctions de requête: pour récupérer des valeurs particulières à partir d'un objet JSON

N'hésitez pas à l'utiliser et à vous amuser avec

http://www.codeproject.com/Articles/1000953/JSON-for-Sql-Server-Part

jsegarra
la source
+1 merci fonctionne très bien, mieux que la version PhilFactor. J'ai dû baisser légèrement pour SQL Server 2008 (pas de iiffonction ou OFFSET)
vanderwyst
0
CREATE FUNCTION dbo.parseJSON( @JSON NVARCHAR(MAX))
RETURNS @hierarchy TABLE
  (
   element_id INT IDENTITY(1, 1) NOT NULL, /* internal surrogate primary key gives the order of parsing and the list order */
   sequenceNo [int] NULL, /* the place in the sequence for the element */
   parent_ID INT,/* if the element has a parent then it is in this column. The document is the ultimate parent, so you can get the structure from recursing from the document */
   Object_ID INT,/* each list or object has an object id. This ties all elements to a parent. Lists are treated as objects here */
   NAME NVARCHAR(2000),/* the name of the object */
   StringValue NVARCHAR(MAX) NOT NULL,/*the string representation of the value of the element. */
   ValueType VARCHAR(10) NOT null /* the declared type of the value represented as a string in StringValue*/
  )
AS
BEGIN
  DECLARE
    @FirstObject INT, --the index of the first open bracket found in the JSON string
    @OpenDelimiter INT,--the index of the next open bracket found in the JSON string
    @NextOpenDelimiter INT,--the index of subsequent open bracket found in the JSON string
    @NextCloseDelimiter INT,--the index of subsequent close bracket found in the JSON string
    @Type NVARCHAR(10),--whether it denotes an object or an array
    @NextCloseDelimiterChar CHAR(1),--either a '}' or a ']'
    @Contents NVARCHAR(MAX), --the unparsed contents of the bracketed expression
    @Start INT, --index of the start of the token that you are parsing
    @end INT,--index of the end of the token that you are parsing
    @param INT,--the parameter at the end of the next Object/Array token
    @EndOfName INT,--the index of the start of the parameter at end of Object/Array token
    @token NVARCHAR(200),--either a string or object
    @value NVARCHAR(MAX), -- the value as a string
    @SequenceNo int, -- the sequence number within a list
    @name NVARCHAR(200), --the name as a string
    @parent_ID INT,--the next parent ID to allocate
    @lenJSON INT,--the current length of the JSON String
    @characters NCHAR(36),--used to convert hex to decimal
    @result BIGINT,--the value of the hex symbol being parsed
    @index SMALLINT,--used for parsing the hex value
    @Escape INT --the index of the next escape character


  DECLARE @Strings TABLE /* in this temporary table we keep all strings, even the names of the elements, since they are 'escaped' in a different way, and may contain, unescaped, brackets denoting objects or lists. These are replaced in the JSON string by tokens representing the string */
    (
     String_ID INT IDENTITY(1, 1),
     StringValue NVARCHAR(MAX)
    )
  SELECT--initialise the characters to convert hex to ascii
    @characters='0123456789abcdefghijklmnopqrstuvwxyz',
    @SequenceNo=0, --set the sequence no. to something sensible.
  /* firstly we process all strings. This is done because [{} and ] aren't escaped in strings, which complicates an iterative parse. */
    @parent_ID=0;
  WHILE 1=1 --forever until there is nothing more to do
    BEGIN
      SELECT
        @start=PATINDEX('%[^a-zA-Z]["]%', @json collate SQL_Latin1_General_CP850_Bin);--next delimited string
      IF @start=0 BREAK --no more so drop through the WHILE loop
      IF SUBSTRING(@json, @start+1, 1)='"'
        BEGIN --Delimited Name
          SET @start=@Start+1;
          SET @end=PATINDEX('%[^\]["]%', RIGHT(@json, LEN(@json+'|')-@start) collate SQL_Latin1_General_CP850_Bin);
        END
      IF @end=0 --no end delimiter to last string
        BREAK --no more
      SELECT @token=SUBSTRING(@json, @start+1, @end-1)
      --now put in the escaped control characters
      SELECT @token=REPLACE(@token, FROMString, TOString)
      FROM
        (SELECT
          '\"' AS FromString, '"' AS ToString
         UNION ALL SELECT '\\', '\'
         UNION ALL SELECT '\/', '/'
         UNION ALL SELECT '\b', CHAR(08)
         UNION ALL SELECT '\f', CHAR(12)
         UNION ALL SELECT '\n', CHAR(10)
         UNION ALL SELECT '\r', CHAR(13)
         UNION ALL SELECT '\t', CHAR(09)
        ) substitutions
      SELECT @result=0, @escape=1
  --Begin to take out any hex escape codes
      WHILE @escape>0
        BEGIN
          SELECT @index=0,
          --find the next hex escape sequence
          @escape=PATINDEX('%\x[0-9a-f][0-9a-f][0-9a-f][0-9a-f]%', @token collate SQL_Latin1_General_CP850_Bin)
          IF @escape>0 --if there is one
            BEGIN
              WHILE @index<4 --there are always four digits to a \x sequence  
                BEGIN
                  SELECT --determine its value
                    @result=@result+POWER(16, @index)
                    *(CHARINDEX(SUBSTRING(@token, @escape+2+3-@index, 1),
                                @characters)-1), @index=@index+1 ;

                END
                -- and replace the hex sequence by its unicode value
              SELECT @token=STUFF(@token, @escape, 6, NCHAR(@result))
            END
        END
      --now store the string away
      INSERT INTO @Strings (StringValue) SELECT @token
      -- and replace the string with a token
      SELECT @JSON=STUFF(@json, @start, @end+1,
                    '@string'+CONVERT(NVARCHAR(5), @@identity))
    END
  -- all strings are now removed. Now we find the first leaf. 
  WHILE 1=1  --forever until there is nothing more to do
  BEGIN

  SELECT @parent_ID=@parent_ID+1
  --find the first object or list by looking for the open bracket
  SELECT @FirstObject=PATINDEX('%[{[[]%', @json collate SQL_Latin1_General_CP850_Bin)--object or array
  IF @FirstObject = 0 BREAK
  IF (SUBSTRING(@json, @FirstObject, 1)='{')
    SELECT @NextCloseDelimiterChar='}', @type='object'
  ELSE
    SELECT @NextCloseDelimiterChar=']', @type='array'
  SELECT @OpenDelimiter=@firstObject

  WHILE 1=1 --find the innermost object or list...
    BEGIN
      SELECT
        @lenJSON=LEN(@JSON+'|')-1
  --find the matching close-delimiter proceeding after the open-delimiter
      SELECT
        @NextCloseDelimiter=CHARINDEX(@NextCloseDelimiterChar, @json,
                                      @OpenDelimiter+1)
  --is there an intervening open-delimiter of either type
      SELECT @NextOpenDelimiter=PATINDEX('%[{[[]%',
             RIGHT(@json, @lenJSON-@OpenDelimiter)collate SQL_Latin1_General_CP850_Bin)--object
      IF @NextOpenDelimiter=0
        BREAK
      SELECT @NextOpenDelimiter=@NextOpenDelimiter+@OpenDelimiter
      IF @NextCloseDelimiter<@NextOpenDelimiter
        BREAK
      IF SUBSTRING(@json, @NextOpenDelimiter, 1)='{'
        SELECT @NextCloseDelimiterChar='}', @type='object'
      ELSE
        SELECT @NextCloseDelimiterChar=']', @type='array'
      SELECT @OpenDelimiter=@NextOpenDelimiter
    END
  ---and parse out the list or name/value pairs
  SELECT
    @contents=SUBSTRING(@json, @OpenDelimiter+1,
                        @NextCloseDelimiter-@OpenDelimiter-1)
  SELECT
    @JSON=STUFF(@json, @OpenDelimiter,
                @NextCloseDelimiter-@OpenDelimiter+1,
                '@'+@type+CONVERT(NVARCHAR(5), @parent_ID))
  WHILE (PATINDEX('%[A-Za-z0-9@+.e]%', @contents collate SQL_Latin1_General_CP850_Bin))<>0
    BEGIN
      IF @Type='Object' --it will be a 0-n list containing a string followed by a string, number,boolean, or null
        BEGIN
          SELECT
            @SequenceNo=0,@end=CHARINDEX(':', ' '+@contents)--if there is anything, it will be a string-based name.
          SELECT  @start=PATINDEX('%[^A-Za-z@][@]%', ' '+@contents collate SQL_Latin1_General_CP850_Bin)--AAAAAAAA
          SELECT @token=SUBSTRING(' '+@contents, @start+1, @End-@Start-1),
            @endofname=PATINDEX('%[0-9]%', @token collate SQL_Latin1_General_CP850_Bin),
            @param=RIGHT(@token, LEN(@token)-@endofname+1)
          SELECT
            @token=LEFT(@token, @endofname-1),
            @Contents=RIGHT(' '+@contents, LEN(' '+@contents+'|')-@end-1)
          SELECT  @name=stringvalue FROM @strings
            WHERE string_id=@param --fetch the name
        END
      ELSE
        SELECT @Name=null,@SequenceNo=@SequenceNo+1
      SELECT
        @end=CHARINDEX(',', @contents)-- a string-token, object-token, list-token, number,boolean, or null
      IF @end=0
        SELECT  @end=PATINDEX('%[A-Za-z0-9@+.e][^A-Za-z0-9@+.e]%', @Contents+' ' collate SQL_Latin1_General_CP850_Bin)
          +1
       SELECT
        @start=PATINDEX('%[^A-Za-z0-9@+.e][A-Za-z0-9@+.e]%', ' '+@contents collate SQL_Latin1_General_CP850_Bin)
      --select @start,@end, LEN(@contents+'|'), @contents 
      SELECT
        @Value=RTRIM(SUBSTRING(@contents, @start, @End-@Start)),
        @Contents=RIGHT(@contents+' ', LEN(@contents+'|')-@end)
      IF SUBSTRING(@value, 1, 7)='@object'
        INSERT INTO @hierarchy
          (NAME, SequenceNo, parent_ID, StringValue, Object_ID, ValueType)
          SELECT @name, @SequenceNo, @parent_ID, SUBSTRING(@value, 8, 5),
            SUBSTRING(@value, 8, 5), 'object'
      ELSE
        IF SUBSTRING(@value, 1, 6)='@array'
          INSERT INTO @hierarchy
            (NAME, SequenceNo, parent_ID, StringValue, Object_ID, ValueType)
            SELECT @name, @SequenceNo, @parent_ID, SUBSTRING(@value, 7, 5),
              SUBSTRING(@value, 7, 5), 'array'
        ELSE
          IF SUBSTRING(@value, 1, 7)='@string'
            INSERT INTO @hierarchy
              (NAME, SequenceNo, parent_ID, StringValue, ValueType)
              SELECT @name, @SequenceNo, @parent_ID, stringvalue, 'string'
              FROM @strings
              WHERE string_id=SUBSTRING(@value, 8, 5)
          ELSE
            IF @value IN ('true', 'false')
              INSERT INTO @hierarchy
                (NAME, SequenceNo, parent_ID, StringValue, ValueType)
                SELECT @name, @SequenceNo, @parent_ID, @value, 'boolean'
            ELSE
              IF @value='null'
                INSERT INTO @hierarchy
                  (NAME, SequenceNo, parent_ID, StringValue, ValueType)
                  SELECT @name, @SequenceNo, @parent_ID, @value, 'null'
              ELSE
                IF PATINDEX('%[^0-9]%', @value collate SQL_Latin1_General_CP850_Bin)>0
                  INSERT INTO @hierarchy
                    (NAME, SequenceNo, parent_ID, StringValue, ValueType)
                    SELECT @name, @SequenceNo, @parent_ID, @value, 'real'
                ELSE
                  INSERT INTO @hierarchy
                    (NAME, SequenceNo, parent_ID, StringValue, ValueType)
                    SELECT @name, @SequenceNo, @parent_ID, @value, 'int'
      if @Contents=' ' Select @SequenceNo=0
    END
  END
INSERT INTO @hierarchy (NAME, SequenceNo, parent_ID, StringValue, Object_ID, ValueType)
  SELECT '-',1, NULL, '', @parent_id-1, @type
--
   RETURN
END
GO

--- Pase JSON

Declare @pars varchar(MAX) = 
' {"shapes":[{"type":"polygon","geofenceName":"","geofenceDescription":"",
"geofenceCategory":"1","color":"#1E90FF","paths":[{"path":[{
"lat":"26.096254906968525","lon":"65.709228515625"}
,{"lat":"28.38173504322308","lon":"66.741943359375"}
,{"lat":"26.765230565697482","lon":"68.983154296875"}
,{"lat":"26.254009699865737","lon":"68.609619140625"}
,{"lat":"25.997549919572112","lon":"68.104248046875"}
,{"lat":"26.843677401113002","lon":"67.115478515625"}
,{"lat":"25.363882272740255","lon":"65.819091796875"}]}]}]}'
Select * from parseJSON(@pars) AS MyResult 
Addi Khan
la source
3
-1 Pour copier et coller quelqu'un d'autre, répondez sans aucune explication ni lien. Cette solution provient de RedGate écrit en novembre 2010. red-gate.com/simple-talk/sql/t-sql-programming/… Pour ceux qui exécutent SQL Server 2016+, ne l'utilisez pas car SQL Server a introduit une solution native.
MGot90
-4

J'ai vu un article assez soigné à ce sujet ... donc si vous aimez ça:

CREATE PROC [dbo].[spUpdateMarks]
    @inputJSON VARCHAR(MAX)  -- '[{"ID":"1","C":"60","CPP":"60","CS":"60"}]'
AS
BEGIN
    -- Temp table to hold the parsed data
    DECLARE @TempTableVariable TABLE(
        element_id INT,
        sequenceNo INT,
        parent_ID INT,
        [Object_ID] INT,
        [NAME] NVARCHAR(2000),
        StringValue NVARCHAR(MAX),
        ValueType NVARCHAR(10)
    )
    -- Parse JSON string into a temp table
    INSERT INTO @TempTableVariable
    SELECT * FROM parseJSON(@inputJSON)
END

Essayez de regarder ici:

https://www.simple-talk.com/sql/t-sql-programming/consuming-json-strings-in-sql-server/

Il existe un projet ASP.Net complet à ce sujet ici: http://www.codeproject.com/Articles/788208/Update-Multiple-Rows-of-GridView-using-JSON-in-ASP

JanBorup
la source
10
Il y a déjà une réponse de l'auteur original de l'article: stackoverflow.com/a/4187412/389424
janv8000