リレーショナルデータベースがネストされた形式で情報を返すことをサポートしないのはなぜですか?


46

ブログを構築していて、投稿やコメントが必要だとします。そこで、自動インクリメント整数「id」列を持つ「posts」テーブルと、外部キー「post_id」を持つ「comments」テーブルの2つのテーブルを作成します。

次に、おそらく最も一般的なクエリを実行します。クエリとは、投稿とそのコメントをすべて取得することです。リレーショナルデータベースはかなり新しいので、私にとって最も明白なアプローチは、次のようなクエリを作成することです。

SELECT id, content, (SELECT * FROM comments WHERE post_id = 7) AS comments
FROM posts
WHERE id = 7

これにより、必要な投稿のIDとコンテンツ、および配列(JSONで使用するようなネストされた表現)にきちんとパッケージ化されたすべての関連するコメント行が得られます。もちろん、SQLおよびリレーショナルデータベースはこのようには機能せず、最も近い方法は、「投稿」と「コメント」の間の結合を行うことです。これにより、多くの不必要なデータの重複が返されます(同じ投稿情報が繰り返されます)すべての行で)、つまり、データベースをまとめて処理するためと、ORMですべてを解析して元に戻すための両方の処理時間が費やされます。

ORMに投稿のコメントを熱心に読み込むように指示した場合でも、最善の方法は、投稿に対する1つのクエリをディスパッチし、次にすべてのコメントを取得する2番目のクエリをディスパッチしてからクライアント側にまとめることです。また非効率的です。

リレーショナルデータベースは実証済みのテクノロジーである(地獄、私よりも古い)こと、そして何十年にもわたって膨大な量の研究が行われていることを理解しています。そして、それらには本当に正当な理由があると確信しています(そしてSQL標準)は、そのように機能するように設計されていますが、上記で説明したアプローチが不可能な理由はわかりません。私は、レコード間の最も基本的な関係の1つを実装する最も単純で明白な方法であるように思えます。リレーショナルデータベースがこのようなものを提供しないのはなぜですか?

(免責事項:私は主にRailsとNoSQLデータストアを使用してwebappを書いていますが、最近Postgresを試しました。実際にそれが大好きです。リレーショナルデータベースを攻撃するつもりはありません。ただ困惑しています。)

Railsアプリを最適化する方法や、特定のデータベースでこの問題を回避する方法を尋ねるのではありません。私は直感に反し、無駄に思えるのに、なぜSQL標準がこのように機能するのかを尋ねています。SQLの元の設計者が結果をこのようにしたかったという歴史的な理由がいくつかあるに違いありません。


1
すべての組織がそのように機能するわけではありません。hibernate / nhibernateは結合の指定を可能にし、1つのクエリからオブジェクトツリー全体を積極的にロードできます。
ネイサンゴンザレス

1
また、議論の興味深いポイントながら、私は必ずこれはANSI SQLの男との出会いがなくて本当に釈明ではないよ
ネイサン・ゴンザレス

@nathan:ええ、すべてではありません。私はSequelを使用しており、特定のクエリ(docs)にどのアプローチを選択するかを選択できますが、それでも複数クエリアプローチを推奨しています(パフォーマンス上の理由から、私は思う)。

5
RDBMSはセットを保存および取得するように設計されているため、表示用のデータを返すことを意図していません。MVCのように考えてください。なぜモデルを遅くしたり使用しにくくしたりして、ビューを実装しようとするのでしょうか。RDBMSには、NoSQLデータベースにはない(およびその逆)利点があります。問題を解決するための適切なツールであるため、それを使用している場合、表示するデータを返すように要求することはありません。

1
彼らはxmlを
イアン

回答:


42

CJ Dateについては、SQLとリレーショナル理論の第7章と付録Bで詳しく説明しています。その通りです。すべての行で同じリレーションタイプである限り、属性のデータタイプがリレーション自体になることを禁止するリレーショナル理論はありません。あなたの例は資格があるでしょう。

しかし、Date氏によると、関係の階層は非対称であるため、このような構造は「通常-常にではなく-禁忌」(つまり、悪い考え)です。たとえば、ネスト構造からおなじみの「フラット」構造への変換は、ネストを再作成するために常に元に戻すことはできません。

クエリ、制約、および更新はより複雑で、記述が難しく、リレーションシップ値属性(RVA)を許可する場合、RDBMSがサポートするのが難しくなります。

また、関係の最適な階層がそれほど明確ではないため、データベース設計の原則を混乱させます。サプライヤと、特定のサプライヤによって供給される部品のネストされたRVAの関係を設計する必要がありますか?または、特定の部品を供給するサプライヤ用の、ネストされたRVAと部品の関係ですか?または、両方を保存して、さまざまな種類のクエリを簡単に実行できるようにしますか?

