Come gestire il formato JSON su SQL Server

JSON su SQL Server

Il JSON è un particolare formato di rappresentazione dei dati utilizzato sempre più frequentemente all’interno di svariate tipologie di progetti informatici. Alcuni database non relazionali come MongoDB utilizzano in modo nativo questa rappresentazione dei dati, guarda ad esempio questo articolo sul tema https://www.yimp.it/find-mongodb/. Qui invece vogliamo concentrarci su come interagire con il JSON all’interno del database relazionale SQL Server, sia per effettuare il parsing di dati in input e sia in fase di esportazione.

Parsing di JSON su SQL Server con OpenJson

Iniziamo gli esempi di questa sezione dichiarando su SQL Server una variabile di tipo VARCHAR(MAX) e valorizzandola con una stringa in formato JSON:

DECLARE @json_string VARCHAR(MAX) = '
{"id_fattura":1,
"nome_prodotto":"Prodotto1",
"tipologia":"A",
"importo":120.12,
"data_fattura":"2018-05-01"}';

La prima funzione che studiamo è OpenJson che ci permetterà di trasformare il documento in una tabella:

SELECT *
FROM   OpenJson(@json_string) AS f;

Vediamo il risultato:

output openjson su sql server

Otteniamo una tabella con tre colonne:

  • key contenente le chiavi del documento JSON;
  • value contenente i rispettivi valori;
  • type che è un numero da 0 a 5 e identifica il tipo associato al valore: 0 per i null, 1 per le stringhe, 2 per i numeri, 3 per i booleani, 4 per gli array e 5 per i sotto-documenti.

Tuttavia, per ottenere un output effettivamente lavorabile, possiamo specificare la struttura dell’output tramite l’istruzione WITH, facendo attenzione anche a rispettare i caratteri maiuscoli o minuscoli con cui sono definite le chiavi nel JSON (altrimenti otterrei dei NULL):

SELECT *
FROM   OpenJson(@json_string)
WITH (
  id_fattura INT '$.id_fattura',
  nome_prodotto VARCHAR(50) '$.nome_prodotto',
  tipologia VARCHAR(50) '$.tipologia',
  importo DECIMAL(18,4) '$.importo',
  data_fattura DATE '$.data_fattura'
) AS f;
utilizzo clausola with con openjson

Facciamo molta attenzione quando utilizziamo il tipo DATETIME in quanto per alcune lingue associate alla sessione (come l’italiano) le date all’interno del JSON saranno interpretate in modo errato se contengo il carattere separatore “-“, come mostrato nella figura in basso. In questo articolo ho approfondito altri temi sul tipo DATETIME https://www.yimp.it/sql-server-filtro-campi-datetime/

json e datetime

Gestire JSON più complessi con Array e sotto-documenti

Vediamo ora come gestire un documento JSON più complesso contenente anche una chiave di tipo array (tipologie) e una di tipo sotto-documento (cliente).

DECLARE @json_string VARCHAR(max) = '
{"id_fattura":1,
"cliente":{"id_cliente": 2,
           "nome":"nicola",
            "cognome":"Iantomasi"},
"tipologie":["A","V"]
}';

In questo caso, se utilizzassi la clausola WITH come prima, otterrei dei NULL in corrispondenza delle chiavi più complesse. Per esse servirà scegliere il tipo NVARCHAR(MAX) e specificare l’espressione AS JSON, come nel codice in basso

SELECT *
FROM   OpenJson(@json_string)
WITH (
 id_fattura INT '$.id_fattura',
 cliente NVARCHAR(MAX) '$.cliente' AS JSON,
 tipologie NVARCHAR(MAX) '$.tipologie' AS JSON
) AS f;
utilizzo as json per gestire array e sotto-documenti

Tuttavia possiamo migliorare il formato dell’output in questo modo:

  • per i sotto-documenti si può specificare meglio la struttura dopo il carattere $;
  • per gli array possiamo dividere i dati su più righe utilizzando una CROSS APPLY.
SELECT 
 f.id_fattura,
 f.id_cliente,
 f.nome,
 f.cognome,
 t.tipologia
FROM   OpenJson(@json_string)
WITH (
 id_fattura INT '$.id_fattura',
 id_cliente INT '$.cliente.id_cliente',
 nome VARCHAR(50) '$.cliente.nome',
 cognome VARCHAR(50) '$.cliente.cognome',
 tipologia NVARCHAR(MAX) '$.tipologie' AS JSON
) AS f 
CROSS APPLY 
OpenJson(f.tipologia)
WITH (
 tipologia VARCHAR(20) '$') AS  t;
output cross apply e json

Import su SQL Server di file JSON

Vediamo ora come importare su SQL Server i dati contenuti in un file JSON contenente più documenti, come in questo esempio:

[{"NomeCorso":"Analisi dei dati",
  "NumeroPartecipanti": 6},
 {"NomeCorso":"Python",
  "NumeroPartecipanti": 8},
 {"NomeCorso":"SQL",
  "NumeroPartecipanti": 7}]

Come primo step possiamo utilizzare l’operatore OPENROWSET

SELECT *
FROM   OPENROWSET (BULK 'C:\User\file.json', SINGLE_CLOB) AS JsonFile;

In questo modo otterremo un output con una sola riga nella colonna BulkColumn, con l’intero contenuto del file.

output openrowset per importare file json

Per portare i dati in un vero formato tabellare scriveremo una CROSS APPLY con la colonna BulkColumn e l’operatore OpenJson descritto nei paragrafi precedenti.

SELECT f.*
FROM   OPENROWSET (BULK 'C:\User\file.json', SINGLE_CLOB) AS JsonFile
CROSS APPLY OpenJson(JsonFile.BulkColumn)
WITH (
 NomeCorso VARCHAR(50) '$.NomeCorso',
 NumeroPartecipanti INT '$.NumeroPartecipanti'
) AS f;
output di query sql con openrowset e cross apply con openjson

Trasformare una tabella in un JSON

Studiamo ora il processo inverso, cioè passare da una tabella di SQL Server a un documento in formato JSON. Nel caso più semplice in cui interroghiamo una singola tabella, ci basterà utilizzare l’istruzione JSON PATH in fondo alla query. Tramite l’operatore AS possiamo assegnare i nomi delle chiavi alle colonne nella SELECT, con la possibilità di costruire anche una struttura di sotto-documenti scegliendo un’espressione con il carattere punto “.”

--query sql
SELECT TOP 2 
  IdFattura, 
  Importo AS [DatiContabili.importo],
  Iva AS [DatiContabili.iva] 
FROM dbo.Fatture 
FOR JSON PATH;
/* JSON risultante
[{"IdFattura":1,"DatiContabili":{"importo":120.00,"iva":20}},
 {"IdFattura":2,"DatiContabili":{"importo":32.00,"iva":20}}]   
*/

In presenza di NULL, le relative chiavi non verranno create, a meno di specificare la proprietà INCLUDE_NULL_VALUES descritta qui https://learn.microsoft.com/it-it/sql/relational-databases/json/include-null-values-in-json-include-null-values-option?view=sql-server-ver16

Se invece vogliamo combinare i dati presenti su più due tabelle, potremmo utilizzare una subquery all’interno della SELECT per ottenere un documento JSON correttamente strutturato e che segua le relazioni molti a uno nel database.

SELECT
  c.IdCliente,
  (SELECT IdFattura ,
          Importo
   FROM Fatture AS f
   WHERE f.IdCliente = c.IdCliente
   FOR JSON PATH) AS Fatture
FROM Clienti AS c
FOR JSON PATH;
output for json path con query annidata

Continua a imparare

Torna in alto
Torna su