2017-04-06 99 views
7

我正在尋找編寫最簡單,最有效的SQL查詢來檢索與給定的user相關的所有events如何簡單高效地查詢SQL中的嵌套關​​系?


設置

這裏是什麼我的架構看起來像一個簡單的表示:

enter image description here

幾件事情要注意:

  • users通過memberships屬於teams
  • teams可以有許多collections,appswebhooks
  • collections也可以有很多webhooks
  • webhooks可以屬於teamcollection,但只有一個。
  • events可以屬於任何對象,但只有一個。

這似乎是大多數SaaS類型公司都會擁有的基本設置(例如Slack或Stripe)。一切都由團隊「擁有」,但用戶屬於團隊並與界面交互。


問題

鑑於設置,我想創建一個解決一個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} 

網絡掛接得到一個更復雜的,因爲他們可以在兩種不同的方式有關......

找出所有通過任何網絡掛接與用戶間接事件他們的團隊或收藏。

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,等等等等

我能想到的處理這類問題的最好辦法(思可維護性和一個braod方法效率)是通過使用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原則
  4. 如果CTE具有查詢之外值,可以將它移動到一個存儲過程,並說明,而不是
3

我不知道你有多大的控制在你的模式上。如果答案是「無」,則不要再閱讀。我不會把太多細節在這裏下來的情況下,它不適合你的情況,但它看起來像一個所有權模式給我。

BaseTable

標識

IdOwner(FK與Id上BaseTable - 非常重要)

類型(用戶= 0,應用程序= 1,類別= 2等,或使用枚舉)

應用

ID(FK至基礎表)

收集

ID(FK到BaseTable)

會員

ID(FK到BaseTable)

網絡掛接

ID(FK Ť ØBaseTable)

ID(FK到BaseTable)

活動

ID(FK到BaseTable)

成員

TEAM_ID( FK到Basetable或團隊)

USER_ID(FK到Basetable或用戶)

用戶

ID(FK到BaseTable)

然後將查詢變成一個遞歸CTE: 「查找我擁有的所有類型的事件 - 或最終由用戶擁有x「

這會給你一個id列表,然後你必須加入到你的Events表中,並且你有你的對象。

這種類型的模型確實有些毛茸茸,因爲要加載任何必須與基表連接的東西,但對於這種嵌套所有權,它的工作原理非常好。

我想發佈這個作爲評論,但如果我這樣做格式化將消失,所以我已經發布它作爲答案。如果它有幫助,並且您想要更多細節,請隨時與我聯繫。

如果我完全錯過了這一點,這並沒有幫助,請不要喊我(如果之前有這樣的),只是說「謝謝,亞當,但這並沒有幫助」,我會刪除它。

親切的問候,

亞當。