web-dev-qa-db-ja.com

RDBMSがネストされた形式で結合テーブルを返さないのはなぜですか?

たとえば、ユーザーとそのすべての電話番号とメールアドレスを取得したいとします。電話番号と電子メールは別々のテーブルに保存されます。1人のユーザーから多数の電話/電子メール。私はこれを非常に簡単に行うことができます:

_SELECT * FROM users user 
    LEFT JOIN emails email ON email.user_id=user.id
    LEFT JOIN phones phone ON phone.user_id=user.id
_

この問題*は、ユーザー名、DOB、お気に入りの色、およびユーザーテーブルに保存されているその他すべての情報を、各レコード(users emails phones records)に対して何度も繰り返し返すことです。 、おそらく帯域幅を消費し、結果を遅くします。

ユーザーごとに1行を返し、そのレコード内にlistのメールとlistの電話があったほうがいいのではないでしょうか。これにより、データの操作もはるかに簡単になります。

LINQまたはおそらく他のフレームワークを使用してこのような結果を得ることができることは知っていますが、リレーショナルデータベースの基礎となる設計の弱点のようです。

NoSQLを使用することでこれを回避することができますが、中間的な根拠はありませんか?

何か不足していますか?なぜこれが存在しないのですか?

*はい、このように設計されています。わかった。作業しやすい代替手段がないのはなぜでしょうか。 SQLは実行中の処理を続行できますが、次に、1つまたは2つのキーワードを追加して、デカルト積ではなくネストされた形式でデータを返す少しの後処理を行うことができます。

これは任意のスクリプト言語で実行できることは承知していますが、SQLサーバーが冗長データ(以下の例)を送信するか、SELECT email FROM emails WHERE user_id IN (/* result of first query */)のような複数のクエリを発行する必要があります。


MySQLがこれに似たものを返すようにする代わりに:

_[
    {
        "name": "John Smith",
        "dob": "1945-05-13",
        "fav_color": "red",
        "email": "[email protected]",
    },
    {
        "name": "John Smith",
        "dob": "1945-05-13",
        "fav_color": "red",
        "email": "[email protected]",
    },
    {
        "name": "Jane Doe",
        "dob": "1953-02-19",
        "fav_color": "green",
        "email": "[email protected]",
    }
]
_

そして、いくつかの一意の識別子でグループ化する必要があります(つまり、それもフェッチする必要があります!)クライアント側で、結果セットを希望どおりに再フォーマットし、次のように返します。

_[
    {
        "name": "John Smith",
        "dob": "1945-05-13",
        "fav_color": "red",
        "emails": ["[email protected]", "[email protected]"]
    },
    {
        "name": "Jane Doe",
        "dob": "1953-02-19",
        "fav_color": "green",
        "emails": ["[email protected]"],
    }
]
_

または、3つのクエリを発行することもできます。1つはユーザー、1つはメール、1つは電話番号ですが、メールと電話番号の結果セットにuser_idを含めて、ユーザーと照合できるようにする必要があります。以前にフェッチしました。繰り返しになりますが、冗長なデータと不要な後処理。

14
mpen

深いところにある、リレーショナルデータベースのすべての行と列です。これは、リレーショナルデータベースが動作するように最適化された構造です。 カーソル 個々の行を一度に処理します。一部の操作は一時テーブルを作成します(ここでも、行と列である必要があります)。

行のみを処理して行のみを返すことにより、システムはメモリとネットワークトラフィックをより適切に処理できます。

前述のように、これにより特定の最適化を行うことができます(インデックス、結合、ユニオンなど)。

ネストされたツリー構造が必要な場合は、データを一度にallプルする必要があります。データベース側のカーソルの最適化はなくなりました。同様に、ネットワーク上のトラフィックは1つの大きなバーストになり、行ごとの遅いトリクルよりもはるかに長くかかる可能性があります(これは、今日のWebの世界では時々失われるものです)。

すべての言語はその中に配列を持っています。これらは、操作やインターフェースが簡単なものです。非常に原始的な構造を使用することにより、データベースとプログラムの間のドライバーは、どの言語であっても共通の方法で機能します。ツリーを追加し始めると、言語の構造がより複雑になり、トラバースが困難になります。

プログラミング言語が、返された行を他の構造に変換することはそれほど難しくありません。それをツリーまたはハッシュセットにするか、反復可能な行のリストとして残します。

