Lorsque vous utilisez SQL
ou MySQL
(ou toute autre base de données relationnelle), je comprends que l'enregistrement des données dans des colonnes normales est préférable pour l'indexation et d'autres objectifs ...
Le problème est de charger et d’enregistrer JSON
les données sont parfois beaucoup plus simples. et facilite le développement.
Existe-t-il des "règles d'or" pour la sauvegarde des données brutes JSON
dans la base de données?
est-ce une pratique absolument fausse de le faire?
De très jolies réponses ont été données, mais la plus organisée est sans aucun doute la réponse donnée par @Shnugo qui mérite la prime.
J'aimerais également souligner les réponses données par @Gordon Linoff et @Amresh Pandey pour expliquer d'autres cas d'utilisation spéciaux.
Dieu merci, et bon travail à tous!
Les principales questions sont
JSON (comme XML) est idéal pour l'échange de données, le stockage réduit et les structures définies de manière générique, mais il ne peut pas participer aux actions typiques que vous exécutez dans votre SGBDR. Dans la plupart des cas, il est préférable de transférer vos données JSON dans des tables normales et de recréer le JSON lorsque vous en avez besoin.
La première règle de normalisation impose de ne jamais stocker plus d'un bit d'information dans une colonne. Vous voyez une colonne "PersonName" avec une valeur comme "Mickey Mouse"? Vous pointez ceci et criez: Modifiez cela immédiatement!
Qu'en est-il de XML ou de JSON? Est-ce que ces types cassent 1.NF? Eh bien oui et non ...
Il est parfaitement correct de stocker une structure complète sous la forme d’un bit d’information s’il s’agit d’un bit d’information en fait. Vous obtenez une réponse SOAP et souhaitez la stocker, car vous en aurez peut-être besoin comme référence ultérieure (mais vous n'utiliserez pas ces données pour vos propres processus )? Il suffit de le stocker tel quel !
Imaginons maintenant une structure complexe (XML ou JSON) représentant une personne (avec son adresse, plus de détails ...). Maintenant, vous mettez ceci dans une colonne sous la forme PersonInCharge
. Est-ce faux? Cela ne devrait-il pas plutôt vivre dans des tables liées correctement conçues avec une référence de clé étrangère au lieu de XML/JSON? Surtout si la même personne peut apparaître dans plusieurs rangées différentes, il est définitivement faux d'utiliser une approche XML/JSON.
Mais maintenant, imaginez la nécessité de stocker des données historiques. Vous souhaitez persister les données de la personne pour un moment donné. Quelques jours plus tard, la personne vous dit une nouvelle adresse? Aucun problème! L'ancienne adresse réside dans un XML/JSON si vous en avez besoin ...
Conclusion: Si vous stockez les données simplement pour les conserver, c'est bon. Si cette donnée est une partie unique , ça va ...
Mais si vous avez besoin des pièces internes régulièrement ou si cela signifie un stockage redondant en double, cela ne va pas ...
Ce qui suit est pour SQL Server et peut être différent sur d'autres RDBM.
XML n'est pas stocké en tant que texte que vous voyez, mais sous forme d'arborescence. Interroger cela est étonnamment performant! Cette structure n'est pas analysée au niveau des chaînes!
JSON dans SQL Server (2016+) vit dans une chaîne et doit être analysé. Il n'y a pas de type JSON natif réel (comme il existe un type XML natif). Cela pourrait arriver plus tard, mais pour le moment je suppose, JSON ne sera pas aussi performant que XML sur SQL Server (voir la section UPDATE 2 ). Tout besoin de lire une valeur hors JSON nécessitera un sacré tas d'appels de méthodes de chaîne cachée ...
votre adorable artiste de base de données :-D sait que stocker JSON tel quel , va à l’encontre des principes communs des RDBM. Il sait,
Il existe certaines solutions (en fonction du SGBDR que vous utilisez), mais la plupart d'entre elles ne fonctionnent pas comme vous le souhaitez ...
OUI
NON
Vous pouvez commencer par le code JSON dans une colonne de chaîne ou en tant que BLOB et le remplacer par des tables physiques lorsque vous en avez besoin. Ma boule de cristal magique me dit que cela pourrait être demain :-D
Trouvez quelques idées sur les performances et l'espace disque ici: https://stackoverflow.com/a/47408528/5089204
Les adresses suivantes prennent en charge JSON et XML dans SQL-Server 2016
Utilisateur @ mike123 a indiqué un article sur un blog officiel de Microsoft qui semble prouver dans une expérience que interroger un JSON est 10 x plus rapide puis interroge un XML dans SQL-Server.
Quelques réflexions à ce sujet:
Quelques recoupements avec "l'expérience":
XQuery
! Trouver un produit avec un identifiant donné dans un tableau? JSON doit lire la totalité du texte et utiliser un filtre à l'aide de WHERE
, alors que XML
autoriserait un XQuery predicate
interne. Sans parler de FLWOR
.../text()
au XPath
réduit ce nombre à moins de 2x . Dans l'article lié, l'utilisateur "Mister Magoo" l'a déjà souligné, mais le titre de click-bait reste inchangé ...SUBSTRING
et CHARINDEX
:-D.Le code suivant montrera une expérience plus réaliste
Product
(un tableau JSON par rapport à des nœuds frères)GO 10
parcourra ce bloc dix fois pour éviter le biais du premier appel Le résultat final montre clairement que JSON est plus lent que XML (pas beaucoup, environ 1,5x sur un exemple encore très simple).
La déclaration finale:
Le code de test
USE master;
GO
--create a clean database
CREATE DATABASE TestJsonXml;
GO
USE TestJsonXml;
GO
--create tables
CREATE TABLE TestTbl1(ID INT IDENTITY,SomeXml XML);
CREATE TABLE TestTbl2(ID INT IDENTITY,SomeJson NVARCHAR(MAX));
CREATE TABLE Target1(SomeString NVARCHAR(MAX));
CREATE TABLE Target2(SomeString NVARCHAR(MAX));
CREATE TABLE Times(Test VARCHAR(10),Diff INT)
GO
--insert 10000 XMLs into TestTbl1
WITH Tally AS(SELECT TOP 10000 ROW_NUMBER() OVER(ORDER BY (SELECT NULL))*2 AS Nmbr FROM master..spt_values AS v1 CROSS APPLY master..spt_values AS v2)
INSERT INTO TestTbl1(SomeXml)
SELECT
N'<Root>
<Products>
<ProductDescription>
<Features>
<Maintenance>' + CAST(Nmbr AS NVARCHAR(10)) + ' year parts and labor extended maintenance is available</Maintenance>
<Warranty>1 year parts and labor</Warranty>
</Features>
<ProductID>' + CAST(Nmbr AS NVARCHAR(10)) + '</ProductID>
<ProductName>Road Bike</ProductName>
</ProductDescription>
<ProductDescription>
<Features>
<Maintenance>' + CAST(Nmbr + 1 AS NVARCHAR(10)) + ' blah</Maintenance>
<Warranty>1 year parts and labor</Warranty>
</Features>
<ProductID>' + CAST(Nmbr + 1 AS NVARCHAR(10)) + '</ProductID>
<ProductName>Cross Bike</ProductName>
</ProductDescription>
</Products>
</Root>'
FROM Tally;
--insert 10000 JSONs into TestTbl2
WITH Tally AS(SELECT TOP 10000 ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) AS Nmbr FROM master..spt_values AS v1 CROSS APPLY master..spt_values AS v2)
INSERT INTO TestTbl2(SomeJson)
SELECT
N'{
"Root": {
"Products": {
"ProductDescription": [
{
"Features": {
"Maintenance": "' + CAST(Nmbr AS NVARCHAR(10)) + ' year parts and labor extended maintenance is available",
"Warranty": "1 year parts and labor"
},
"ProductID": "' + CAST(Nmbr AS NVARCHAR(10)) + '",
"ProductName": "Road Bike"
},
{
"Features": {
"Maintenance": "' + CAST(Nmbr + 1 AS NVARCHAR(10)) + ' blah",
"Warranty": "1 year parts and labor"
},
"ProductID": "' + CAST(Nmbr + 1 AS NVARCHAR(10)) + '",
"ProductName": "Cross Bike"
}
]
}
}
}'
FROM Tally;
GO
--Do some initial action to avoid first-call-bias
INSERT INTO Target1(SomeString)
SELECT SomeXml.value('(/Root/Products/ProductDescription/Features/Maintenance/text())[1]', 'nvarchar(4000)')
FROM TestTbl1;
INSERT INTO Target2(SomeString)
SELECT JSON_VALUE(SomeJson, N'$.Root.Products.ProductDescription[0].Features.Maintenance')
FROM TestTbl2;
GO
--Start the test
DECLARE @StartDt DATETIME2(7), @EndXml DATETIME2(7), @EndJson DATETIME2(7);
--Read all ProductNames of the second product and insert them to Target1
SET @StartDt = SYSDATETIME();
INSERT INTO Target1(SomeString)
SELECT SomeXml.value('(/Root/Products/ProductDescription/ProductName/text())[2]', 'nvarchar(4000)')
FROM TestTbl1
ORDER BY NEWID();
--remember the time spent
INSERT INTO Times(Test,Diff)
SELECT 'xml',DATEDIFF(millisecond,@StartDt,SYSDATETIME());
--Same with JSON into Target2
SET @StartDt = SYSDATETIME();
INSERT INTO Target2(SomeString)
SELECT JSON_VALUE(SomeJson, N'$.Root.Products.ProductDescription[1].ProductName')
FROM TestTbl2
ORDER BY NEWID();
--remember the time spent
INSERT INTO Times(Test,Diff)
SELECT 'json',DATEDIFF(millisecond,@StartDt,SYSDATETIME());
GO 10 --do the block above 10 times
--Show the result
SELECT Test,SUM(Diff) AS SumTime, COUNT(Diff) AS CountTime
FROM Times
GROUP BY Test;
GO
--clean up
USE master;
GO
DROP DATABASE TestJsonXml;
GO
Le résultat (SQL Server 2016 Express sur un processeur Acer Aspire v17 Intel i7, 8 Go)
Test SumTime
------------------
json 2706
xml 1604
C'est trop long pour un commentaire.
S'il était "absolument faux", la plupart des bases de données ne le prendraient pas en charge. D'accord, la plupart des bases de données prennent en charge les virgules dans la clause FROM
et je considère cela comme "absolument faux". Mais la prise en charge de JSON est un nouveau développement et non une "fonctionnalité" compatible avec les versions antérieures.
Un cas évident est lorsque la structure JSON est simplement un BLOB qui est renvoyé à l'application. Ensuite, il n’ya pas de débat - autre que la surcharge liée au stockage de JSON, qui est inutilement détaillé pour les données structurées avec des champs communs dans chaque enregistrement.
Un autre cas est celui des colonnes "clairsemées". Vous avez des lignes avec beaucoup de colonnes possibles, mais celles-ci varient d'une ligne à l'autre.
Vous pouvez également stocker des enregistrements "imbriqués" dans un enregistrement. JSON est puissant.
Si le JSON comporte des champs communs aux enregistrements sur lesquels vous souhaitez interroger, il est généralement préférable de les placer dans les colonnes de base de données appropriées. Cependant, les données sont compliquées et il existe une place pour des formats tels que JSON.
Je vais agiter ma baguette magique. Pouf! Règles d'or sur l'utilisation de JSON:
Si MySQL n'a pas besoin de regarder à l'intérieur du JSON et que l'application a simplement besoin d'une collection de choses, alors JSON va bien, peut-être même mieux.
Si vous recherchez des données à l'intérieur et , vous avez MariaDB 10.0.1 ou MySQL 5.7 (avec un type de données et des fonctions JSON), puis JSON pourrait être pratique. Les colonnes "dynamiques" de MariaDB 5.3 en sont une variante.
Si vous faites des choses "Entity-Attribute-Value", alors JSON n'est pas bon, mais c'est le moindre de plusieurs maux. http://mysql.rjweb.org/doc.php/eav
Pour rechercher dans une colonne indexée, ne pas avoir la valeur enfouie dans JSON est un gros plus.
Pour la recherche par plage dans une colonne indexée, ou FULLTEXT
recherche ou SPATIAL
, JSON n'est pas possible.
Pour WHERE a=1 AND b=2
l'index "composite" INDEX(a,b)
est génial; ne peut probablement pas être proche avec JSON.
JSON fonctionne bien avec les données "clairsemées"; INDEXing fonctionne, mais pas aussi bien, avec tel. (Je fais référence à des valeurs qui sont "manquantes" ou NULL pour beaucoup de lignes.)
JSON peut vous donner des "tableaux" et des "arbres" sans recourir à des tables supplémentaires. Mais creuser dans ces tableaux/arbres seulement dans l'application, pas dans SQL.
JSON est un monde meilleur que XML. (Mon avis)
Si vous ne voulez pas entrer dans la chaîne JSON sauf à partir de l'application, je vous recommande de le compresser (dans le client) pour le stocker dans un BLOB
. Pensez-y comme un fichier .jpg - il y a des choses là-dedans, mais SQL ne s'en soucie pas.
Énoncez votre demande. peut-être pouvons-nous être plus précis.
Nouveau SQL Server fournit des fonctions pour le traitement de texte JSON. Les informations au format JSON peuvent être stockées sous forme de texte dans des colonnes SQL Server standard. SQL Server fournit des fonctions permettant d'extraire des valeurs de ces objets JSON.
DROP TABLE IF EXISTS Person
CREATE TABLE Person
( _id int identity constraint PK_JSON_ID primary key,
value nvarchar(max)
CONSTRAINT [Content should be formatted as JSON]
CHECK ( ISJSON(value)>0 )
)
Cette structure simple est similaire à la collection NoSQL standard que vous pouvez créer dans des bases de données NoSQL (Azure DocumentDB ou MongoDB, par exemple) dans laquelle vous ne disposez que d'une clé représentant l'ID et d'une valeur représentant le JSON.
Notez que NVARCHAR n'est pas simplement un texte brut. SQL Server possède un mécanisme de compression de texte intégré capable de compresser de manière transparente les données stockées sur le disque. La compression dépend de la langue et peut aller jusqu'à 50% en fonction de vos données (voir Compression UNICODE).
La principale différence entre SQL Server et les autres bases de données NoSQL simples réside dans le fait que SQL Server vous permet d'utiliser un modèle de données hybride dans lequel vous pouvez stocker plusieurs objets JSON dans la même "collection" et les combiner avec des colonnes relationnelles classiques.
Par exemple, imaginons que nous savons que chaque personne de votre collection aura FirstName et LastName, et que vous pouvez stocker des informations générales sur la personne sous la forme d'un objet JSON, ainsi que des numéros de téléphone/adresses électroniques sous la forme d'objets distincts. Dans SQL Server 2016, nous pouvons facilement créer cette structure sans aucune syntaxe supplémentaire:
DROP TABLE IF EXISTS Person
CREATE TABLE Person (
PersonID int IDENTITY PRIMARY KEY,
FirstName nvarchar(100) NOT NULL,
LastName nvarchar(100) NOT NULL,
AdditionalInfo nvarchar(max) NULL,
PhoneNumbers nvarchar(max) NULL,
EmailAddresses nvarchar(max) NULL
CONSTRAINT [Email addresses must be formatted as JSON array]
CHECK ( ISJSON(EmailAddresses)>0 )
)
Au lieu d'un seul objet JSON, vous pouvez organiser vos données dans cette "collection". Si vous ne souhaitez pas vérifier explicitement la structure de chaque colonne JSON, il n'est pas nécessaire d'ajouter une contrainte de vérification JSON à chaque colonne (dans cet exemple, j'ai ajouté la contrainte CHECK uniquement à la colonne EmailAddresses).
Si vous comparez cette structure à la collection NoSQL standard, vous remarquerez peut-être que vous aurez un accès plus rapide aux données fortement typées (Prénom et Nom). Par conséquent, cette solution constitue un choix judicieux pour les modèles hybrides dans lesquels vous pouvez identifier certaines informations répétées sur tous les objets, tandis que d'autres informations variables peuvent être stockées au format JSON. De cette façon, vous pouvez combiner flexibilité et performance.
Si vous comparez cette structure avec le schéma de la base de données AdventureWorks de la table Person, vous remarquerez peut-être que nous avons supprimé de nombreuses tables associées.
Outre la simplicité du schéma, vos opérations d’accès aux données seront simplifiées par rapport à une structure relationnelle complexe. Vous pouvez maintenant lire une seule table au lieu de joindre plusieurs tables. Lorsque vous devez insérer une nouvelle personne avec des informations associées (adresses e-mail, numéros de téléphone), vous pouvez insérer un seul enregistrement dans une table au lieu de l'insérer dans la table AdventureWorks, en prenant la colonne identité pour trouver la clé étrangère utilisée pour stocker les téléphones. , adresses e-mail, etc. En outre, dans ce modèle, vous pouvez facilement supprimer une ligne à une seule personne sans les supprimer en cascade à l'aide de relations de clé étrangère.
Les bases de données NoSQL sont optimisées pour des opérations simples, de lecture, d'insertion et de suppression - SQL Server 2016 vous permet d'appliquer la même logique dans une base de données relationnelle.
Contraintes JSON Dans les exemples précédents, nous avons vu comment ajouter une contrainte simple qui valide que le texte stocké dans la colonne est correctement formaté. Bien que JSON n'ait pas de schéma solide, vous pouvez également ajouter des contraintes complexes en combinant des fonctions lisant les valeurs à partir de JSON et des fonctions T-SQL standard:
ALTER TABLE Person
ADD CONSTRAINT [Age should be number]
CHECK ( ISNUMERIC(JSON_VALUE(value, '$.age'))>0 )
ALTER TABLE Person
ADD CONSTRAINT [Person should have skills]
CHECK ( JSON_QUERY(value, '$.skills') IS NOT NULL)
First constraint will take the value of $.age property and check is this numeric value. Second constraint will try to find JSON object in $.skills property and verify that it exists. The following INSERT statements will fail due to the violation of constraints:
INSERT INTO Person(value)
VALUES ('{"age": "not a number", "skills":[]}')
INSERT INTO Person(value)
VALUES ('{"age": 35}')
Notez que les contraintes de vérification peuvent ralentir vos processus d’insertion/mise à jour et vous éviter de les éviter si vous avez besoin de performances d’écriture plus rapides.
Stockage JSON compressé Si vous avez un texte JSON volumineux, vous pouvez compresser explicitement le texte JSON à l'aide de la fonction COMPRESS intégrée. Dans l'exemple suivant, le contenu JSON compressé est stocké sous forme de données binaires et nous avons calculé une colonne qui décompresse JSON en tant que texte d'origine à l'aide de la fonction DECOMPRESS:
CREATE TABLE Person
( _id int identity constraint PK_JSON_ID primary key,
data varbinary(max),
value AS CAST(DECOMPRESS(data) AS nvarchar(max))
)
INSERT INTO Person(data)
VALUES (COMPRESS(@json))
Les fonctions COMPRESS et DECOMPRESS utilisent la compression GZip standard. Si votre client peut gérer la compression GZip (par exemple, un navigateur qui comprend le contenu gzip), vous pouvez directement renvoyer un contenu compressé. Notez qu'il s'agit d'un compromis performances/stockage. Si vous interrogez fréquemment des données compressées, vos performances sont ralenties car le texte doit être décompressé à chaque fois.
Remarque: les fonctions JSON sont disponibles uniquement dans SQL Server 2016+ et dans la base de données Azure SQL.
Plus peut être lu de la source de cet article
https://blogs.msdn.Microsoft.com/sqlserverstorageengine/2015/11/23/storing-json-in-sql-server/
La "règle d'or" que j'utilise, de manière très vague, est que si j'ai besoin de JSON dans son format brut, le stockage est acceptable. Si je dois accorder une attention particulière à l'analyse, ce n'est pas le cas.
Par exemple, si je crée une API qui envoie du JSON brut, et pour une raison quelconque, cette valeur ne changera pas, il est alors okay de le stocker en tant que JSON brut. Si je dois l'analyser, le modifier, le mettre à jour, etc., alors pas tant que ça.
La question que vous devez poser est la suivante:
Suis-je lié à l'utilisation de cette base de données uniquement?
FAIRE
Ne pas
Les Json ne sont pas géniaux dans les db relationnels. Si vous déployez le JSON en colonnes et le stockez dans une base de données, c'est génial, mais le stockage d'un JSON sous la forme d'un blob est le prochain moyen de l'utiliser comme système d'archivage de données.
Il peut y avoir plusieurs raisons de ne pas déplier un JSON et de le stocker dans une seule colonne, mais la décision aurait été prise car les valeurs de ce champ Json ne seraient pas utilisées pour une interrogation (ou les valeurs avaient déjà été dépliées en colonnes).
En outre, la plupart des traitements JSON, si le champ demandé était interrogé, seraient en dehors de l'environnement SQL, car SQL n'est tout simplement pas destiné au traitement JSON. La vraie question devient alors: où dois-je stocker ce JSON, puis-je le laisser être sous forme de fichiers plats et, le cas échéant, les interroger via un autre système (spark/Hive/etc.).
Je suis d'accord avec votre artiste DB, n'utilisez pas de SGBDR pour l'archivage. Il y a des options moins chères. De plus, les blobs JSON peuvent devenir énormes et commencer à encombrer l'espace disque de la base de données avec le temps.
PostgreSQL a un type de données json
et jsonb
intégré
Voici quelques exemples:
CREATE TABLE orders (
ID serial NOT NULL PRIMARY KEY,
info json NOT NULL
);
INSERT INTO orders (info)
VALUES
(
'{ "customer": "Lily Bush", "items": {"product": "Diaper","qty": 24}}'
),
(
'{ "customer": "Josh William", "items": {"product": "Toy Car","qty": 1}}'
),
(
'{ "customer": "Mary Clark", "items": {"product": "Toy Train","qty": 2}}'
);
PostgreSQL fournit deux opérateurs natifs ->
et ->>
pour interroger les données JSON.
L'opérateur ->
renvoie le champ d'objet JSON par clé.
L'opérateur ->>
renvoie le champ d'objet JSON par le texte.
SELECT
info -> 'customer' AS customer
FROM
orders;
SELECT
info ->> 'customer' AS customer
FROM
orders
WHERE
info -> 'items' ->> 'product' = 'Diaper'