これは、階層型データベースドキュメント指向のデータベースモデルから生じる同じジレンマです。最終的に、ネストされたデータ構造へのアクセスの複雑さとコストにより、設計者はデータを冗長に保存し、さまざまなクエリで簡単に検索できるようになります。リレーショナルモデルは冗長性を妨げるため、RVAはリレーショナルモデリングの目標に反して機能します。

私が理解していることから(私はそれらを使用したことはありません)、RelDataphorは、関係値属性をサポートするRDBMSプロジェクトです。


@dportasからのコメント:

構造化型はSQL-99の一部であり、Oracleはこれらをサポートしています。ただし、ベーステーブルの行ごとにネストされたテーブルに複数のタプルを格納することはありません。一般的な例は、ベーステーブルの1つの列のように見える「住所」属性ですが、通り、市、郵便番号などのサブ列がさらにあります。

ネストされたテーブルもOracleでサポートされており、ベーステーブルの行ごとに複数のタプルを使用できます。しかし、これが標準SQLの一部であることは知りません。そして、あるブログの結論に留意してください。「CREATE TABLEステートメントでネストされたテーブルを使用することは決してありません。それらを再びネスト化するために時間を費やします!」


3
あるリレーションを別のリレーションに実際に保存したくない-それらは別々のテーブルにあり、通常どおり非正規化されます 結合モデルよりも直感的に思えるのに、クエリでこの種の結果の埋め込みが許可されていない理由をたずねています。
PreciousBodilyFluids

結果セットとテーブルは一種です。日付はそれらをそれぞれ関係relvar呼びます(例えれば、42は整数ですが、変数xは整数42の値を持つことができます)。同じ操作がリレーションとrelvarにも適用されるため、それらの構造には互換性が必要です。
ビルカーウィン

2
標準SQLはネストされたテーブルをサポートします。それらは「構造化タイプ」と呼ばれます。Oracleは、この機能を持つDBMSの1つです。
nvogel

2
データの重複を避けるために、クエリをフラットなデータ複製方法で記述する必要があると主張するのは少しばかげたことではありませんか?
イーモンネルボンヌ

1
@EamonNerbonne、関係演算の対称性。たとえば、投影。RVAからいくつかのサブ属性を選択した場合、元の階層を再現するために、結果セットに対して逆の操作を適用するにはどうすればよいですか?:私はあなたが彼が書いたものを見ることができるように日の本のページのページ293は、Googleブックスである見つけbooks.google.com/...
ビルKarwin

15

初期のデータベースシステムの一部は、階層型データベースモデルに基づいていました。これは、あなたがここで提案しているように、親と子を持つ構造のようなツリーでデータを表しました。HDMSは、リレーショナルモデルに基づいて構築されたデータベースにほぼ取って代わられました。これの主な理由は、RDBMSが階層型データベースでは困難な「多対多」関係をモデル化できることと、RDBMSが元の設計の一部ではないクエリを簡単に実行できるのに対し、HDBMSは設計時に指定されたパスを介してクエリするように制約したことです。

野生の階層型データベースシステム、特にWindowsレジストリおよびLDAPの例がまだいくつかあります。

この主題の広範な報道は、次の記事で利用可能です


10

データベースは確かな論理に基づいており、理論的根拠に基づいており、参照整合性、同時実行性を確保しながら(2次元)セットのデータを保存、操作、取得する非常に良い仕事をしているという事実にあなたの質問は本当に集中していると思います他の多くのものは、オブジェクト指向形式または階層形式と呼ばれるものでデータを送信(および受信)する(追加の)機能を提供しません。

そして、「ORMに投稿のコメントを熱心に読み込むように指示しても、投稿の1つのクエリを送信し、次にすべてのコメントを取得する2番目のクエリを送信して、それらをまとめることが最善だクライアント側、これも非効率的です "

次の2つのクエリを送信し、2つの結果のバッチを受信するのに非効率なものはありません。

--- Query-1-posts
SELECT id, content 
FROM posts
WHERE id = 7


--- Query-2-comments
SELECT * 
FROM comments 
WHERE post_id = 7