ここには仕事の歴史もあります。昔は構造化データの転送は醜いものでした。 EDI形式を見て、何が求められているかを理解してください。ツリーは再帰も意味します-一部の言語ではサポートされていませんでした(昔の2つの最も重要な言語ではサポートされていませんでした) t再帰をサポート- 再帰はF90までFortranに入りませんでした とCOBOLの時代もそうではありませんでした)。

そして、今日の言語は再帰とより高度なデータ型をサポートしていますが、実際に変更する正当な理由はありません。彼らは働き、彼らはうまく働きます。 変化するものは、nosqlデータベースです。ツリーをドキュメントベースのドキュメントのドキュメントに保存できます。 LDAP(実際には古い)もツリーベースのシステムです(ただし、あなたが求めているものとは異なります)。誰が知っているか、おそらくnosqlデータベースの次のことは、クエリをjsonオブジェクトとして返すものでしょう。

ただし、「古い」リレーショナルデータベースは、行を処理します。これは、それが得意であり、すべてが問題や変換なしにそれらと通信できるためです。

  1. プロトコル設計では、追加するものが残っていないときではなく、削除するものが残っていないときに完全に達しています。

から RFC 1925-The Twelve Networking Truths

11
user40980

それはあなたが要求したものを正確に返しています:結合によって定義されたデカルト積を含む単一のレコードセット。それがまさにあなたが望むものである有効なシナリオはたくさんあります。そのため、SQLが悪い結果を与えている(そして、それを変更した方が良いことを意味している)と言っても、実際には多くのクエリが失敗します。

発生しているのは " Object/Relational Impedance Mismatch、 "と呼ばれ、オブジェクト指向のデータモデルとリレーショナルデータモデルが根本的にいくつかの点で異なるという事実から生じる技術的な問題です。 LINQおよびその他のフレームワーク(ORM、オブジェクト/リレーショナルマッパーとして知られていますが、偶然ではありません)は、魔法のように「これを回避する」ことはできません。異なるクエリを発行するだけです。 SQLでも実行できます。ここに私がそれをする方法があります:

SELECT * FROM users user where [criteria here]

ユーザーのリストを繰り返し、IDのリストを作成します。

SELECT * from EMAILS where user_id in (list of IDs here)
SELECT * from PHONES where user_id in (list of IDs here)

そして、あなたは参加するクライアント側を行います。これは、LINQと他のフレームワークが行う方法です。関連する本当の魔法はありません。単なる抽象化のレイヤーです。

51
Mason Wheeler

組み込み関数を使用して、レコードを連結することができます。 MySQLではGROUP_CONCAT()関数を使用でき、OracleではLISTAGG()関数を使用できます。

MySQLでのクエリの例を以下に示します。

SELECT user.*, 
    (SELECT GROUP_CONCAT(DISTINCT emailAddy) FROM emails email WHERE email.user_id = user.id
    ) AS EmailAddresses,
    (SELECT GROUP_CONCAT(DISTINCT phoneNumber) FROM phones phone WHERE phone.user_id = user.id
    ) AS PhoneNumbers
FROM users user 

これは次のようなものを返します

username    department       EmailAddresses                        PhoneNumbers
Tim_Burton  Human Resources  [email protected], [email protected], [email protected]   231-123-1234, 231-123-1235
12
Linger

これの問題は、ユーザーの名前、DOB、お気に入りの色、および保存されている他のすべての情報が返されることです

問題は、あなたが十分に選択的でないことです。あなたが言ったときにあなたはすべてを求めました

Select * from...

...そしてあなたはそれを手に入れました(DOBと好きな色を含む)。

あなたはおそらくもう少し(エヘン)...選択的である必要があり、次のようなことを言った:

select users.name, emails.email_address, phones.home_phone, phones.bus_phone
from...

userが複数のemailレコードに結合する可能性があるため、重複のように見えるレコードが表示される可能性もありますが、これら2つを区別するフィールドはSelectステートメントにありません。だからあなたは次のようなことを言いたいかもしれません

select distinct users.name, emails.email_address, phones.home_phone, phones.bus_phone
from...

...レコードごとに何度も何度も...

また、LEFT JOIN。これにより、結合の左側のすべてのレコード(つまり、users)が右側のすべてのレコードに結合されます。つまり、次のようになります。

