Archiv pro štítek: JSON

Čtení JSON formátu v SQL serveru

Pro JSON není v SQL serveru spciální datový typ. Používá se NVARCHAR(MAX), JSON formát můžu vynutit constraintem pomocí funkce ISJSON.


Jako přiklad si udělám tempovou tabulku s constraintem

CREATE TABLE #json(
	JSONData NVARCHAR(MAX) NOT NULL
		CONSTRAINT check_JSONData_is_json CHECK (ISJSON(JSONData) = 1)
)
GO



Pokusím se vložit něco, co není JSON

INSERT INTO #json(JSONData) VALUES('nesmysl')
GO
--The INSERT statement conflicted with the CHECK constraint "check_JSONData_is_json". The conflict occurred in database "tempdb", table "dbo.#json_______________________________________________________________________________________________________________000000000067", column 'JSONData'.



Založím testovací data

INSERT INTO #json(JSONData) VALUES(
N'
[
	{
		"jmeno": "Kamil",
		"prijmeni": "Skočdopole",
		"konicky": [
			"šachy",
			"sport",
			"vážná hudba"
		],
		"adresy": [
			{
				"typ": "bydliště",
				"mesto": "Frenštát pod Radhoštěm",
				"cp": "10"
			},
			{
				"typ": "doručovací",
				"mesto": "Ostrava",
				"cp": "50"
			}
		],
		"hodne pije": true,
		"telesna_teplota": 36.9,
		"zbrojni_pas":null
	},
	{
		"jmeno": "Uršula",
		"prijmeni": "Králová",
		"konicky": [
			"pletení",
			"ultramaraton"
		],
		"adresy": [
			{
				"typ": "bydliště",
				"mesto": "Frenštát pod Radhoštěm",
				"cp": "10"
			}
		]
	}
]
'
)

SELECT * FROM #json



Otestování, zda máme validní JSON

