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


14

たとえば、ユーザーとそのすべての電話番号とメールアドレスを取得したいとします。電話番号とメールは別々のテーブルに保存されます。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、お気に入りの色、および各レコード(ユーザーが電話レコードにメールを送信する)についてユーザーテーブルに保存されている他のすべての情報を返し、おそらく帯域幅を消費して速度が低下することです結果をダウン。

ユーザーごとに1つの行を返し、そのレコード内に電子メールのリストと電話のリストがあった方が良いと思いませんか?また、データの操作がはるかに簡単になります。

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

NoSQLを使用してこれを回避することもできますが、中間点はないはずです。

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

*はい、このように設計されています。わかった。なぜ作業が簡単な代替手段がないのか疑問に思っています。SQLは実行中の処理を続けることができますが、キーワードまたは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": "johnsmith45@gmail.com",
    },
    {
        "name": "John Smith",
        "dob": "1945-05-13",
        "fav_color": "red",
        "email": "john@smithsunite.com",
    },
    {
        "name": "Jane Doe",
        "dob": "1953-02-19",
        "fav_color": "green",
        "email": "originaljane@deerclan.com",
    }
]

そして、一意の識別子でグループ化する必要があります(これも取得する必要があることを意味します!)クライアント側で結果セットを希望どおりに再フォーマットするには、これを返すだけです:

[
    {
        "name": "John Smith",
        "dob": "1945-05-13",
        "fav_color": "red",
        "emails": ["johnsmith45@gmail.com", "john@smithsunite.com"]
    },
    {
        "name": "Jane Doe",
        "dob": "1953-02-19",
        "fav_color": "green",
        "emails": ["originaljane@deerclan.com"],
    }
]

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


6
SQLをMicrosoft Excelのようなスプレッドシートと考え、内部セルを含むセル値を作成する方法を見つけてください。スプレッドシートとしてはうまく機能しなくなりました。探しているのはツリー構造ですが、スプレッドシートの利点がなくなります(つまり、ツリーの列を合計することはできません)。ツリー構造は、非常に人間が読めるレポートにはなりません。
Reactgular

54
SQLはデータを返すのが苦手ではなく、必要なものを照会するのが苦手です。経験則として、広く使用されているツールが一般的なユースケースでバグがあるか壊れていると思う場合、問題はあなたです。
ショーンマクサムシング

12
@SeanMcSomethingそれが痛いほど真実なので、私はそれを自分でもっと良く言うことはできなかった。
WernerCD

5
これは素晴らしい質問です。「これが現状だ」と言っている答えには、ポイントがありません。行のコレクションが埋め込まれた行を返すことができないのはなぜですか?
クリスピットマン

8
@SeanMcSomething:広く使用されているツールがC ++またはPHPでない限り、その場合はおそらく正しいでしょう。;)
メイソンウィーラー

回答:


11

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

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

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

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

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

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

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

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

ただし、「古い」リレーショナルデータベースは、行を操作しているのは、それが得意なことであり、問​​題や翻訳なしですべてが行と通信できるからです。

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

RFC 1925 -十二ネットワーキング真実


「ネストされたツリー構造が必要な場合、すべてのデータを一度にプルする必要があります。データベース側のカーソルの最適化はなくなりました。」-それは本当のように聞こえません。メインテーブル用に1つ、結合テーブルごとに1つ、合計2つのカーソルを保持する必要があります。インターフェースに応じて、1つの行と結合されたすべてのテーブルを1つのチャンク(部分的にストリーミング)で返すか、反復を開始するまでサブツリーをストリーミングできます(クエリを行わない場合もあります)。しかし、はい、それは多くのことを複雑にします。
mpen

3
しかし、すべての現代言語には、何らかのツリークラスが必要です。それに対処するのはドライバー次第ではないでしょうか?SQL担当者はまだ一般的なフォーマットを設計する必要があると思います(それについてはあまり知りません)。私を得るものは、結合で1つのクエリを送信し、各行(N番目の行ごとにのみ変更されるユーザー情報)の冗長データを取得してフィルタリングするか、1つのクエリを発行する(ユーザー) 、結果をループし、必要な情報を取得するために各レコードに対してさらに2つのクエリ(電子メール、電話)を送信します。どちらの方法も無駄に思えます。
mpen

51

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

あなたが経験していることは、「オブジェクト/関係インピーダンスの不一致」として知られています。これは、オブジェクト指向データモデルとリレーショナルデータモデルがいくつかの点で根本的に異なるという事実から生じる技術的困難です。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および他のフレームワークが行う方法です。本当の魔法はありません。単なる抽象化のレイヤー。


14
「まさにあなたが求めたもの」に対する+1。テクノロジーの効果的な使用方法を学ぶ必要があるという結論ではなく、テクノロジーに何か問題があるという結論にジャンプすることがよくあります。
マット

1
Hibernateは、ルートエンティティと特定のコレクションを1つのクエリで取得します。これらのコレクションに対してeager fetchモードが使用されている場合です。その場合、メモリ内のルートエンティティプロパティの削減を行います。他のORMもおそらく同じことを行うことができます。
マイクパートリッジ

3
実際、これはリレーショナルモデルのせいではありません。ネストされた関係に非常にうまく対応します。ありがとうございます。これは、SQLの初期バージョンの純粋な実装バグです。しかし、最近のバージョンでは追加されていると思います。
ジョンニルソン