左外部結合は、内部結合からのすべての値と、右のテーブルに一致しない左のテーブルのすべての値を返します。

http://en.wikipedia.org/wiki/Join_(SQL)#Left_outer_join

だから別の質問はあなたが実際に左結合を必要とするか、あるいはINNER JOIN十分ですか?それらは非常に異なるタイプの結合です。

ユーザーごとに1つの行が返され、そのレコード内にメールのリストがあった場合、これは適切ではありません。

実際に結果セット内の単一の列に、オンザフライで生成されるリストを含める場合、それは可能ですが、使用しているデータベースによって異なります。 Oracleには listagg関数 があります。


最終的に、私はあなたの問題mightが次のようなものに近いクエリを書き換えれば解決すると思います:

select distinct users.name, users.id, emails.email_address, phones.phone_number
from users
  inner join emails on users.user_id = emails.user_id
  inner join phones on users.user_id = phones.user_id

クエリは常に、長方形の(ギザギザでない)表形式のデータセットを生成します。セット内にネストされたサブセットはありません。セットの世界では、すべてがネストされていない純粋な長方形です。

結合は、2つのセットを並べて配置することと考えることができます。 「オン」状態は、各セットのレコードがどのように照合されるかです。ユーザーが3つの電話番号を持っている場合、ユーザー情報に3回重複が表示されます。長方形の非ジャグセットは、クエリによって生成される必要があります。それは単に、1対多の関係でセットを結合する性質です。

必要なものを取得するには、Mason Wheelerが説明しているような別のクエリを使用する必要があります。

select * from Phones where user_id=344;

このクエリの結果は、長方形のギザギザでないセットです。セットの世界のすべてがそうであるように。

4
mike30

ボトルネックが存在する場所を決定する必要があります。データベースとアプリケーション間の帯域幅は通常、かなり高速です。ほとんどのデータベースが1回の呼び出しで3つの個別のデータセットを返せず、結合もできなかった理由はありません。その後、必要に応じて、アプリでそれらをすべて一緒に結合します。

それ以外の場合は、データベースでこのデータセットをまとめて、結合の結果である各行のすべての繰り返し値を削除する必要があります。必ずしも、同じ名前または電話番号を持つ2人のように、データ自体が重複しているとは限りません。帯域幅を節約するために多くのオーバーヘッドのようです。フィルタリングを改善し、不要な列を削除することで、より少ないデータを返すことに焦点を当てた方がよいでしょう。 Select *は、依存する本番環境では決して使用されないためです。

2
JeffO

非常に単純に、ユーザークエリと電話番号クエリの異なる結果が必要な場合はデータを結合しないでください。そうしないと、他の人が「セット」を指摘したり、データにすべての行の追加フィールドが含まれたりします。

結合のあるクエリではなく、2つの異なるクエリを発行します。

ストアドプロシージャまたはインラインパラメーター化されたsql craft 2クエリで、両方の結果を返します。ほとんどのデータベースと言語は複数の結果セットをサポートしています。

たとえば、SQL ServerとC#は、IDataReader.NextResult()を使用してこの機能を実現しています。

2
Jon Raynor

何か不足しています。データを非正規化する場合は、自分で行う必要があります。

;with toList as (
    select  *, Stuff(( select ',' + (phone.phoneType + ':' + phone.PhoneNumber) 
                    from phones phone
                    where phone.user_id = user.user_id
                    for xml path('')
                  ), 1,1,'') as phoneNumbers
from users user
)
select *
from toList
1
jmoreno

リレーショナルクロージャの概念は、基本的に、クエリの結果が、ベーステーブルであるかのように他のクエリで使用できるリレーションであることを意味します。クエリを構成可能にするため、これは強力な概念です。

SQLでネストされたデータ構造を出力するクエリを記述できる場合、この原則を破ることになります。ネストされたデータ構造はリレーションではないため、さらにクエリを実行したり、他のどのリレーションに結合したりするには、新しいクエリ言語、またはSQLへの複雑な拡張が必要になります。

基本的には、リレーショナルDBMSの上に階層型DBMSを構築します。それは疑わしい利益のためにはるかに複雑になり、一貫したリレーショナルシステムの利点を失います。

SQLから階層的に構造化されたデータを出力できると便利な場合がある理由を理解していますが、これをサポートするためにDBMS全体で追加された複雑さのコストは、明らかに価値がありません。

1
JacquesB