SQLやMySQL(リレーショナルDBでも可)を使う場合、インデックス作成などのために通常のカラムでデータを保存した方が良いのは理解できるのですが・・・。
JSON`データの読み込みと保存は、時にもっとシンプルで、開発を容易にします。
生のJSONデータをDBに保存する際の黄金律はありますか?
というのは、絶対に間違った習慣なのでしょうか?
とても素晴らしい回答がありましたが、最もよくまとまっていたのは間違いなく@Shnugoさんの回答で、賞金に値すると思います。
また、他の特殊な使用例について説明した@Gordon Linoffと@Amresh Pandeyの回答も指摘しておきたい。
よかった、みんなお疲れ様でした!
主な質問は以下の通りです。
正規化の最初のルールとして、1つのカラムに2つ以上の情報を格納しないことが定められています。PersonName"というカラムに、quot;Mickey Mouse"という値があるとします。あなたはこれを指差して叫ぶ:すぐに変えてください!」。
XMLやJSONについてはどうでしょうか?これらのタイプは1.NFを壊しているのでしょうか?そうですね、イエスでもありノーでもありますね;
もしそれが実際に1ビットの情報であるならば、完全な構造を1ビットの情報として保存することは完全に問題ない。SOAPの応答を受け取って、将来参照するためにこれが必要になるかもしれないから保存したい(しかし、あなたはこのデータをあなた自身のプロセスに使用しません)?そのまま保存しておけばいいのです!
では、人を表す複雑な構造(XMLまたはJSON)(住所、詳細...を含む)を想像してみてください。これを「PersonInCharge`」として1つのカラムに入れるのです。これは間違っているのでしょうか?XML/JSONの代わりに、外部キーで参照できるように適切に設計された関連テーブルに置くべきじゃないでしょうか?特に、同じ人物が多くの異なる行に現れる可能性がある場合、XML/JSONのアプローチを使うのは間違いなく間違っています。
しかし、ここで、履歴データを保存する必要性を想像してみてください。あなたは、ある時点のその人のデータを保存したい。何日か後に、その人が新しい住所を教えてくれましたか?問題ありません!古い住所はXML/JSONに保存されているので、もし必要になったとしても...。
結論:データを保存しておくだけなら、大丈夫です。このデータがユニークな部分であれば、大丈夫です...
しかし、定期的に内部部品*を必要とする場合や、冗長な二重保存を意味する場合は、大丈夫ではありません...。
以下はSQL Serverの場合であり、他のRDBMでは異なる場合があります。
XMLは目に見えるテキストとしてではなく、階層ツリーとして保存されます。これをクエリすると、驚くほど性能が良い!この構造は文字列レベルではパースされない!
SQL Server(2016+)のJSONは文字列で生きており、パースする必要があります。ネイティブのXML型があるように)本当のネイティブのJSON型は存在しません。しかし、今のところ、JSON は SQL Server 上で XML ほどのパフォーマンスを発揮しないと考えています(UPDATE 2 のセクションを参照)。JSONから値を読み出す必要がある場合、大量の隠し文字列メソッド呼び出しが必要になります...。
あなたの 愛すべきDBアーティスト :-D は、*JSON をそのまま保存することは、RDBMの一般原則に反することを知っています。 彼は知っている、
JSONが1.NFに違反している可能性が高いこと。
JSONが時間と共に変化する可能性があること(同じ列、異なるコンテンツ)。
JSONは読みやすくなく、フィルタリング、検索、結合、ソートが非常に困難であること。
そのような操作は、貧しい小さなDBサーバーにかなりの余分な負荷をかけることになる。 (使用しているRDBMSによって)いくつかの回避策はありますが、そのほとんどはあなたが望むように'機能しません...。
YESです。
JSONに格納されているデータを、高価な操作(フィルタ/結合/ソート)に使用したくない場合。
他の存在のみのコンテンツと同じように保存することができます。私たちは多くの写真をBLOBとして保存していますが、花のある画像をすべてフィルタリングしようとは思いません。
もし、中身を全く気にしないのであれば(ただ保存して、それを1つの情報として読み取るだけです)
構造体が可変である場合、物理テーブルを作成するのが難しくなり、JSONデータを扱うのが難しくなる。
構造が深くネストしている場合、物理テーブルへの格納がオーバーヘッドになる。 NO
リレーショナルテーブルのデータを使うように内部データを使いたい場合(フィルタ、インデックス、結合...)。
重複して保存する場合(冗長性を持たせる)。
一般的にはパフォーマンスの問題に直面した場合(多くの典型的なシナリオで直面することは間違いないでしょう!)。 JSONを文字列カラムやBLOBで管理し、必要なときに物理テーブルへ変更することができます。私の魔法の水晶玉は、これが明日になるかもしれないと教えてくれました :-D
パフォーマンスとディスク容量に関するアイデアはこちらでご覧ください: https://stackoverflow.com/a/47408528/5089204
以下は、SQL-Server 2016のJSONとXMLのサポートに対応しています。 ユーザー @mike123 さんの指摘で、article on an official microsoft blog に、SQL-ServerでJSONをクエリすると、XMLをクエリするよりも10倍速いことが実験で証明されているようだ。 それについて、いくつか考えてみました: 実験と照らし合わせてみました:
この実験では、XMLとJSONの性能を比較することはできませんが、多くのことを測定することができます**。同じ(変わらない)文字列に対して同じ動作を繰り返し行うことは、現実的なシナリオではない
テストした例は、一般論としてははるかに単純である!
読み込まれた値は常に同じで、使われることすらない。オプティマイザはこれを見るでしょう...
強力なXQuery
のサポートについては一言もありません!配列の中から指定されたIDを持つ製品を探す?JSONでは全体を読み込んでから WHERE
を使ってフィルタをかける必要がありますが、XML
では内部の XQuery predicate
を利用することができます。FLWOR`については言うまでもないが...。
-"experments"のコードをそのまま私のシステムで実行すると、以下のようになります:JSONは3倍速くなるようです(10倍ではありませんが)。
XPathに
/text()`を追加すると、2倍以下に減少します*。関連記事のユーザー "Mister Magoo" が既に指摘していることですが、click-bait*なタイトルはそのままです...。
このような簡単なJSONの場合、最も速い純粋なT-SQLのアプローチは、SUBSTRING
とCHARINDEX
の組み合わせでした :-D
次のコードは、より現実的な実験を示しています。
複数の「商品」を持つJSONと同一のXMLを使用する(JSONの配列と兄弟ノードの比較)
JSONとXMLは少し変更され(10000の実行数)、テーブルに挿入されます。
ファーストコールバイアス*を避けるために、両方のテーブルに対して最初の呼び出しがあります。
10000件のエントリーがすべて読み込まれ、取得された値は別のテーブルに挿入される。
GO 10`を使用すると、このブロックを10回実行し、ファーストコールバイアスを回避する。 最終的な結果は、JSONがXMLよりも遅いことを明確に示しています(まだ非常に単純な例で約1.5倍とそれほど大きくはありません)。 最後の声明です:
過度に単純化された例で、不当な状況下では、JSONはXMLより速くなる可能性がある。
JSONを扱うのは純粋な文字列アクションで、XMLはパースと変換を行います。これは、最初のアクションではかなり高価ですが、これが完了すると、すべてのスピードアップにつながります。
JSONは、1回のアクションの方が良いかもしれません(XMLの内部階層表現を作成するオーバーヘッドを回避することができます)。
まだ非常にシンプルですが、より現実的な例として、XMLは単純な読み方で速くなります。
配列から特定の要素を読み出したり、指定した ProductID が配列に含まれるすべてのエントリをフィルタリングしたり、パスを上下に移動したりする必要がある場合、JSON は持ちこたえることができません。その都度、文字列から完全に解析しなければなりません。 テストコード
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
結果(Acer Aspire v17 Nitro Intel i7, 8GB Ram上のSQL Server 2016 Express)
Test SumTime
------------------
json 2706
xml 1604
コメントにしては長すぎる。
もしそれが「絶対に間違っている」というなら、ほとんどのデータベースはそれをサポートしていないはずです。 なるほど、ほとんどのデータベースは「FROM」句のカンマをサポートしており、私はそれを「絶対に間違っている」と考えています。 しかし、JSONのサポートは新しい開発であり、後方互換性のある機能ではありません。
1つの明白なケースは、JSON構造体が単にBLOBであり、アプリケーションに引き渡される場合です。 ただし、JSONを保存するためのオーバーヘッドが発生し、すべてのレコードに共通のフィールドがある構造化データでは、不必要に冗長になります。
もう一つのケースは、「列がまばら」なケースです。 多くの列を持つ行があるが、それらは行ごとに異なる。
もう一つのケースは、レコードの中に"nested"レコードを格納したい場合です。 JSONは強力です。
JSONに、クエリを実行したいレコードに共通するフィールドがある場合は、通常、適切なデータベース・カラムに配置したほうがよいでしょう。 しかし、データは複雑であり、JSONのような形式も存在する。
魔法の杖を振ろう パッとね! JSONの使用に関するゴールデンルール:
MySQL が JSON の内部を見る必要がなく、アプリケーションが単に物のコレクションを必要とする場合、JSON は問題なく、場合によってはさらに良いこともあります。
また、MariaDB 10.0.1やMySQL 5.7(JSONデータ型と関数がある)であれば、JSONは実用的かもしれません。 MariaDB 5.3'の "Dynamic" カラムはこれの亜種です。
もしあなたが、Entity-Attribute-Value"をやっているのなら、JSONは良くないが、それはいくつかの悪のうちの最小のものである。http://mysql.rjweb.org/doc.php/eav。
インデックスされたカラムで検索する場合、JSONの中に値が埋もれてしまうことがないのは大きなプラスです。
インデックスされたカラムの範囲検索や、FULLTEXT
検索、SPATIAL
検索では、JSONは使えません。
*WHERE a=1 AND b=2の場合、複合インデックス
INDEX(a,b)` は素晴らしいものです。
JSONは、quot;sparse"データでうまく機能します。INDEXは、そのようなものでうまく機能しますが、そうではありません。 (私は、多くの行で 'missing' またはNULLである値を指しています)。
JSONは、余分なテーブルに頼ることなく、quot;array"やquot;tree"を提供することができます。 しかし、そのような配列やツリーを掘り下げるのは、アプリの中だけで、SQLではできません。
JSONはXMLより優れている。(私の意見)
アプリ以外ではJSON文字列を取得したくない場合は、(クライアントで)圧縮してBLOB
に格納することをお勧めします。 .jpgのようなもので、中にいろいろ入っていますが、SQLは気にしません。
具体的な用途をお書きください。
新しいSQL Serverは、JSONテキストを処理するための関数を提供します。 JSONとしてフォーマットされた情報は、標準のSQL Server列にテキストとして保存でき、SQL Serverは、これらの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 )
)
この単純な構造は、NoSQLデータベースで作成できる標準のNoSQLコレクションに似ています(例:. Azure DocumentDBまたはMongoDB)。IDを表すキーとJSONを表す値があります。
NVARCHARは単なるプレーンテキストではないことに注意してください。 SQL Serverには、ディスクに保存されているデータを透過的に圧縮できる組み込みのテキスト圧縮メカニズムがあります。 圧縮は言語に依存し、データに応じて最大50%増加する可能性があります(UNICODE圧縮を参照)。
SQLサーバーと他のプレーンNoSQLデータベースの主な違いは、SQL Serverを使用すると、ハイブリッドデータモデルを使用して、複数のJSONオブジェクトを同じ「コレクション」に保存し、それらを通常のリレーショナル列と組み合わせることができることです。
例として、コレクション内のすべての人がFirstNameとLastNameを持ち、その人物に関する一般的な情報を1つのJSONオブジェクトとして保存し、電話番号/メールアドレスを個別のオブジェクトとして保存できることを想像してみてください。 SQL Server 2016では、追加の構文なしでこの構造を簡単に作成できます。
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 )
)
単一のJSONオブジェクトの代わりに、この「コレクション」でデータを整理できます。 各JSON列の構造を明示的にチェックしたくない場合は、すべての列にJSONチェック制約を追加する必要はありません(この例では、EmailAddresses列にのみCHECK制約を追加しました)。
この構造を標準のNoSQLコレクションと比較すると、強く入力されたデータ(FirstNameとLastName)へのアクセスが速くなることに気付くでしょう。 したがって、このソリューションは、すべてのオブジェクトにわたって繰り返される情報を識別でき、他の変数情報をJSONとして保存できるハイブリッドモデルに適しています。このようにして、柔軟性とパフォーマンスを組み合わせることができます。
この構造をPersonテーブルAdventureWorksデータベースのスキーマと比較すると、関連する多くのテーブルが削除されていることに気付くでしょう。
スキーマのシンプルさに加えて、データアクセス操作は、複雑なリレーショナル構造と比較してシンプルになります。 これで、複数のテーブルに参加する代わりに、単一のテーブルを読むことができます。 関連情報を持つ新しい人物を挿入する必要がある場合。 (メールアドレス。, 電話番号。) AdventureWorks Personテーブルに1つのレコードを挿入する代わりに、1つのテーブルに1つのレコードを挿入できます。, アイデンティティ列を取得して、電話の保存に使用される外部キーを見つけます。, メールアドレス。, 等. さらに、このモデルでは、外部キー関係を使用してカスケード削除を行わずに、1人の行を簡単に削除できます。
NoSQLデータベースは、単純な操作、読み取り、挿入、削除操作用に最適化されています。SQLServer 2016では、リレーショナルデータベースに同じロジックを適用できます。
JSON制約。 前の例では、列に保存されているテキストが適切にフォーマットされていることを検証する単純な制約を追加する方法を確認しました。 JSONには強力なスキーマはありませんが、JSONの値を読み取る関数と標準のT-SQL関数を組み合わせることで、複雑な制約を追加することもできます。
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}')
CHECK制約により、挿入/更新プロセスが遅くなる可能性があるため、より高速な書き込みパフォーマンスが必要な場合は回避できることに注意してください。
圧縮JSONストレージ。 JSONテキストが大きい場合は、組み込みのCOMPRESS関数を使用してJSONテキストを明示的に圧縮できます。 次の例では、圧縮されたJSONコンテンツがバイナリデータとして格納され、DECOMPRESS関数を使用してJSONを元のテキストとして解凍する列が計算されています。
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))
COMPRESSおよびDECOMPRESS関数は、標準のGZip圧縮を使用します。 クライアントがGZip圧縮を処理できる場合(gzipコンテンツを理解するブラウザなど)、圧縮コンテンツを直接返すことができます。 これはパフォーマンス/ストレージのトレードオフであることに注意してください。 圧縮データを頻繁にクエリする場合は、毎回テキストを解凍する必要があるため、パフォーマンスが遅くなります。
注:JSON関数は、SQL Server 2016+およびAzure SQLデータベースでのみ使用できます。。
この記事の出典からさらに読むことができます。
https://blogs.msdn.microsoft.com/sqlserverstorageengine/2015/11/23/storing-json-in-sql-server/。
あなたが尋ねなければならない質問は:です。
このデータベースのみを使用することに縛られていますか?
行う。
1。 別のデータベースを使用してJSONを保存できる場合は、CouchDB、DynamoDB、MongoDBなどのドキュメントストレージソリューションを使用してください。 1。 これらのドキュメントストレージDBを使用して、階層データをインデックス付けして検索します。 1。 リレーショナルデータにはリレーショナルデータベースを使用します。 1。 リレーショナルデータベースを使用して、レポート、データウェアハウジング、データマイニングを行います。
しないでください。
1。 可能であればJSONを文字列として保存します。 1。 JSONデータの最大長を試してみてください。 1。 varcharを使用してJSONを格納します(必要に応じてテキスト/ブロブを使用します)。 1。 保存されたJSONを介して値を検索します。 1。 JSONをエスケープして文字列として保存することを心配しています。
Jsonは、関係のあるdbでは優れていません。 jsonを列に展開してdbに保存するのは素晴らしいことですが、jsonをblobとして保存することは、データアーカイブシステムとして使用する次です。
jsonを展開せずに1つの列に保存する理由はいくつかありますが、そのjsonフィールドの値はクエリに使用されないため(または値がすでに列に展開されているため)、決定が下されます。
また、sqlはjson処理を目的としていないため、フィールドがまったくクエリされた場合のほとんどのjson処理はsql環境の外側になります。 次に、本当の問題は、このjsonをどこに保存するか、フラットファイルとして配置し、必要に応じて他のシステム(spark / hive / etc)を介してクエリを実行することです。
私はあなたのDBアーティストに同意します。アーカイブにRDBMSを使用しないでください。 安いオプションがあります。 また、json blobは巨大になる可能性があり、時間とともにDBディスク領域を掘り下げることができます。
PostgreSQLには、組み込みの「json」および「jsonb」データ型があります。
-json。 -json vs jsonb。
これらはいくつかの例です。
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は、JSONデータをクエリするための2つのネイティブ演算子 ->
と->>
を提供します。
オペレーター ->
は、キーごとにJSONオブジェクトフィールドを返します。
オペレーター ->>
は、テキストごとにJSONオブジェクトフィールドを返します。
SELECT
info -> 'customer' AS customer
FROM
orders;
SELECT
info ->> 'customer' AS customer
FROM
orders
WHERE
info -> 'items' ->> 'product' = 'Diaper'