2017-04-06 61 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表中,并且你有你的对象。

这种类型的模型确实有些毛茸茸,因为要加载任何必须与基表连接的东西,但对于这种嵌套所有权,它的工作原理非常好。

我想发布这个作为评论,但如果我这样做格式化将消失,所以我已经发布它作为答案。如果它有帮助,并且您想要更多细节,请随时与我联系。

如果我完全错过了这一点,这并没有帮助,请不要喊我(如果之前有这样的),只是说“谢谢,亚当,但这并没有帮助”,我会删除它。

亲切的问候,

亚当。