SELECT ISJSON((SELECT * FROM #json))--vrací 1



JSON_QUERY vrací JSON objekt, nebo pole

SELECT JSON_QUERY((SELECT * FROM #json))
SELECT JSON_QUERY((SELECT * FROM #json), '$[0].adresy')
SELECT JSON_QUERY((SELECT * FROM #json), '$[1].adresy')
SELECT JSON_QUERY((SELECT * FROM #json), '$[1].konicky')



Striktní vs. laxní mód

By deafault je laxní

--NULL, protoze jmeno je hodnota a ne objekt
SELECT JSON_QUERY((SELECT * FROM #json), '$[0].jmeno')

--NULL, protoze jmeno je hodnota a ne objekt
SELECT JSON_QUERY((SELECT * FROM #json), 'lax $[0].jmeno')

--Object or array cannot be found in the specified JSON path.
SELECT JSON_QUERY((SELECT * FROM #json), 'strict $[0].jmeno')



JSON_VALUE vrací hodnotu NVARCHAR(4000)

SELECT JSON_VALUE((SELECT * FROM #json), '$[0].jmeno')--Kamil



Pokud je v názvu atributu mezera, tak ho musím mít v uvozovkách

--JSON path is not properly formatted. Unexpected character ' ' is found at position 10.
SELECT JSON_VALUE((SELECT * FROM #json), '$[0].hodne pije')

--true
SELECT JSON_VALUE((SELECT * FROM #json), '$[0]."hodne pije"')



Neexistující atribut je problém rozeznat od atributu s NULL hodnotou

--NULL
SELECT JSON_VALUE((SELECT * FROM #json), '$[0].neexistujici')

--Property cannot be found on the specified JSON path.
SELECT JSON_VALUE((SELECT * FROM #json), 'strict $[0].neexistujici')



Atribut s NULL hodnotou

--NULL
SELECT JSON_VALUE((SELECT * FROM #json), '$[0].zbrojni_pas')

--NULL
SELECT JSON_VALUE((SELECT * FROM #json), 'strict $[0].zbrojni_pas')

OPENJSON

Další možností čtení JSONu je s použitím funkce OPENJSON. Umožňuje přečíst více než 4000 znaků a vracet data v tabulkové podobě.

Syntaxe je:

OPENJSON( jsonExpression [ , path ] )  [ <with_clause> ]
SELECT *
FROM OPENJSON((SELECT * FROM #json))

Výsledkem je tabulka se sloupci key, value, type
openjson1

key
název atributu, nebo pořadové číslo v poli
value
hodnota
type
0 NULL Rozumná cesta jak rozlišit hodnotu NULL od neexistujícího atributu
1 string
2 číslo
3 boolean
4 pole
5 objekt

Dá se k tomu doplnit cesta

SELECT *
FROM OPENJSON((SELECT * FROM #json),'$[0]')

openjson2

SELECT *
FROM OPENJSON((SELECT * FROM #json),'$[0].konicky')

openjson3

Doplněním WITH kluzule uděláme z JSON tabulku, kde budou atributy ve sloupcích


Syntaxe:

<with_clause> ::= WITH ( { colName type [ column_path ] [ AS JSON ] } [ ,...n ] )
SELECT *
FROM OPENJSON((SELECT * FROM #json)) WITH(
	jmeno			NVARCHAR(50),
	prijmeni		NVARCHAR(50),
	[hodne pije]	BIT,
	nesmysl			NVARCHAR(50),
	zbrojni_pas		BIT
)

openjson4

Můžeme doplnit column_path a přejmenovat si na vlastní colName

SELECT *
FROM OPENJSON((SELECT * FROM #json)) WITH(
	jmeno			NVARCHAR(50)	'$.jmeno',
	prijmeni		NVARCHAR(50)	'$.prijmeni',
	[ochlasta]		BIT				'$."hodne pije"',
	nesmysl			NVARCHAR(50)	'$.nesmysl',
	zbrojni_pas		BIT				'$.zbrojni_pas',
	prvniKonicek	NVARCHAR(250)	'$.konicky[0]',
	mesto			NVARCHAR(250)	'$.adresy[0].mesto'
)

openjson5
Pokud chceme vrátit JSON, nebo pole, tak musíme použít AS JSON, jinak dostaneme NULL

SELECT *
FROM OPENJSON((SELECT * FROM #json)) WITH(
	jmeno			NVARCHAR(50)	'$.jmeno',
	prijmeni		NVARCHAR(50)	'$.prijmeni',
	[ochlasta]		BIT				'$."hodne pije"',
	nesmysl			NVARCHAR(50)	'$.nesmysl',
	zbrojni_pas		BIT				'$.zbrojni_pas',
	adresy1			NVARCHAR(MAX)	'$.adresy',				--pokud je tam objekt, nebo pole, 
															--tak nevrátí nic v lax modu. strict by spadnul
	adresy2			NVARCHAR(MAX)	'$.adresy' AS JSON		--je potřeba použít AS JSON
)

openjson6

Vrácený JSON z první funkce můžeme dál rozebrat pomocí APPLY OPENSJON

SELECT *
FROM OPENJSON((SELECT * FROM #json)) WITH(
	jmeno			NVARCHAR(50)	'$.jmeno',
	prijmeni		NVARCHAR(50)	'$.prijmeni',
	[ochlasta]		BIT				'$."hodne pije"',
	nesmysl			NVARCHAR(50)	'$.nesmysl',
	zbrojni_pas		BIT				'$.zbrojni_pas',
	adresy			NVARCHAR(MAX)	'$.adresy' AS JSON
)pole
OUTER APPLY OPENJSON(pole.adresy) WITH(
	typ			NVARCHAR(50)	,
	mesto		NVARCHAR(50)	,
	cp			NVARCHAR(50)
)

openjson7

Tvoření JSONu v SQL – FOR JSON

Pár příkladů jak vygenerovat JSON v SQL serveru.

Nejdřív tabulky a data, které se pak budeme snažit vysypat ve formátu JSON.

IF OBJECT_ID('dbo.Kategorie') IS NULL
CREATE TABLE dbo.Kategorie(
    IdKategorie INT NOT NULL IDENTITY CONSTRAINT pk_Kategorie PRIMARY KEY CLUSTERED,
    Kod NVARCHAR(50) NOT NULL CONSTRAINT uq_Kategorie_Kod UNIQUE,
    Nazev NVARCHAR(250) NOT NULL,
    PlatnostOd DATETIME NOT NULL,
    PlatnostDo DATETIME NOT NULL
)
GO
 
IF OBJECT_ID('dbo.KategorieTyp') IS NULL
CREATE TABLE dbo.KategorieTyp(
    IdKategorieTyp INT NOT NULL IDENTITY CONSTRAINT pk_KategorieTyp PRIMARY KEY CLUSTERED,
    IdKategorie INT NOT NULL CONSTRAINT fk_KategorieTyp_Kategorie FOREIGN KEY (IdKategorie) REFERENCES dbo.Kategorie(IdKategorie),
    Kod NVARCHAR(50) NOT NULL CONSTRAINT uq_KategorieTyp_Kod UNIQUE,
    Nazev NVARCHAR(250) NOT NULL,
    PlatnostOd DATETIME NOT NULL,
    PlatnostDo DATETIME NOT NULL
)
GO


INSERT INTO dbo.Kategorie
(
    Kod,
    Nazev,
    PlatnostOd,
    PlatnostDo
)
SELECT
     data.Kod
    ,data.Nazev
    ,GETDATE()
    ,'20991231'
FROM (VALUES
    (N'domacnost'   ,N'domácnost'   ),
    (N'podnikatel'  ,N'podnikatel'  )
)data(Kod           ,Nazev)
WHERE NOT EXISTS(
    SELECT * 
    FROM  dbo.Kategorie k
    WHERE k.Kod = data.Kod
)
GO
 
INSERT INTO dbo.KategorieTyp
(
    IdKategorie,
    Kod,
    Nazev,
    PlatnostOd,
    PlatnostDo
)
SELECT
     k.IdKategorie
    ,data.Kod
    ,data.Nazev
    ,GETDATE()
    ,'20991231'
FROM (VALUES
    (N'domacnost'   ,N'dum'         ,N'dům'         ),
    (N'domacnost'   ,N'byt'         ,N'byt'         ),
    (N'domacnost'   ,N'chata'       ,N'chata'       ),
    (N'podnikatel'  ,N'garaz'       ,N'garáž'       ),
    (N'podnikatel'  ,N'provozovna'  ,N'provozovna'  ),
    (N'podnikatel'  ,N'kancelar'    ,N'kacelář'     )
)data(KategorieKod  ,Kod            ,Nazev)
JOIN dbo.Kategorie k ON data.KategorieKod = k.Kod
WHERE NOT EXISTS(
    SELECT * 
    FROM  dbo.KategorieTyp kt
    WHERE kt.Kod = data.Kod
)
GO

A teď už příklady

--Ručně s použitím FOR JSON PATH
SELECT k.IdKategorie,
       k.Kod,
       k.Nazev,
	   (
		SELECT 
			   kt.IdKategorieTyp,
               kt.Kod,
               kt.Nazev,
               kt.PlatnostOd,
               kt.PlatnostDo
		FROM dbo.KategorieTyp kt
		WHERE kt.IdKategorie = k.IdKategorie
		FOR JSON PATH
	   )typ
FROM dbo.Kategorie k
FOR JSON PATH
--automaticky s použitím FOR JSON AUTO
SELECT k.IdKategorie,
       k.Kod,
       k.Nazev,
       k.PlatnostOd,
       k.PlatnostDo,
       typ.IdKategorieTyp,
       typ.Kod,
       typ.Nazev,
       typ.PlatnostOd,
       typ.PlatnostDo
FROM dbo.Kategorie k
JOIN dbo.KategorieTyp typ ON typ.IdKategorie = k.IdKategorie
FOR JSON AUTO

Výsledek je stejný v obou případech.

[
	{
		"IdKategorie": 1,
		"Kod": "domacnost",
		"Nazev": "domácnost",
		"PlatnostOd": "2022-01-29T14:47:43.493",
		"PlatnostDo": "2099-12-31T00:00:00",
		"typ": [
			{
				"IdKategorieTyp": 1,
				"Kod": "dum",
				"Nazev": "dům",
				"PlatnostOd": "2022-01-29T14:47:43.540",
				"PlatnostDo": "2099-12-31T00:00:00"
			},
			{
				"IdKategorieTyp": 2,
				"Kod": "byt",
				"Nazev": "byt",
				"PlatnostOd": "2022-01-29T14:47:43.540",
				"PlatnostDo": "2099-12-31T00:00:00"
			},
			{
				"IdKategorieTyp": 3,
				"Kod": "chata",
				"Nazev": "chata",
				"PlatnostOd": "2022-01-29T14:47:43.540",
				"PlatnostDo": "2099-12-31T00:00:00"
			}
		]
	},
	{
		"IdKategorie": 2,
		"Kod": "podnikatel",
		"Nazev": "podnikatel",
		"PlatnostOd": "2022-01-29T14:47:43.493",
		"PlatnostDo": "2099-12-31T00:00:00",
		"typ": [
			{
				"IdKategorieTyp": 4,
				"Kod": "garaz",
				"Nazev": "garáž",
				"PlatnostOd": "2022-01-29T14:47:43.540",
				"PlatnostDo": "2099-12-31T00:00:00"
			},
			{
				"IdKategorieTyp": 5,
				"Kod": "provozovna",
				"Nazev": "provozovna",
				"PlatnostOd": "2022-01-29T14:47:43.540",
				"PlatnostDo": "2099-12-31T00:00:00"
			},
			{
				"IdKategorieTyp": 6,
				"Kod": "kancelar",
				"Nazev": "kacelář",
				"PlatnostOd": "2022-01-29T14:47:43.540",
				"PlatnostDo": "2099-12-31T00:00:00"
			}
		]
	}
]

Pokud se chceme zbavit pole a vrátit jen jeden objekt, pak přidáme WITHOUT_ARRAY_WRAPPER.

SELECT TOP 1 *
FROM dbo.Kategorie k
FOR JSON PATH, WITHOUT_ARRAY_WRAPPER



Zanořený jeden objekt dělá problémy.

SELECT k.IdKategorie,
       k.Kod,
       k.Nazev,
	   (
		SELECT TOP 1
			   kt.IdKategorieTyp,
               kt.Kod,
               kt.Nazev,
               kt.PlatnostOd,
               kt.PlatnostDo
		FROM dbo.KategorieTyp kt
		WHERE kt.IdKategorie = k.IdKategorie
		FOR JSON PATH, WITHOUT_ARRAY_WRAPPER
	   )Typ
FROM dbo.Kategorie k
FOR JSON PATH

Výsledek subselectu se projeví jako escapovaný řetězec, což není zpravidla to, čeho chceme dosáhnout.

[
	{
		"IdKategorie": 1,
		"Kod": "domacnost",
		"Nazev": "domácnost",
		"Typ": "{\"IdKategorieTyp\":1,\"Kod\":\"dum\",\"Nazev\":\"dům\",\"PlatnostOd\":\"2022-01-29T14:47:43.540\",\"PlatnostDo\":\"2099-12-31T00:00:00\"}"
	},
	{
		"IdKategorie": 2,
		"Kod": "podnikatel",
		"Nazev": "podnikatel",
		"Typ": "{\"IdKategorieTyp\":4,\"Kod\":\"garaz\",\"Nazev\":\"garáž\",\"PlatnostOd\":\"2022-01-29T14:47:43.540\",\"PlatnostDo\":\"2099-12-31T00:00:00\"}"
	}
]

Pomůže finta s funkcí JSON_QUERY.

SELECT k.IdKategorie,
       k.Kod,
       k.Nazev,
	   (SELECT JSON_QUERY((
			SELECT TOP 1
				   kt.IdKategorieTyp,
				   kt.Kod,
				   kt.Nazev,
				   kt.PlatnostOd,
				   kt.PlatnostDo
			FROM dbo.KategorieTyp kt
			WHERE kt.IdKategorie = k.IdKategorie
			FOR JSON PATH, WITHOUT_ARRAY_WRAPPER
	   )))Typ
FROM dbo.Kategorie k
FOR JSON PATH

Výsledek už pak vypadá dle očekávání.

[
	{
		"IdKategorie": 1,
		"Kod": "domacnost",
		"Nazev": "domácnost",
		"Typ": {
			"IdKategorieTyp": 1,
			"Kod": "dum",
			"Nazev": "dům",
			"PlatnostOd": "2022-01-29T14:47:43.540",
			"PlatnostDo": "2099-12-31T00:00:00"
		}
	},
	{
		"IdKategorie": 2,
		"Kod": "podnikatel",
		"Nazev": "podnikatel",
		"Typ": {
			"IdKategorieTyp": 4,
			"Kod": "garaz",
			"Nazev": "garáž",
			"PlatnostOd": "2022-01-29T14:47:43.540",
			"PlatnostDo": "2099-12-31T00:00:00"
		}
	}
]

Escapování JSONu

SELECT STRING_ESCAPE('"Nějaká důmyslná data \ / 
druhý řádek"','json')
\"Nějaká důmyslná data \\ \/ \r\ndruhý řádek\"

Pole hodnot je potřeba slepit ručně

--SQL2017 a vyšší
SELECT '['+STRING_AGG('"' + STRING_ESCAPE(kt.Kod,'json') + '"', ',')+']'
FROM dbo.KategorieTyp kt


--SQL2016
SELECT '['+STUFF((
	SELECT 
			',"' + STRING_ESCAPE(kt.Kod,'json') + '"'
	FROM dbo.KategorieTyp kt
	FOR XML PATH('')
),1,1,'')+']'
[
	"byt",
	"dum",
	"garaz",
	"chata",
	"kancelar",
	"provozovna"
]

Pole jako atribut

--zapracuju jako atribut JSONu pomocí funkce JSON_QUERY
SELECT k.IdKategorie,
       k.Kod,
       k.Nazev,
       JSON_QUERY((
            SELECT '['+STUFF((
				SELECT 
						',"' + STRING_ESCAPE(kt.Kod,'json') + '"'
				FROM dbo.KategorieTyp kt
				WHERE kt.IdKategorie = k.IdKategorie
				FOR XML PATH('')
			),1,1,'')+']'
       ))Typy
FROM dbo.Kategorie k
FOR JSON PATH
[
	{
		"IdKategorie": 1,
		"Kod": "domacnost",
		"Nazev": "domácnost",
		"Typy": [
			"dum",
			"byt",
			"chata"
		]
	},
	{
		"IdKategorie": 2,
		"Kod": "podnikatel",
		"Nazev": "podnikatel",
		"Typy": [
			"garaz",
			"provozovna",
			"kancelar"
		]
	}
]

NULL hodnoty

SELECT *
FROM (
	VALUES
	('Kamil'	, 'Novák'),
	('Jarmila'	, NULL)
)a	(jmeno		, prijmeni)
FOR JSON PATH
[
	{
		"jmeno": "Kamil",
		"prijmeni": "Novák"
	},
	{
		"jmeno": "Jarmila"
	}
]
SELECT *
FROM (
	VALUES
	('Kamil'	, 'Novák'),
	('Jarmila'	, NULL)
)a	(jmeno		, prijmeni)
FOR JSON PATH, INCLUDE_NULL_VALUES
[
	{
		"jmeno": "Kamil",
		"prijmeni": "Novák"
	},
	{
		"jmeno": "Jarmila",
		"prijmeni": null
	}
]