2017-04-06 16 views
7

userに関連するeventsをすべて取得するための、最も単純で効率的なSQLクエリを作成したいと考えています。SQLでネストされたリレーションシップを簡単かつ効率的にクエリする方法はありますか?

enter image description here

注意するカップルの事を:


セットアップ

は、ここに私のスキーマがどのように見えるかの単純化した表現だ

  • usersmemberships経由teamsに属し。
  • teamsは、多くがcollections,appsおよびwebhooksであることがあります。
  • collectionsもまた、多くのwebhooksを持つことができます。
  • webhooksは、teamまたはcollectionのいずれかに属することができますが、1つのみです。
  • eventsは、任意のオブジェクトに属することができますが、1つだけです。

これは、ほとんどのSaaS型企業(例えば、スラックまたはストライプ)が持つようなかなり基本的な設定のようです。すべてがチームによって「所有」されていますが、ユーザーはチームに属し、インターフェイスとやりとりします。セットアップは、私が解決SQLクエリを作成したいことを考えると


問題

...

が関連しているすべてのイベント(直接的または間接的に)にして下さい指定されたユーザーはidです。

直接または特定の手段で間接的に検索されるクエリを簡単に書き込むことができます。たとえば...

は直接 idにより、ユーザに関連するすべてのイベントを検索します。

SELECT * 
FROM events 
WHERE user_id = ${id} 

それとも...

間接的自分のチームを介してユーザに関連するイベントのすべてを検索します。

SELECT events.* 
FROM events 
JOIN memberships ON memberships.team_id = events.team_id 
WHERE memberships.user_id = ${id} 

たり...

が間接的
が自分のチームのいずれかのコレクションを介してユーザに関連するイベントのすべてを検索します。

SELECT events.* 
FROM events 
JOIN collections ON collections.id = events.collection_id 
JOIN memberships ON memberships.team_id = collections.team_id 
WHERE memberships.user_id = ${id} 

彼らは2つの異なる方法で関連することができますので、ウェブフックは、より複雑な取得...

は、任意のウェブフックを介してユーザに関連する間接的あるすべてのイベントを探しますそのチームやコレクションの

SELECT * 
FROM events 
WHERE webhook_id IN (
    SELECT webhooks.id 
    FROM webhooks 
    JOIN memberships ON memberships.team_id = webhooks.team_id 
    WHERE memberships.user_id = ${id} 
) 
OR webhook_id IN (
    SELECT webhooks.id 
    FROM webhooks 
    JOIN collections ON collections.id = webhooks.collection_id 
    JOIN memberships ON memberships.team_id = collections.team_id 
    WHERE memberships.user_id = ${id} 
) 

しかし、あなたが見ることができるように、これらすべての経路を介して、発生したイベントに関連するユーザーのためのさまざまな方法がたくさんあります!私は成功し、それらに関連するイベントのすべてを取得するクエリにしようとすると、だから、それはのように見てしまい...

SELECT * 
FROM events 
WHERE user_id = ${id} 
OR app_id IN (
    SELECT apps.id 
    FROM apps 
    JOIN memberships ON memberships.team_id = apps.team_id 
    WHERE memberships.user_id = ${id} 
) 
OR collection_id IN (
    SELECT collections.id 
    FROM collections 
    JOIN memberships ON memberships.team_id = collections.team_id 
    WHERE memberships.user_id = ${id} 
) 
OR memberships_id IN (
    SELECT id 
    FROM memberships 
    WHERE user_id = ${id} 
) 
OR team_id IN (
    SELECT team_id 
    FROM memberships 
    WHERE user_id = ${id} 
) 
OR webhook_id IN (
    SELECT webhooks.id 
    FROM webhooks 
    JOIN memberships ON memberships.team_id = webhooks.team_id 
    WHERE memberships.user_id = ${id} 
) 
OR webhook_id IN (
    SELECT webhooks.id 
    FROM webhooks 
    JOIN collections ON collections.id = webhooks.collection_id 
    JOIN memberships ON memberships.team_id = collections.team_id 
    WHERE memberships.user_id = ${id} 
) 

質問

  • 最終的には非常にクエリを「すべて含まれている」ということです非効率的な?
  • もっと効率的な書き方がありますか?
  • 後で読むのが簡単で、読みやすい方法がありますか?
+1

ここでは3つの異なるデータベースシステムにタグを付けました。 – DavidG

+2

これは、私の友人のよく書かれた質問です。あなたがすでにそれを解決しようとしているのを見て、あなたが仕事をしていることを示しています。 –

答えて

4

私がそれを速くすることができると思う唯一のことは、共用体を使用することです。

SELECT e.* 
FROM events e 
WHERE user_id = ${id} 
UNION 
select e.* 
    FROM apps a 
    join events e on a.apps_id = e.apps_id 
    JOIN memberships ON memberships.team_id = apps.team_id 
    WHERE memberships.user_id = ${id} 
