SQLテーブルにJSONやXMLデータを保存できるのはいつからか
質問
を使用する場合
SQL
または
MySQL
(またはリレーショナルDB) - データを通常の列に保存する方がインデックスのためや他の目的には良いことは理解していますが...。
ことは、ロードと保存
JSON
のデータをロードして保存することは、時にはもっとシンプルで、開発をより簡単にするものです。
生のデータを保存するための黄金律はありますか?
JSON
のデータをDBに保存するための黄金律はありますか?
絶対にやってはいけないことなのでしょうか?
どのように解決するのですか?
主な質問事項は
- このデータで何をするつもりですか? と
- このデータをどのようにフィルタリング/ソート/結合/操作するのですか?
JSON は (XML のように) データ交換、小さなストレージ、一般的に定義された構造には最適ですが、RDBMS 内で実行する一般的なアクションに参加することはできません。ほとんどの場合、JSON データを 通常のテーブル に転送し、必要なときにJSONを再作成する方がよいでしょう。
XML / JSONと 1.NF
正規化の最初のルールは、1つの列に2つ以上の情報を格納しないことです。PersonName" という列に "Mickey Mouse" のような値があるのを見ましたか?あなたはこれを指差して叫ぶのです。 すぐに変更してください!
XMLやJSONはどうでしょうか?これらのタイプは1.NFを壊しているのでしょうか?まあ、イエスでもありノーでもあるのですが...。
完全な構造を保存することは全く問題ありません。 を1つの情報 もしそれが 1ビットの情報 を実際に使ってみてください。あなたはSOAPレスポンスを取得し、将来の参照にこれが必要かもしれないので、それを保存したいと思います(しかし、あなたは このデータを自分自身の処理に使用しない )? ちょうどそれを格納する そのまま !
では
を表す複雑な構造 (XML または JSON) があるとします。
(住所、詳細...を含む)。今、あなたはこの
を一つのカラムに
PersonInCharge
. これは間違っているのでしょうか?これはむしろ、XML/JSONの代わりに外部キー参照で適切に設計された関連テーブルに住むべきではありませんか?特に、同じ人が多くの異なる行で発生する可能性がある場合、XML/JSONのアプローチを使用することは間違いなく間違っています。
しかし、今度は、履歴データを保存する必要性を想像してください。あなたは を持続させたい。 が必要です。何日か後に、その人が新しい住所を教えてくれましたか?問題ありません。古い住所はXML/JSONに保存されているので、もし必要なら...。
結論。
データを保存するだけなら問題ないのですが もしこのデータが
ユニーク
の部分であれば、大丈夫ですが...。
しかし、もしあなたが
内部部品
が定期的に必要な場合や、冗長な二重保存を意味する場合は、大丈夫ではありません......。
物理ストレージ
以下はSQL Serverの場合であり、他のRDBMでは異なる場合があります。
XMLは目に見えるテキストとしてではなく、階層ツリーとして保存されます。これをクエリすると、驚くほど性能が良くなります! この構造は文字列レベルではパースされません!
SQL Server(2016+)のJSONは文字列で生きているため、パースする必要があります。ネイティブのXML型があるように)本当のネイティブJSON型は存在しません。これは後になるかもしれませんが、今のところ、JSON は SQL Server 上の XML ほどパフォーマンス良くはないと考えています(セクション
アップデイト 2
). JSONから値を読み出す必要がある場合、非常に多くの隠し文字列メソッド呼び出しが必要になるでしょう...。
これはあなたにとって何を意味するのでしょうか?
あなたの 愛すべきDBアーティスト :-D は知っている、その格納 JSON をそのまま というのは、RDBMの一般的な原則に反しています。 彼は知っている。
- JSON はおそらく 1.NF を壊している。
- JSONが時間と共に変化する可能性があること(同じカラム、異なるコンテンツ)。
- JSONは読みにくく、フィルタリングや検索、結合、ソートが難しい。
- そのような操作は、貧しい小さなDBサーバーにかなりの余分な負荷をシフトすること。
いくつかの回避策はありますが(使用しているRDBMSに依存)、そのほとんどは思い通りにいきません...。
質問に対する答えを簡単に言うと
はい
-
もし、あなたが
に格納されているデータを使用したくない場合、そのデータは
内に
JSON
は、高価な操作 (filter/join/sort) のために使用されます。
これを他の だけが存在します。 のような内容になっています。私たちは多くの画像をBLOBとして保存していますが、花のあるすべての画像をフィルタリングしようとは思わないでしょう...。 - 中身を全く気にしない場合(ただ保存して1つの情報として読むだけ)
- 構造が可変で、物理的なテーブルを作成するのが難しく、JSONデータで作業する場合。
- 構造が深くネストされている場合、物理テーブルへの格納は多くのオーバーヘッドになります。
NO
- リレーショナルテーブルのデータを使うように内部データを使いたい場合(フィルタ、インデックス、結合...)
- 重複して保存する場合(冗長性を持たせる)
- 一般的には パフォーマンスの問題に直面した場合 (多くの典型的なシナリオで問題に直面することは間違いありません!)
JSONを文字列カラムまたはBLOBとして開始し、必要なときに物理テーブルに変更することができます。私の魔法の水晶玉は、これが明日になるかもしれないと告げています :-D
最新情報
パフォーマンスとディスク容量に関するアイデアは、こちらでご覧いただけます。 https://stackoverflow.com/a/47408528/5089204
UPDATE 2: パフォーマンスについての詳細...
以下は、SQL-Server 2016のJSONとXMLのサポートについてです。
ユーザー @mike123 が指摘した マイクロソフトの公式ブログの記事 という実験で証明されているようです。 JSONをクエリすることは 10倍速い であり、XMLのクエリ をSQL-Serverでクエリします。
それについていくつか考えてみました。
"experiment"とのいくつかのクロスチェック。
- その 実験"実験"は多くのことを測定しますが、XML対JSONの性能は測定しません。 . 同じ (変更されていない) 文字列に対して同じアクションを繰り返し実行することは、現実的なシナリオではありません。
- テストされた例は 一般的なステートメントとしては単純すぎる !
- 読み込まれた値は常に同じであり、使用すらされない。オプティマイザーはこれを見ることになります...
-
については一言もなく、強大な
XQuery
をサポートしています! 配列の中から指定された ID を持つ製品を見つけるのですか?JSON は全体を読み込んでから、フィルタとしてWHERE
を使用する必要があります。XML
は内部のXQuery predicate
. について話すのではなくFLWOR
... - 実験コード そのまま を私のシステムで実行すると、表示されます。JSON は 3 倍速くなったようです (ただし 10 倍ではありません)。
-
追加
/text()
にXPath
は、これを2倍以下 . 関連記事の中で、ユーザーの "Mister Magoo" が既に指摘していることですが クリックベイト というタイトルはそのままです...。 -
このような簡単なJSONが与えられたので、最速の純粋なT-SQLのアプローチは、以下の組み合わせでした。
SUBSTRING
とCHARINDEX
:-D
次のコードは、より現実的な実験を示しています。
-
JSONと同一のXMLに複数の
Product
(JSONの配列と兄弟ノードの比較) - JSONとXMLを少し変えて(10000の実行数字)、テーブルに挿入しています。
- を避けるために、両方のテーブルに対して初期呼び出しがあります。 ファーストコールバイアス
- 10000件のエントリーを全て読み込み、取得した値を別のテーブルに挿入します。
-
使用方法
GO 10
を使用すると、このブロックが10回実行され ファーストコールバイアス
最終的な結果は、JSONがXMLよりも遅いことを明確に示しています。 (それほどでもないが、まだ非常に単純な例で約1.5倍)です。
最終的なステートメントです。
- 不当な状況下で過度に単純化された例では、JSON は XML よりも高速になる可能性があります。
- JSON を処理することは 純粋な文字列操作 で、XMLはパースと変換が行われます。これは最初のアクションではかなり高価ですが、一度これが行われると、すべてのスピードアップにつながります。
- JSONは 一度だけ アクションの方が良いかもしれません (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
関連
-
[解決済み] JSONでコメントを使用することはできますか?
-
[解決済み] cURLでJSONデータをPOSTするにはどうすればよいですか?
-
[解決済み] PHPでSQLインジェクションを防ぐにはどうしたらいいですか?
-
[解決済み] SQLテーブルで重複する値を検索する
-
[解決済み] JSONデータをファイルに書き込むにはどうしたらいいですか?
-
[解決済み] Chromeを使用してASP.NET Web APIがXMLの代わりにJSONを返すようにするにはどうすればよいですか?
-
[解決済み] SQLのIN句をパラメータ化する
-
[解決済み] SQL JOIN - WHERE句とON句の比較
-
[解決済み】SQL Serverで既存のテーブルにデフォルト値を持つカラムを追加する
-
[解決済み] SQL ServerでINNER JOINを使用して削除するにはどうすればよいですか?
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
SQLラーニングノート--オペランドには1つのカラムが必要です。
-
解決策:java.sql.SQLException: ユーザー ''@'localhost'' (パスワード: YES を使用) のアクセスが拒否されました。
-
SQL-初心者のためのベスト入門SQLクエリ I
-
[解決済み] SQLのSELECTでIF...THENを実行するにはどうすればよいですか?
-
[解決済み] SQL Serverでレコードを削除した後、IDシードをリセットする。
-
[解決済み] PostgreSQLで一重引用符で囲まれたテキストを挿入する
-
[解決済み] SQL Serverには、.NETのMath.Maxのような2つの値を取るMax関数はありますか?
-
[解決済み] "ON UPDATE CASCADE "を使用する場合について
-
[解決済み] SQLサーバーで行を列に効率的に変換する
-
[解決済み] SQL/mysql - Select distinct/UNIQUE but return all column?