私はそれが(ほとんど)最も効率的な方法であると主張します(ほとんど、あなたが本当に必要ではposts.idなく、すべての列ではありませんcomments.*

トッドが彼のコメントで指摘したように、表示する準備ができたデータを返すようデータベースに要求するべきではありません。それを行うのはアプリケーションの仕事です。(1つまたはいくつかの)クエリを記述して、すべての表示操作に必要な結果を取得できるため、dbからアプリケーションにワイヤ(またはメモリバス)経由で送信されるデータに不必要な重複がありません。

私はORMについて本当に話すことはできませんが、おそらくそれらのいくつかは私たちのためにこの仕事の一部をすることができます。

Webサーバーとクライアント間のデータの配信にも同様の手法を使用できます。他の手法(キャッシングなど)が使用されるため、データベース(またはWebまたはその他のサーバー)が重複した要求で過負荷になりません。

私の推測では、SQLのような標準は、1つの分野に特化したままで、フィールドのすべての領域をカバーしようとしない場合に最適です。

一方で、SQL標準を設定する委員会は、将来、別の方法で考え、そのような追加機能の標準化を提供する可能性があります。しかし、それは一晩で設計できるものではありません。


1
私のアプリケーションでは、1つだけではなく2つのデータベース呼び出しのオーバーヘッドと遅延が発生するという意味で、非効率的でした。それとは別に、結合を行うことは、表示の準備ができている形式でデータを返すだけではありませんか?または、データベースビューを使用していますか?必要に応じて、より小さなクエリを実行し、アプリでそれらをつなぎ合わせるだけで、それらを回避することもできますが、それらは依然として有用なツールです。私が提案していることは、使いやすく、パフォーマンスが高いことを除けば、結合とは大きく異なるとは思いません。

2
@Precious:複数のクエリを実行するためにオーバーヘッドを増やす必要はありません。ほとんどのデータベースでは、1つのバッチで複数のクエリを送信し、1つのクエリから複数の結果セットを受け取ることができます。
ダニエルプライデン

@PreciousBodilyFluids-ypercubeの答えのSQLスニペットは、単一のデータベース呼び出しで送信され、単一の応答で2つの結果セットを返す単一のクエリです。
Carson63000

5

私は適切な議論された答えで答えることができないので、間違っているなら私を忘却に落とすことをお気軽にお願いします(しかし、私たちが何か新しいことを学ぶことができるように私を修正してください)。その理由は、リレーショナルデータベースがリレーショナルモデルに集中しているためだと思います。リレーショナルモデルは、「ファーストオーダーロジック」と呼ばれるものについて私が何も知らないことに基づいています。おそらく、リレーショナルデータベースが構築されている数学的/論理的な枠組みに概念的に適合しないかもしれません。さらに、あなたが求めることは一般にグラフデータベースによって簡単に解決され、達成したいものと競合するのはデータベースの基礎概念であるというより多くのヒントを与えます。


5

FOR XMLを使用する場合、少なくともSQLServerはネストされたクエリをサポートします。

SELECT id, content, (SELECT * FROM comments WHERE post_id = posts.id FOR XML PATH('comments'), TYPE) AS comments
FROM posts
WHERE id = 7
FOR XML PATH('posts')

ここでの問題は、RDBMSからのサポートの欠如ではなく、テーブル内のネストされたテーブルのサポートの欠如です。

また、内部結合の使用を妨げるものは何ですか?

SELECT id, content, comments.*
FROM posts inner join comments on comments.post_id = posts.id
WHERE id = 7

実際には、内部結合をネストされたテーブルとして見ることができます。最初の2つのフィールドの内容のみが繰り返し表示される場合があります。結合のパフォーマンスについてはあまり心配しません。このようなクエリの遅い部分は、データベースからクライアントへのioだけです。これは、コンテンツに大量のデータが含まれる場合にのみ問題になります。その場合、2つのクエリを提案します。1つselect id, contentは内部結合、もう1つは内部結合and select posts.id, comments.*です。まだ2つのクエリしか使用しないため、これは複数の投稿でもスケーリングします。


質問はこれに対処します。2回のラウンドトリップを行う必要がある(最適ではない)か、最初の2列に冗長データを返す必要があります(これも最適ではありません)。彼は最適なソリューションを望んでいます(私の意見では非現実的ではありません)。
スコットホイットロック

私は知っていますが、最適な解決策として悪いことはありません。私が主張できる唯一のことは、オーバーヘッドが最小になる場所と依存する場所です。最適なソリューションが必要な場合は、ベンチマークを行い、さまざまなアプローチを試してください。特定の状況によってはXMLソリューションでさえ遅くなる可能性があり、NoSQLデータストアに慣れていないため、に似たものがあるかどうかはわかりませんfor xml
ドルス

5

実際、Oracleは必要なものをサポートしていますが、サブクエリを「cursor」キーワードでラップする必要があります。結果はオープンカーソルを介して取得されます。たとえば、Javaでは、コメントは結果セットとして表示されます。詳細は、「CURSOR Expression」に関するOracleのドキュメントを参照してください

SELECT id, content, cursor(SELECT * FROM comments WHERE post_id = 7) AS comments
FROM posts
WHERE id = 7

1

ネストをサポートするものもあります(階層的)。

1つのクエリが必要な場合は、自己参照するテーブルを1つ作成できます。一部のRDMSはこの概念をサポートしています。たとえば、SQL Serverを使用すると、階層クエリの共通テーブル式(CTE)を使用できます。

あなたの場合、投稿はレベル0になり、すべてのコメントはレベル1になります。

他のオプションは、2つのクエリ、または返されたすべてのレコード(他の人が言及した)の追加情報を含む結合です。

階層の例:

https://stackoverflow.com/questions/14274942/sql-server-cte-and-recursion-example

上記のリンクでは、EmpLevelはネスト(または階層)のレベルを示しています。


SQL Serverのサブ結果セットに関するドキュメントが見つかりません。CTEを使用している場合でも。結果セットとは、厳密に型指定された列が十分にあるデータ行を意味します。回答に参照を追加できますか?
サンドロック14年

@SandRock-データベースは、SQLクエリから単一の結果セットを送り返します。クエリ自体のレベルを識別することにより、処理する必要がある階層またはネストされた結果セットを作成できます。現時点では、ネストされたデータを返すことに最も近いと思います。
ジョンレイナー14年

0

申し訳ありませんが、あなたの問題を正確に理解しているかどうかはわかりません。

MSSQLでは、2つのSQLステートメントを実行するだけです。

SELECT id, content
FROM posts
WHERE id = 7

SELECT * FROM comments WHERE post_id = 7

また、2つの結果セットが同時に返されます。


質問をしている人は、データベースへの往復が2回発生するため、これは効率が悪いと言っています。通常、オーバーヘッドのために往復を最小限にしようとします。彼は1回往復して両方のテーブルを取り戻したいと考えています。
スコットホイットロック

しかし、それは一往復になります。stackoverflow.com/questions/2336362/...
ビフMaGriff

0

RDBMは理論に基づいており、理論に固執しています。これにより、優れた一貫性と数学的に実証された信頼性が実現します。

モデルはシンプルであり、理論に基づいているため、人々は最適化と多くの実装を簡単に行うことができます。これは、誰もがわずかに異なるNoSQLとは異なります。

過去に階層型データベースを作成する試みがありましたが、IIRC(カントはそれをグーグルするようです)には問題がありました(サイクルと平等が思い浮かびます)。


0

特定のニーズがあります。必要な形式でデータベースからデータを抽出することをお勧めします。そのため、必要なデータを使用できます。

データベースではうまくいかないこともありますが、とにかくそれを行うためにそれらを構築することは不可能ではありません。他のアプリケーションにフォーマットを残すことは現在の推奨事項ですが、それができない理由を正当化するものではありません。

あなたの提案に対して私が持っている唯一の主張は、この結果セットを「sql」の方法で処理できることです。データベースで結果を作成したり、ある程度操作したり操作したりすることはできません。あなたが提案する方法で構築されたビューを作成したとしましょう。別のselectステートメントにそれを含めるにはどうすればよいですか?データベースは、結果を取得し、それらを使用して処理を行います。別のテーブルにどのように参加しますか?結果セットを別の結果セットと比較するにはどうすればよいですか?

RDMSの利点は、sqlの柔軟性です。テーブルからデータを選択するための構文は、システム内のユーザーまたは他のオブジェクトのリストにかなり近いものです(少なくともそれが目標です)。完全に異なることを行うことに意味があるかどうかはわかりません。手続き型のコード/カーソル、またはデータのBLOBを非常に効率的に処理できるようになることすらありません。


0

私の意見では、主にSQLと集計クエリの実行方法が原因です。集計関数とグループ化は大きな2次元行セットで実行され、結果を返します。それは当初からの方法であり、非常に高速です(ほとんどのNoSQLソリューションは集約のため非常に遅く、複雑なクエリではなく非正規化スキーマに依存しています)

もちろん、PostgreSQLにはオブジェクト指向データベースの機能がいくつかあります。このメール(メッセージ)によると、カスタム集計を作成することで必要なものを実現できます。

個人的には、Doctrine ORM(PHP)のようなフレームワークを使用しています。これは、アプリケーション側で集約を行い、遅延ロードなどの機能をサポートしてパフォーマンスを向上させます。


0

PostgreSQLは、ArraysJSONなど、さまざまな構造化データ型をサポートしています。SQLまたは埋め込み手続き言語の1つを使用して、任意の複雑な構造を持つ値を構築し、アプリケーションに返すことができます。また、任意の構造化タイプの列を持つテーブルを作成することもできますが、デザインを不必要に非正規化しているかどうかを慎重に検討する必要があります。


1
これは、以前の13の回答で作成され説明されたポイントに対して実質的なものを提供していないようです
-gnat

質問は特にJSONに言及しており、この回答は、少なくとも1つのRDBMSからのクエリでJSONを返すことができることを指摘する唯一の回答です。私はむしろ、それが誤った前提に基づいているため、決定的な答えを期待できないと言って質問にコメントしたいと思います。ただし、StackExchangeはそれを許可しません。
ジョナサンロジャース
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.