UNION 
select e.* 
from 
    FROM collections c 
    join events e on e.collections_id = c.collections_id 
    JOIN memberships ON memberships.team_id = collections.team_id 
    WHERE memberships.user_id = ${id} 
UNION 
select e.* 
    FROM memberships m 
    join events e on e.memberships_id = e.memberships_id 
    WHERE user_id = ${id} 
UNION 
...; 
+1

ここにUNIONまたはUNION ALLが必要ですか? UNIONは、照会が相互に排他的ではないが、結果は一意でなければならないことを意味する。通常、クエリのソートステップが実行されます。 UNIONは、結果が重複しないようにするか、サブクエリが(この場合のように)互いに排他的であることが保証されているため、通常は余分な並べ替えを伴わず、高速です。 – joshp

+0

これが元の "OR .... IN"クエリよりも高速かどうかは、おそらく特定のデータベースに大きく依存します。 – joshp

5

は、任意のクエリと同じように、最も効率的な方法は、「それが依存」です。テーブルの行数、行の長さ、インデックスの有無、サーバー上のRAMなど多くの変数があります。

この種の問題を処理するにはどうすればいいか考えてみてくださいCTEを使用することで、一時的な結果を作成し、その結果をクエリ全体に再利用することができます。あなたはそれに対して複数回参加できるようにするCTEは、表のようにキーワードWITH、および本質的結果に別名を使用します。

WITH user_memberships AS (
    SELECT * 
    FROM memberships 
    WHERE user_id = ${id} 
), user_apps AS (
    SELECT * 
    FROM apps 
    INNER JOIN user_memberships 
     ON user_memberships.team_id = apps.team_id 
), user_collections AS (
    SELECT * 
    FROM collections 
    INNER JOIN user_memberships 
     ON user_memberships.team_id = collections.team_id 
), user_webhooks AS (
    SELECT * 
    FROM webhooks 
    LEFT OUTER JOIN user_collections ON user_collections.id = webhooks.collection_id 
    INNER JOIN user_memberships 
     ON user_memberships.team_id = webhooks.team_id 
     OR user_memberships.team_id = user_collections.team_id 
) 

SELECT events.* 
FROM events 
WHERE app_id IN (SELECT id FROM user_apps) 
OR collection_id IN (SELECT id FROM user_collections) 
OR membership_id IN (SELECT id FROM user_memberships) 
OR team_id IN (SELECT team_id FROM user_memberships) 
OR user_id = ${id} 
OR webhook_id IN (SELECT id FROM user_webhooks) 
; 

このようにそれをすることのメリットは以下のとおりです。

  1. 各CTEすることができます適切なJOIN述語のインデックスを利用し、実行プランナに一連の複合述語を解決させようとするのではなく、そのサブセットの結果をより速く返す
  2. CTEを個別に管理できるため、サブセットのトラブルシューティングが容易になります
  3. あなたはDRY原則CTEは、クエリの外の値を持つ場合
  4. に違反していない、あなたの代わりに
3

私はあなたが持っているどのくらいのコントロールがわからないストアドプロシージャや参照に移動することができますあなたのスキーマ上。答えが「なし」の場合は、それ以上読み取らないでください。あなたの状況に適切ではない場合に備えて、ここに詳細を書き込むつもりはありませんが、それは私の所有権モデルのように見えます。

すなわち

BASETABLE

同上

IdOwner(BASETABLE上のIDにFK - 非常に重要)

タイプ(ユーザー= 0、アプリケーション= 1、コレクション= 2などまたは列挙を使用)

ベースにアプリ

ID(FK表)

コレクションBASETABLEに

ID(FK)

会員BASETABLEに

ID(FK)

ウェブフック

ID(FK t O BASETABLE)(FK

イベントBASETABLEに

ID(FK)

会員

TEAM_ID)BASETABLEへ

チーム

ID( BASETABLEやチームへのFK)

USER_ID(BASETABLEまたはユーザーへのFK)

ユーザー

ID(BASETABLEにFK)

は、その後、あなたのクエリは、再帰CTE次のようになります。 「検索します私はすべてのタイプのオブジェクトを所有しています。つまり、最終的にユーザーxが所有していたイベントです。 "

これで、あなたはイベントテーブルに参加しなければならないIDのリストを得ることができます。あなたのオブジェクトを持っている。

この種のモデルでは、基本テーブルに何かをロードする必要がありますが、このようなネストされた所有権は非常にうまく動作するため、少し毛深いものになります。

私はこれをコメントとして投稿したいと思いますが、フォーマットすると消えてしまいますので、回答として投稿しました。それが助けになったら、ちょっとした詳細を自由に感じて、私に戻ってほしい。

私は完全にその点を逃したが、これは私に叫ばないでください(あまりにも前にそれを持っていた)ちょうど "感謝、アダム、しかしそれは役に立たない"と私は消して。

種類:

アダム。

関連する問題