8
これはオブジェクトリレーショナルインピーダンスの例ですか?リレーショナルモデルはOPの概念データモデルと完全に一致しているようです。各ユーザーは、0、1、または複数の電子メールアドレスのリストに関連付けられています。このモデルは、オブジェクト指向のパラダイムでも完全に使用できます(集計:ユーザーオブジェクトにはメールのコレクションがあります)。制限は、データベースのクエリに使用される手法にあります。これは実装の詳細です。階層的なデータを返すクエリテクニックがあります。たとえば、.Netの階層的なDataSets
MarkJ

@MarkJを回答として書き留めてください。
ミンダミンダー

12

組み込み関数を使用して、レコードを連結できます。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  hr@m.com, tb@me.com, nunya@what.com   231-123-1234, 231-123-1235

これは、OPが実行しようとしていることに最も近い(SQLでの)ソリューションのようです。彼は潜在的に、EmailAddressesとPhoneNumbersの結果をリストに分割するために、クライアント側の処理を行う必要があります。
Mr.Mindor

2
電話番号に「セル」、「自宅」、「職場」などの「タイプ」がある場合はどうなりますか?さらに、電子メールアドレスにコンマが技術的に許可されています(引用符で囲まれている場合)-それをどのように分割しますか?
mpen

10

これの問題は、ユーザーの名前、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機能があります


最終的に、クエリを次のように書き換えると、問題解決する可能性があると思います。

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

1
*の使用は推奨されませんが、彼の問題の核心ではありません。電話番号とメールの両方がユーザーと1対多の関係にあるため、ユーザー列を0個選択しても、重複効果が発生する可能性があります。個別であっても、電話番号がphone1 / name @ hotmail.com、phone1 / name @ google.comのように2回表示されることはありません。
mike30

6
-1:「問題解決する可能性あります」と言うのは、からleft joinにどのような効果が変わるかわからないということですinner join。この場合、これによりユーザーが不平を言っている「繰り返し」は減りません。電話やメールがないユーザーを単に省略します。ほとんど改善しません。また、「左側のすべてのレコードから右側のすべてのレコード」を解釈すると、ON基準がスキップされ、デカルト積に固有のすべての「間違った」関係が排除されますが、繰り返しフィールドはすべて保持されます。
ハビエル

@Javier:はい、だからこそ、実際に左結合が必要だと言ったのですか、それとも内部結合で十分だったのでしょうか?* OPの問題の説明により、内部結合の結果を期待しているように聞こえます。もちろん、サンプルデータや彼らが本当に欲しかったものの説明がなければ、言うのは難しいです。私は実際に人々(私が働いている人)がこれを行うのを見たので提案しました:間違った結合を選択し、得られた結果を理解していないときに文句を言います。それをて、私はそれがここで起こったかもしれないと思った。
FrustratedWithFormsDesigner

3
質問のポイントがありません。この仮定の例では、私がしたいすべてのユーザーデータ(名前、生年月日など)をして、私はすべての彼/彼女の電話番号をしたいです。内部参加は、メールも電話も持たないユーザーを除外します-それはどのように役立ちますか?
mpen

4

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

結合は、2セットを並べて配置すると考えることができます。「オン」条件は、各セットのレコードがどのように一致するかです。ユーザーが3つの電話番号を持っている場合、ユーザー情報に3回の重複が表示されます。クエリによって、ギザギザのない長方形のセットを作成する必要があります。これは、1対多の関係でセットを結合するという性質にすぎません。

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

select * from Phones where user_id=344;

このクエリの結果は、まだ四角形のないギザギザのセットです。セットの世界のすべてがそうです。


2

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

そうでない場合は、データベースでこのデータセットをまとめてから、結合の結果である各行で繰り返される値をすべて削除し、必ずしも同じ名前または電話番号を持つ2人のような重複データを持つ行自体を削除する必要があります。帯域幅を節約するためのオーバーヘッドが大きいようです。フィルタリングを改善し、不要な列を削除することで、より少ないデータを返すことに集中する方が良いでしょう。Select *は依存する生産ウェルでは決して使用されないためです。


「ほとんどのデータベースが1回の呼び出しで3つの個別のデータセットを返せず、結合もできない理由はありません」-1回の呼び出しで3つの個別のデータセットを返す方法 3つの異なるクエリを送信する必要があり、各クエリ間に遅延が発生すると考えましたか?
mpen

ストアドプロシージャは1つのトランザクションで呼び出され、必要な数のデータセットを返します。「SelectUserWithEmailsPhones」sprocが必要な場合があります。
グラハム

1
@Mark:同じバッチの一部として複数のコマンドを(少なくともSQLサーバーで)送信できます。cmdText = "select * from b; select * from a; select * from c"そして、それをsqlcommandのコマンドテキストとして使用します。
-jmoreno

2

非常に単純に、ユーザークエリと電話番号クエリの明確な結果が必要な場合は、データを結合しないでください。

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

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

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


1

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

;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

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

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

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

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


-4

Plsは、列(連絡先)の複数の行(電話番号)をグループ化するSTUFF関数の使用法を指します。この関数は、行(ユーザー)の区切り値の単一セルとして抽出できます。

今日、これを広範囲に使用していますが、CPUとパフォーマンスの問題に直面しています。XMLデータ型は別のオプションですが、クエリレベルではなく設計変更です。


5
これが質問をどのように解決するかについて詳しく説明してください。「Plsの使用方法を指す」と言うのではなく、質問がどのように達成されるかを例を示してください。また、物事を明確にするサードパーティのソースを引用することも役立ちます。
bitsoflogic

1
STUFFスプライスに似ているように見えます。それが私の質問にどのように当てはまるかわかりません。
mpen
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.