如何在SQL中简单有效地查询嵌套关系?

时间:2017-04-06 23:43:35

标签: sql postgresql

我希望编写最简单,最有效的SQL查询来检索与给定events相关的所有user

设置

这是我的架构的简单表示形式:

enter image description here

有几点需要注意:

  • users通过teams属于memberships
  • teams可以包含多个collectionsappswebhooks
  • 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}

Webhooks变得更复杂,因为它们可以通过两种不同的方式相关联......

  

通过其团队或馆藏的任何网络链接,查找与用户间接相关的所有事件。

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}
)

问题

  • 是最后的#34;所有包括在内"查询非常低效?
  • 有没有更有效的方法来写它?
  • 是否有更简单,更易于阅读的方式来编写它?

3 个答案:

答案 0 :(得分:6)

与任何查询一样,最有效的方法是"它取决于"。有很多变量在起作用 - 表中的行数,行长度,是否存在索引,服务器上的RAM等等。

我能想到处理这类问题的最佳方法(思考可维护性和效率的braod方法)是通过使用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原则
  4. 如果CTE的值超出查询范围,您可以将其移动到存储过程并引用该

答案 1 :(得分: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
...;

答案 2 :(得分:3)

我不知道您对架构有多少控制权。如果答案是"没有"然后不再阅读。我不打算在这里放一些细节,以防它不适合你的情况,但它看起来像是我的所有权模型。

<强> BaseTable

编号

IdOwner(基础表上的FK到Id - 非常重要)

键入(User = 0,App = 1,Collection = 2等,或使用枚举)

应用

Id(FK到BaseTable)

<强>集合

Id(FK到BaseTable)

<强>成员

Id(FK到BaseTable)

<强>网络挂接

Id(FK到BaseTable)

<强>团队

Id(FK到BaseTable)

<强>事件

Id(FK到BaseTable)

<强>成员

Team_Id(FK到Basetable或Team)

User_Id(FK到Basetable或用户)

用户

Id(FK到BaseTable)

然后您的查询成为递归CTE: &#34;找到我所拥有的所有类型的对象 - 或者最终由用户x&#34;

拥有

这会为您提供一个ID列表,然后您必须加入“事件”表并获得对象。

这种模型确实有点毛茸茸,因为加载你必须加入基表的任何东西,但对于这种嵌套的所有权,它的效果非常好。

我想将此作为评论发布,但如果我这样做,格式化将会消失,因此我将其作为答案发布。如果它有帮助,你想要更多细节随时回复我。

如果我完全错过了这一点并且这并没有帮助,请不要对我大喊大叫(之前有过这样的话)只是说'#34;谢谢,亚当,但那并没有#39; t help&#34;我会删除它。

亲切的问候,

亚当。