SQL 查询 - 加入多对多关系,有选择地过滤/加入

Posted

技术标签:

【中文标题】SQL 查询 - 加入多对多关系,有选择地过滤/加入【英文标题】:SQL query - Joining a many-to-many relationship, filtering/joining selectively 【发布时间】:2013-06-12 23:14:35 【问题描述】:

我发现自己在使用 SQL 查询时遇到了一些不可行的情况,我希望自己遗漏了一些东西或者可能会学到一些新东西。我正在使用的 DB2 数据库的结构并不完全适合这种查询,但我的任务是......

假设我们有餐桌人员和餐桌组。组可以包含多个人,一个人可以是多个组的一部分。是的,已经很乱了。无论如何,有几个中间表将两者联系起来。问题是我需要从组列表开始,获取这些组中的所有人员,然后获取这些人员所属的所有组,这将是初始组集的超集。这意味着从团体开始,加入到人中,然后返回并再次加入团体。我还需要结果集中两个表的信息,因此排除了一些技术。

我必须将它与许多其他表结合起来以获取更多信息,并且查询变得庞大、繁琐且缓慢。我想知道是否有某种方法可以从 People 开始,将其加入 Groups,然后指定如果一个人在提供的一组组中拥有一个组(通过子查询完成),那么 ALL 组因为那个人应该被退回。我不知道实现这一点的方法,但我在想(希望)有一种相对干净的方法可以在 SQL 中实现这一点。

一个快速而肮脏的例子:

SELECT ...
FROM GROUPS g
  JOIN LINKING_A a 
     ON g.GROUPID = a.GROUPID
        AND GROUPID IN (subquery)
  JOIN LINKING_B b 
     ON a.GROUPLIST = b.GROUPLIST
  JOIN PEOPLE p 
     ON b.PERSONID = p.PERSONID
    --This gets me all people affiliated with groups, 
    -- but now I need all groups affiliated with those people...
  JOIN LINKING_B b2 
     ON p.PERSONID = b2.PERSONID
  JOIN LINKING_A a2 
     ON b2.GROUPLIST = a.GROUPLIST
  JOIN GROUPS g2
     ON a2.GROUPID = g.GROUPID

然后我可以从结果集中的 p 和 g2 返回信息。你可以看到我在哪里遇到了麻烦。这是在一些大表上的很多连接,更不用说在这个查询中执行的许多其他连接了。我需要能够通过将 PEOPLE 加入 GROUPS 来进行查询,然后指定如果任何人在子查询中有关联的组,它应该返回与 PEOPLE 中的条目相关联的所有组。我在想 GROUP BY 可能只是一件事,但我还没有充分使用它来真正知道。因此,如果 Bill 是组 A、B 和 C 的一部分,并且我们的子查询返回一个包含组 A 的集合,则结果集应该包括 Bill 以及组 A、B 和 C。

【问题讨论】:

【参考方案1】:

以下是获取提供的组列表中的人所在的所有组的更短的方法。这有帮助吗?

Select g.*
From Linking_B b
   Join Linking_B b2
      On b2.PersonId = b.PersonId
   Join Group g
      On g.GroupId = b2.GroupId
Where b.Groupid in (SubQuery)

【讨论】:

也许我做得不对,但它不起作用。有两个链接表,在其中一个上进行自连接并从那里拆分不会返回所有组,只返回子查询中的人和组。【参考方案2】:

我不清楚为什么您同时拥有 Linking_A 和 Linking_B。通常,您只需要一个具有 GroupID 和 PersonId 的关联表来表示两个主表之间的多对多关系。

我经常建议使用“通用表表达式”[CTE's],以帮助您将问题分解成更易于理解的块。 CTE 使用 WITH 子句指定,在开始主 SELECT 查询之前可以包含多个 CTE。

我将假设您要开始的组列表由您的子查询指定,因此这将是第一个 CTE。下一个选择属于这些组的人。查询的最后部分然后选择这些人所属的组,并返回两个主表中的列。

WITH g1 as
(subquery)
, p1 as
(SELECT p.*
   from g1
   join Linking a1  on g1.groupID=a1.groupID
   join People  p   on p.personID=a1.personID )
SELECT p1.*, g2.*
  from p1
  join Linking a2   on p2.personID=a2.personID
  join Groups  g2   on  g2.groupID=a2.groupID

【讨论】:

但这并不能避免两次跨链接表连接的成本。由于对与此特定查询无关的其他数据进行了规范化,因此存在多个链接表。 好吧,您几乎没有选择跨链接表连接两次,因为问题的基本性质涉及两个级别的关联 - 所有属于特定组的人,然后是这些人所属的所有组。您必须以一种或另一种方式在某个时候遍历每一层的链接信息。 我们所说的每个表的行数是多少?您正在处理什么样的性能要求,或有任何特殊问题?起始组是静态的,还是会改变?【参考方案3】:

我想我会先建立您想要为其提取记录的人员列表,然后使用它来查询这些人员的所有组。这将适用于添加了适当连接的任意数量的链接表:

with persons_wanted as
(
     --figure out which people are in a group you want to include
     select p.person_key
     from person p
     join link l1
     on p.person_key = l1.person_key
     join groups g
     on l1.group_key = g.group_key
     where g.group name in ('GROUP_I_WANT_PEOPLE_FROM', 'THIS_ONE_TOO')
     group by p.person_key --we only want each person_key once
)
--now pull all the groups for the list of people in at least one group we want
select p.name as person_name, g.name as group_name, ...
from person p
join link l1
on p.person_key = l1.person_key
join groups g
on l1.group_key = g.group_key
where p.person_key in (select person_key from persons_wanted);

【讨论】:

问题是我需要避免遍历那些链接表两次。当我这样做时,查询的成本太高了。 鉴于您要执行的操作,我看不出有任何方法可以避免这种情况。 WITH 子句仍然可能会有所帮助,因为它将您的人员列表移动到临时表中并且只执行一次初始连接。如果做不到这一点,您是否考虑过使用包含人员列表的临时表或物化查询,并针对它运行更简单的查询以提取组列表?

以上是关于SQL 查询 - 加入多对多关系,有选择地过滤/加入的主要内容,如果未能解决你的问题,请参考以下文章

根据特定的多对多关系过滤 Django 查询集

SQL:多对多关系和“ALL”子句

多对多关系过滤器

是否可以使用 findAll() 创建查询并通过使用来自 pivot 的 ForeignKey(关系多对多)获得过滤结果?

多对多注释未在选择查询中生成连接

多对多关系过滤器