如何简单高效地查询SQL中的嵌套关系?
我正在寻找编写最简单,最有效的SQL查询来检索与给定的user
相关的所有events
。如何简单高效地查询SQL中的嵌套关系?
设置
这里是什么我的架构看起来像一个简单的表示:
几件事情要注意:
-
users
通过memberships
属于teams
。 -
teams
可以有许多collections
,apps
和webhooks
。 -
collections
也可以有很多webhooks
。 -
webhooks
可以属于team
或collection
,但只有一个。 -
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}
)
问题
- 那是最后的“全部纳入”非常查询效率低下?
- 有没有更有效的方法来编写它?
- 有没有更简单,更易于阅读的方式来编写它?
我能想到的唯一的事情就让它更快一点是使用工会。
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
...;
与任何查询一样,最有效的方法是“取决于”。有很多变量在起作用 - 行的表格数,行长度,指数是否存在,在服务器上的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)
;
做这种方式的好处是:
- 每个CTE可以利用适当JOIN谓词上的索引并更快地返回该子集的结果,而不是让执行计划员尝试解析一系列复杂谓词
- CTE可以单独维护,使子集的故障排除问题更容易
- 你没有违反DRY原则
- 如果CTE具有查询之外值,可以将它移动到一个存储过程,并说明,而不是
我不知道你有多大的控制在你的模式上。如果答案是“无”,则不要再阅读。我不会把太多细节在这里下来的情况下,它不适合你的情况,但它看起来像一个所有权模式给我。
即
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表中,并且你有你的对象。
这种类型的模型确实有些毛茸茸,因为要加载任何必须与基表连接的东西,但对于这种嵌套所有权,它的工作原理非常好。
我想发布这个作为评论,但如果我这样做格式化将消失,所以我已经发布它作为答案。如果它有帮助,并且您想要更多细节,请随时与我联系。
如果我完全错过了这一点,这并没有帮助,请不要喊我(如果之前有这样的),只是说“谢谢,亚当,但这并没有帮助”,我会删除它。
亲切的问候,
亚当。
您在此处标记了3个不同的数据库系统,请仅使用一个。 – DavidG
这是一个写得很好的问题,我的朋友,我可以看到你已经试图自己解决它,表明你已经投入了工作。 –